summaryrefslogtreecommitdiffstats
path: root/browser/modules
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules')
-rw-r--r--browser/modules/AboutHome.jsm175
-rw-r--r--browser/modules/AboutNewTab.jsm43
-rw-r--r--browser/modules/AttributionCode.jsm123
-rw-r--r--browser/modules/BrowserUITelemetry.jsm888
-rw-r--r--browser/modules/BrowserUsageTelemetry.jsm468
-rw-r--r--browser/modules/CastingApps.jsm164
-rw-r--r--browser/modules/ContentClick.jsm98
-rw-r--r--browser/modules/ContentCrashHandlers.jsm922
-rw-r--r--browser/modules/ContentLinkHandler.jsm147
-rw-r--r--browser/modules/ContentObservers.jsm55
-rw-r--r--browser/modules/ContentSearch.jsm566
-rw-r--r--browser/modules/ContentWebRTC.jsm393
-rw-r--r--browser/modules/DirectoryLinksProvider.jsm1255
-rw-r--r--browser/modules/E10SUtils.jsm128
-rw-r--r--browser/modules/Feeds.jsm104
-rw-r--r--browser/modules/FormSubmitObserver.jsm235
-rw-r--r--browser/modules/FormValidationHandler.jsm157
-rw-r--r--browser/modules/HiddenFrame.jsm86
-rw-r--r--browser/modules/LaterRun.jsm172
-rw-r--r--browser/modules/NetworkPrioritizer.jsm194
-rw-r--r--browser/modules/PermissionUI.jsm595
-rw-r--r--browser/modules/PluginContent.jsm1132
-rw-r--r--browser/modules/ProcessHangMonitor.jsm397
-rw-r--r--browser/modules/ReaderParent.jsm102
-rw-r--r--browser/modules/RecentWindow.jsm67
-rw-r--r--browser/modules/RemotePrompt.jsm110
-rw-r--r--browser/modules/Sanitizer.jsm22
-rw-r--r--browser/modules/SitePermissions.jsm269
-rw-r--r--browser/modules/TransientPrefs.jsm24
-rw-r--r--browser/modules/URLBarZoom.jsm51
-rw-r--r--browser/modules/Windows8WindowFrameColor.jsm53
-rw-r--r--browser/modules/WindowsJumpLists.jsm579
-rw-r--r--browser/modules/WindowsPreviewPerTab.jsm862
-rw-r--r--browser/modules/moz.build47
-rw-r--r--browser/modules/offlineAppCache.jsm20
-rw-r--r--browser/modules/webrtcUI.jsm969
36 files changed, 0 insertions, 11672 deletions
diff --git a/browser/modules/AboutHome.jsm b/browser/modules/AboutHome.jsm
deleted file mode 100644
index 639194c20..000000000
--- a/browser/modules/AboutHome.jsm
+++ /dev/null
@@ -1,175 +0,0 @@
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "AboutHomeUtils", "AboutHome" ];
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
- "resource:///modules/AutoMigrate.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
- "resource://gre/modules/Promise.jsm");
-
-// Should be bumped up if any data content format changes.
-const STARTPAGE_VERSION = 5;
-
-this.AboutHomeUtils = {
- /*
- * showKnowYourRights - Determines if the user should be shown the
- * about:rights notification. The notification should *not* be shown if
- * we've already shown the current version, or if the override pref says to
- * never show it. The notification *should* be shown if it's never been seen
- * before, if a newer version is available, or if the override pref says to
- * always show it.
- */
- get showKnowYourRights() {
- // Look for an unconditional override pref. If set, do what it says.
- // (true --> never show, false --> always show)
- try {
- return !Services.prefs.getBoolPref("browser.rights.override");
- } catch (e) { }
- // Ditto, for the legacy EULA pref.
- try {
- return !Services.prefs.getBoolPref("browser.EULA.override");
- } catch (e) { }
-
- if (!AppConstants.MC_OFFICIAL) {
- // Non-official builds shouldn't show the notification.
- return false;
- }
-
- // Look to see if the user has seen the current version or not.
- var currentVersion = Services.prefs.getIntPref("browser.rights.version");
- try {
- return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
- } catch (e) { }
-
- // Legacy: If the user accepted a EULA, we won't annoy them with the
- // equivalent about:rights page until the version changes.
- try {
- return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted");
- } catch (e) { }
-
- // We haven't shown the notification before, so do so now.
- return true;
- }
-};
-
-/**
- * This code provides services to the about:home page. Whenever
- * about:home needs to do something chrome-privileged, it sends a
- * message that's handled here.
- */
-var AboutHome = {
- MESSAGES: [
- "AboutHome:RestorePreviousSession",
- "AboutHome:Downloads",
- "AboutHome:Bookmarks",
- "AboutHome:History",
- "AboutHome:Addons",
- "AboutHome:Sync",
- "AboutHome:Settings",
- "AboutHome:RequestUpdate",
- "AboutHome:MaybeShowAutoMigrationUndoNotification",
- ],
-
- init: function() {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
-
- for (let msg of this.MESSAGES) {
- mm.addMessageListener(msg, this);
- }
- },
-
- receiveMessage: function(aMessage) {
- let window = aMessage.target.ownerGlobal;
-
- switch (aMessage.name) {
- case "AboutHome:RestorePreviousSession":
- let ss = Cc["@mozilla.org/browser/sessionstore;1"].
- getService(Ci.nsISessionStore);
- if (ss.canRestoreLastSession) {
- ss.restoreLastSession();
- }
- break;
-
- case "AboutHome:Downloads":
- window.BrowserDownloadsUI();
- break;
-
- case "AboutHome:Bookmarks":
- window.PlacesCommandHook.showPlacesOrganizer("UnfiledBookmarks");
- break;
-
- case "AboutHome:History":
- window.PlacesCommandHook.showPlacesOrganizer("History");
- break;
-
- case "AboutHome:Addons":
- window.BrowserOpenAddonsMgr();
- break;
-
- case "AboutHome:Sync":
- window.openPreferences("paneSync", { urlParams: { entrypoint: "abouthome" } });
- break;
-
- case "AboutHome:Settings":
- window.openPreferences();
- break;
-
- case "AboutHome:RequestUpdate":
- this.sendAboutHomeData(aMessage.target);
- break;
-
- case "AboutHome:MaybeShowAutoMigrationUndoNotification":
- AutoMigrate.maybeShowUndoNotification(aMessage.target);
- break;
- }
- },
-
- // Send all the chrome-privileged data needed by about:home. This
- // gets re-sent when the search engine changes.
- sendAboutHomeData: function(target) {
- let wrapper = {};
- Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
- wrapper);
- let ss = wrapper.SessionStore;
-
- ss.promiseInitialized.then(function() {
- let data = {
- showRestoreLastSession: ss.canRestoreLastSession,
- showKnowYourRights: AboutHomeUtils.showKnowYourRights
- };
-
- if (AboutHomeUtils.showKnowYourRights) {
- // Set pref to indicate we've shown the notification.
- let currentVersion = Services.prefs.getIntPref("browser.rights.version");
- Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
- }
-
- if (target && target.messageManager) {
- target.messageManager.sendAsyncMessage("AboutHome:Update", data);
- } else {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.broadcastAsyncMessage("AboutHome:Update", data);
- }
- }).then(null, function onError(x) {
- Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
- });
- },
-
-};
diff --git a/browser/modules/AboutNewTab.jsm b/browser/modules/AboutNewTab.jsm
deleted file mode 100644
index 4337c5a2d..000000000
--- a/browser/modules/AboutNewTab.jsm
+++ /dev/null
@@ -1,43 +0,0 @@
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "AboutNewTab" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
- "resource:///modules/AutoMigrate.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
- "resource://gre/modules/NewTabUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
- "resource://gre/modules/RemotePageManager.jsm");
-
-var AboutNewTab = {
-
- pageListener: null,
-
- init: function() {
- this.pageListener = new RemotePages("about:newtab");
- this.pageListener.addMessageListener("NewTab:Customize", this.customize.bind(this));
- this.pageListener.addMessageListener("NewTab:MaybeShowAutoMigrationUndoNotification",
- (msg) => AutoMigrate.maybeShowUndoNotification(msg.target.browser));
- },
-
- customize: function(message) {
- NewTabUtils.allPages.enabled = message.data.enabled;
- NewTabUtils.allPages.enhanced = message.data.enhanced;
- },
-
- uninit: function() {
- this.pageListener.destroy();
- this.pageListener = null;
- },
-};
diff --git a/browser/modules/AttributionCode.jsm b/browser/modules/AttributionCode.jsm
deleted file mode 100644
index dc42b2be4..000000000
--- a/browser/modules/AttributionCode.jsm
+++ /dev/null
@@ -1,123 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["AttributionCode"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, 'AppConstants',
- 'resource://gre/modules/AppConstants.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'OS',
- 'resource://gre/modules/osfile.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Services',
- 'resource://gre/modules/Services.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Task',
- 'resource://gre/modules/Task.jsm');
-
-const ATTR_CODE_MAX_LENGTH = 200;
-const ATTR_CODE_KEYS_REGEX = /^source|medium|campaign|content$/;
-const ATTR_CODE_VALUE_REGEX = /[a-zA-Z0-9_%\\-\\.\\(\\)]*/;
-const ATTR_CODE_FIELD_SEPARATOR = "%26"; // URL-encoded &
-const ATTR_CODE_KEY_VALUE_SEPARATOR = "%3D"; // URL-encoded =
-
-let gCachedAttrData = null;
-
-/**
- * Returns an nsIFile for the file containing the attribution data.
- */
-function getAttributionFile() {
- let file = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
- // appinfo does not exist in xpcshell, so we need defaults.
- file.append(Services.appinfo.vendor || "mozilla");
- file.append(AppConstants.MOZ_APP_NAME);
- file.append("postSigningData");
- return file;
-}
-
-/**
- * Returns an object containing a key-value pair for each piece of attribution
- * data included in the passed-in attribution code string.
- * If the string isn't a valid attribution code, returns an empty object.
- */
-function parseAttributionCode(code) {
- if (code.length > ATTR_CODE_MAX_LENGTH) {
- return {};
- }
-
- let isValid = true;
- let parsed = {};
- for (let param of code.split(ATTR_CODE_FIELD_SEPARATOR)) {
- let [key, value] = param.split(ATTR_CODE_KEY_VALUE_SEPARATOR, 2);
- if (key && ATTR_CODE_KEYS_REGEX.test(key)) {
- if (value && ATTR_CODE_VALUE_REGEX.test(value)) {
- parsed[key] = value;
- }
- } else {
- isValid = false;
- break;
- }
- }
- return isValid ? parsed : {};
-}
-
-var AttributionCode = {
- /**
- * Reads the attribution code, either from disk or a cached version.
- * Returns a promise that fulfills with an object containing the parsed
- * attribution data if the code could be read and is valid,
- * or an empty object otherwise.
- */
- getAttrDataAsync() {
- return Task.spawn(function*() {
- if (gCachedAttrData != null) {
- return gCachedAttrData;
- }
-
- let code = "";
- try {
- let bytes = yield OS.File.read(getAttributionFile().path);
- let decoder = new TextDecoder();
- code = decoder.decode(bytes);
- } catch (ex) {
- // The attribution file may already have been deleted,
- // or it may have never been installed at all;
- // failure to open or read it isn't an error.
- }
-
- gCachedAttrData = parseAttributionCode(code);
- return gCachedAttrData;
- });
- },
-
- /**
- * Deletes the attribution data file.
- * Returns a promise that resolves when the file is deleted,
- * or if the file couldn't be deleted (the promise is never rejected).
- */
- deleteFileAsync() {
- return Task.spawn(function*() {
- try {
- yield OS.File.remove(getAttributionFile().path);
- } catch (ex) {
- // The attribution file may already have been deleted,
- // or it may have never been installed at all;
- // failure to delete it isn't an error.
- }
- });
- },
-
- /**
- * Clears the cached attribution code value, if any.
- * Does nothing if called from outside of an xpcshell test.
- */
- _clearCache() {
- let env = Cc["@mozilla.org/process/environment;1"]
- .getService(Ci.nsIEnvironment);
- if (env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
- gCachedAttrData = null;
- }
- },
-};
diff --git a/browser/modules/BrowserUITelemetry.jsm b/browser/modules/BrowserUITelemetry.jsm
deleted file mode 100644
index 2b7cc8c20..000000000
--- a/browser/modules/BrowserUITelemetry.jsm
+++ /dev/null
@@ -1,888 +0,0 @@
-// 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";
-
-this.EXPORTED_SYMBOLS = ["BrowserUITelemetry"];
-
-const {interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
- "resource://gre/modules/UITelemetry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyGetter(this, "Timer", function() {
- let timer = {};
- Cu.import("resource://gre/modules/Timer.jsm", timer);
- return timer;
-});
-
-const MS_SECOND = 1000;
-const MS_MINUTE = MS_SECOND * 60;
-const MS_HOUR = MS_MINUTE * 60;
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREA_PLACEMENTS", function() {
- let result = {
- "PanelUI-contents": [
- "edit-controls",
- "zoom-controls",
- "new-window-button",
- "privatebrowsing-button",
- "save-page-button",
- "print-button",
- "history-panelmenu",
- "fullscreen-button",
- "find-button",
- "preferences-button",
- "add-ons-button",
- "sync-button",
- "developer-button",
- ],
- "nav-bar": [
- "urlbar-container",
- "search-container",
- "bookmarks-menu-button",
- "pocket-button",
- "downloads-button",
- "home-button",
- ],
- // It's true that toolbar-menubar is not visible
- // on OS X, but the XUL node is definitely present
- // in the document.
- "toolbar-menubar": [
- "menubar-items",
- ],
- "TabsToolbar": [
- "tabbrowser-tabs",
- "new-tab-button",
- "alltabs-button",
- ],
- "PersonalToolbar": [
- "personal-bookmarks",
- ],
- };
-
- let showCharacterEncoding = Services.prefs.getComplexValue(
- "browser.menu.showCharacterEncoding",
- Ci.nsIPrefLocalizedString
- ).data;
- if (showCharacterEncoding == "true") {
- result["PanelUI-contents"].push("characterencoding-button");
- }
-
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_AREAS", function() {
- return Object.keys(DEFAULT_AREA_PLACEMENTS);
-});
-
-XPCOMUtils.defineLazyGetter(this, "PALETTE_ITEMS", function() {
- let result = [
- "open-file-button",
- "developer-button",
- "feed-button",
- "email-link-button",
- "containers-panelmenu",
- ];
-
- let panelPlacements = DEFAULT_AREA_PLACEMENTS["PanelUI-contents"];
- if (panelPlacements.indexOf("characterencoding-button") == -1) {
- result.push("characterencoding-button");
- }
-
- if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
- result.push("panic-button");
- }
-
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "DEFAULT_ITEMS", function() {
- let result = [];
- for (let [, buttons] of Object.entries(DEFAULT_AREA_PLACEMENTS)) {
- result = result.concat(buttons);
- }
- return result;
-});
-
-XPCOMUtils.defineLazyGetter(this, "ALL_BUILTIN_ITEMS", function() {
- // These special cases are for click events on built-in items that are
- // contained within customizable items (like the navigation widget).
- const SPECIAL_CASES = [
- "back-button",
- "forward-button",
- "urlbar-stop-button",
- "urlbar-go-button",
- "urlbar-reload-button",
- "searchbar",
- "cut-button",
- "copy-button",
- "paste-button",
- "zoom-out-button",
- "zoom-reset-button",
- "zoom-in-button",
- "BMB_bookmarksPopup",
- "BMB_unsortedBookmarksPopup",
- "BMB_bookmarksToolbarPopup",
- "search-go-button",
- "soundplaying-icon",
- ]
- return DEFAULT_ITEMS.concat(PALETTE_ITEMS)
- .concat(SPECIAL_CASES);
-});
-
-const OTHER_MOUSEUP_MONITORED_ITEMS = [
- "PlacesChevron",
- "PlacesToolbarItems",
- "menubar-items",
-];
-
-// Items that open arrow panels will often be overlapped by
-// the panel that they're opening by the time the mouseup
-// event is fired, so for these items, we monitor mousedown.
-const MOUSEDOWN_MONITORED_ITEMS = [
- "PanelUI-menu-button",
-];
-
-// Weakly maps browser windows to objects whose keys are relative
-// timestamps for when some kind of session started. For example,
-// when a customization session started. That way, when the window
-// exits customization mode, we can determine how long the session
-// lasted.
-const WINDOW_DURATION_MAP = new WeakMap();
-
-// Default bucket name, when no other bucket is active.
-const BUCKET_DEFAULT = "__DEFAULT__";
-// Bucket prefix, for named buckets.
-const BUCKET_PREFIX = "bucket_";
-// Standard separator to use between different parts of a bucket name, such
-// as primary name and the time step string.
-const BUCKET_SEPARATOR = "|";
-
-this.BrowserUITelemetry = {
- init: function() {
- UITelemetry.addSimpleMeasureFunction("toolbars",
- this.getToolbarMeasures.bind(this));
- UITelemetry.addSimpleMeasureFunction("contextmenu",
- this.getContextMenuInfo.bind(this));
- UITelemetry.addSimpleMeasureFunction("syncstate",
- this.getSyncState.bind(this));
-
- Services.obs.addObserver(this, "sessionstore-windows-restored", false);
- Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
- Services.obs.addObserver(this, "autocomplete-did-enter-text", false);
- CustomizableUI.addListener(this);
- },
-
- observe: function(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "sessionstore-windows-restored":
- this._gatherFirstWindowMeasurements();
- break;
- case "browser-delayed-startup-finished":
- this._registerWindow(aSubject);
- break;
- case "autocomplete-did-enter-text":
- let input = aSubject.QueryInterface(Ci.nsIAutoCompleteInput);
- if (input && input.id == "urlbar" && !input.inPrivateContext &&
- input.popup.selectedIndex != -1) {
- this._logAwesomeBarSearchResult(input.textValue);
- }
- break;
- }
- },
-
- /**
- * For the _countableEvents object, constructs a chain of
- * Javascript Objects with the keys in aKeys, with the final
- * key getting the value in aEndWith. If the final key already
- * exists in the final object, its value is not set. In either
- * case, a reference to the second last object in the chain is
- * returned.
- *
- * Example - suppose I want to store:
- * _countableEvents: {
- * a: {
- * b: {
- * c: 0
- * }
- * }
- * }
- *
- * And then increment the "c" value by 1, you could call this
- * function like this:
- *
- * let example = this._ensureObjectChain([a, b, c], 0);
- * example["c"]++;
- *
- * Subsequent repetitions of these last two lines would
- * simply result in the c value being incremented again
- * and again.
- *
- * @param aKeys the Array of keys to chain Objects together with.
- * @param aEndWith the value to assign to the last key.
- * @param aRoot the root object onto which we create/get the object chain
- * designated by aKeys.
- * @returns a reference to the second last object in the chain -
- * so in our example, that'd be "b".
- */
- _ensureObjectChain: function(aKeys, aEndWith, aRoot) {
- let current = aRoot;
- let parent = null;
- aKeys.unshift(this._bucket);
- for (let [i, key] of aKeys.entries()) {
- if (!(key in current)) {
- if (i == aKeys.length - 1) {
- current[key] = aEndWith;
- } else {
- current[key] = {};
- }
- }
- parent = current;
- current = current[key];
- }
- return parent;
- },
-
- _countableEvents: {},
- _countEvent: function(aKeyArray, root=this._countableEvents) {
- let countObject = this._ensureObjectChain(aKeyArray, 0, root);
- let lastItemKey = aKeyArray[aKeyArray.length - 1];
- countObject[lastItemKey]++;
- },
-
- _countMouseUpEvent: function(aCategory, aAction, aButton) {
- const BUTTONS = ["left", "middle", "right"];
- let buttonKey = BUTTONS[aButton];
- if (buttonKey) {
- this._countEvent([aCategory, aAction, buttonKey]);
- }
- },
-
- _firstWindowMeasurements: null,
- _gatherFirstWindowMeasurements: function() {
- // We'll gather measurements as soon as the session has restored.
- // We do this here instead of waiting for UITelemetry to ask for
- // our measurements because at that point all browser windows have
- // probably been closed, since the vast majority of saved-session
- // pings are gathered during shutdown.
- let win = RecentWindow.getMostRecentBrowserWindow({
- private: false,
- allowPopups: false,
- });
-
- Services.search.init(rv => {
- // If there are no such windows (or we've just about found one
- // but it's closed already), we're out of luck. :(
- let hasWindow = win && !win.closed;
- this._firstWindowMeasurements = hasWindow ? this._getWindowMeasurements(win, rv)
- : {};
- });
- },
-
- _registerWindow: function(aWindow) {
- aWindow.addEventListener("unload", this);
- let document = aWindow.document;
-
- for (let areaID of CustomizableUI.areas) {
- let areaNode = document.getElementById(areaID);
- if (areaNode) {
- (areaNode.customizationTarget || areaNode).addEventListener("mouseup", this);
- }
- }
-
- for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.addEventListener("mouseup", this);
- }
- }
-
- for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.addEventListener("mousedown", this);
- }
- }
-
- WINDOW_DURATION_MAP.set(aWindow, {});
- },
-
- _unregisterWindow: function(aWindow) {
- aWindow.removeEventListener("unload", this);
- let document = aWindow.document;
-
- for (let areaID of CustomizableUI.areas) {
- let areaNode = document.getElementById(areaID);
- if (areaNode) {
- (areaNode.customizationTarget || areaNode).removeEventListener("mouseup", this);
- }
- }
-
- for (let itemID of OTHER_MOUSEUP_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.removeEventListener("mouseup", this);
- }
- }
-
- for (let itemID of MOUSEDOWN_MONITORED_ITEMS) {
- let item = document.getElementById(itemID);
- if (item) {
- item.removeEventListener("mousedown", this);
- }
- }
- },
-
- handleEvent: function(aEvent) {
- switch (aEvent.type) {
- case "unload":
- this._unregisterWindow(aEvent.currentTarget);
- break;
- case "mouseup":
- this._handleMouseUp(aEvent);
- break;
- case "mousedown":
- this._handleMouseDown(aEvent);
- break;
- }
- },
-
- _handleMouseUp: function(aEvent) {
- let targetID = aEvent.currentTarget.id;
-
- switch (targetID) {
- case "PlacesToolbarItems":
- this._PlacesToolbarItemsMouseUp(aEvent);
- break;
- case "PlacesChevron":
- this._PlacesChevronMouseUp(aEvent);
- break;
- case "menubar-items":
- this._menubarMouseUp(aEvent);
- break;
- default:
- this._checkForBuiltinItem(aEvent);
- }
- },
-
- _handleMouseDown: function(aEvent) {
- if (aEvent.currentTarget.id == "PanelUI-menu-button") {
- // _countMouseUpEvent expects a detail for the second argument,
- // but we don't really have any details to give. Just passing in
- // "button" is probably simpler than trying to modify
- // _countMouseUpEvent for this particular case.
- this._countMouseUpEvent("click-menu-button", "button", aEvent.button);
- }
- },
-
- _PlacesChevronMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- let result = target.id == "PlacesChevron" ? "chevron" : "overflowed-item";
- this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
- },
-
- _PlacesToolbarItemsMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- // If this isn't a bookmark-item, we don't care about it.
- if (!target.classList.contains("bookmark-item")) {
- return;
- }
-
- let result = target.hasAttribute("container") ? "container" : "item";
- this._countMouseUpEvent("click-bookmarks-bar", result, aEvent.button);
- },
-
- _menubarMouseUp: function(aEvent) {
- let target = aEvent.originalTarget;
- let tag = target.localName
- let result = (tag == "menu" || tag == "menuitem") ? tag : "other";
- this._countMouseUpEvent("click-menubar", result, aEvent.button);
- },
-
- _bookmarksMenuButtonMouseUp: function(aEvent) {
- let bookmarksWidget = CustomizableUI.getWidget("bookmarks-menu-button");
- if (bookmarksWidget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
- // In the menu panel, only the star is visible, and that opens up the
- // bookmarks subview.
- this._countMouseUpEvent("click-bookmarks-menu-button", "in-panel",
- aEvent.button);
- } else {
- let clickedItem = aEvent.originalTarget;
- // Did we click on the star, or the dropmarker? The star
- // has an anonid of "button". If we don't find that, we'll
- // assume we clicked on the dropmarker.
- let action = "menu";
- if (clickedItem.getAttribute("anonid") == "button") {
- // We clicked on the star - now we just need to record
- // whether or not we're adding a bookmark or editing an
- // existing one.
- let bookmarksMenuNode =
- bookmarksWidget.forWindow(aEvent.target.ownerGlobal).node;
- action = bookmarksMenuNode.hasAttribute("starred") ? "edit" : "add";
- }
- this._countMouseUpEvent("click-bookmarks-menu-button", action,
- aEvent.button);
- }
- },
-
- _checkForBuiltinItem: function(aEvent) {
- let item = aEvent.originalTarget;
-
- // We don't want to count clicks on the private browsing
- // button for privacy reasons. See bug 1176391.
- if (item.id == "privatebrowsing-button") {
- return;
- }
-
- // We special-case the bookmarks-menu-button, since we want to
- // monitor more than just clicks on it.
- if (item.id == "bookmarks-menu-button" ||
- getIDBasedOnFirstIDedAncestor(item) == "bookmarks-menu-button") {
- this._bookmarksMenuButtonMouseUp(aEvent);
- return;
- }
-
- // Perhaps we're seeing one of the default toolbar items
- // being clicked.
- if (ALL_BUILTIN_ITEMS.indexOf(item.id) != -1) {
- // Base case - we clicked directly on one of our built-in items,
- // and we can go ahead and register that click.
- this._countMouseUpEvent("click-builtin-item", item.id, aEvent.button);
- return;
- }
-
- // If not, we need to check if the item's anonid is in our list
- // of built-in items to check.
- if (ALL_BUILTIN_ITEMS.indexOf(item.getAttribute("anonid")) != -1) {
- this._countMouseUpEvent("click-builtin-item", item.getAttribute("anonid"), aEvent.button);
- return;
- }
-
- // If not, we need to check if one of the ancestors of the clicked
- // item is in our list of built-in items to check.
- let candidate = getIDBasedOnFirstIDedAncestor(item);
- if (ALL_BUILTIN_ITEMS.indexOf(candidate) != -1) {
- this._countMouseUpEvent("click-builtin-item", candidate, aEvent.button);
- }
- },
-
- _getWindowMeasurements: function(aWindow, searchResult) {
- let document = aWindow.document;
- let result = {};
-
- // Determine if the window is in the maximized, normal or
- // fullscreen state.
- result.sizemode = document.documentElement.getAttribute("sizemode");
-
- // Determine if the Bookmarks bar is currently visible
- let bookmarksBar = document.getElementById("PersonalToolbar");
- result.bookmarksBarEnabled = bookmarksBar && !bookmarksBar.collapsed;
-
- // Determine if the menubar is currently visible. On OS X, the menubar
- // is never shown, despite not having the collapsed attribute set.
- let menuBar = document.getElementById("toolbar-menubar");
- result.menuBarEnabled =
- menuBar && Services.appinfo.OS != "Darwin"
- && menuBar.getAttribute("autohide") != "true";
-
- // Determine if the titlebar is currently visible.
- result.titleBarEnabled = !Services.prefs.getBoolPref("browser.tabs.drawInTitlebar");
-
- // Examine all customizable areas and see what default items
- // are present and missing.
- let defaultKept = [];
- let defaultMoved = [];
- let nondefaultAdded = [];
-
- for (let areaID of CustomizableUI.areas) {
- let items = CustomizableUI.getWidgetIdsInArea(areaID);
- for (let item of items) {
- // Is this a default item?
- if (DEFAULT_ITEMS.indexOf(item) != -1) {
- // Ok, it's a default item - but is it in its default
- // toolbar? We use Array.isArray instead of checking for
- // toolbarID in DEFAULT_AREA_PLACEMENTS because an add-on might
- // be clever and give itself the id of "toString" or something.
- if (Array.isArray(DEFAULT_AREA_PLACEMENTS[areaID]) &&
- DEFAULT_AREA_PLACEMENTS[areaID].indexOf(item) != -1) {
- // The item is in its default toolbar
- defaultKept.push(item);
- } else {
- defaultMoved.push(item);
- }
- } else if (PALETTE_ITEMS.indexOf(item) != -1) {
- // It's a palette item that's been moved into a toolbar
- nondefaultAdded.push(item);
- }
- // else, it's provided by an add-on, and we won't record it.
- }
- }
-
- // Now go through the items in the palette to see what default
- // items are in there.
- let paletteItems =
- CustomizableUI.getUnusedWidgets(aWindow.gNavToolbox.palette);
- let defaultRemoved = [];
- for (let item of paletteItems) {
- if (DEFAULT_ITEMS.indexOf(item.id) != -1) {
- defaultRemoved.push(item.id);
- }
- }
-
- result.defaultKept = defaultKept;
- result.defaultMoved = defaultMoved;
- result.nondefaultAdded = nondefaultAdded;
- result.defaultRemoved = defaultRemoved;
-
- // Next, determine how many add-on provided toolbars exist.
- let addonToolbars = 0;
- let toolbars = document.querySelectorAll("toolbar[customizable=true]");
- for (let toolbar of toolbars) {
- if (DEFAULT_AREAS.indexOf(toolbar.id) == -1) {
- addonToolbars++;
- }
- }
- result.addonToolbars = addonToolbars;
-
- // Find out how many open tabs we have in each window
- let winEnumerator = Services.wm.getEnumerator("navigator:browser");
- let visibleTabs = [];
- let hiddenTabs = [];
- while (winEnumerator.hasMoreElements()) {
- let someWin = winEnumerator.getNext();
- if (someWin.gBrowser) {
- let visibleTabsNum = someWin.gBrowser.visibleTabs.length;
- visibleTabs.push(visibleTabsNum);
- hiddenTabs.push(someWin.gBrowser.tabs.length - visibleTabsNum);
- }
- }
- result.visibleTabs = visibleTabs;
- result.hiddenTabs = hiddenTabs;
-
- if (Components.isSuccessCode(searchResult)) {
- result.currentSearchEngine = Services.search.currentEngine.name;
- }
-
- return result;
- },
-
- getToolbarMeasures: function() {
- let result = this._firstWindowMeasurements || {};
- result.countableEvents = this._countableEvents;
- result.durations = this._durations;
- return result;
- },
-
- getSyncState: function() {
- let result = {};
- for (let sub of ["desktop", "mobile"]) {
- let count = 0;
- try {
- count = Services.prefs.getIntPref("services.sync.clients.devices." + sub);
- } catch (ex) {}
- result[sub] = count;
- }
- return result;
- },
-
- countCustomizationEvent: function(aEventType) {
- this._countEvent(["customize", aEventType]);
- },
-
- countSearchEvent: function(source, query, selection) {
- this._countEvent(["search", source]);
- if ((/^[a-zA-Z]+:[^\/\\]/).test(query)) {
- this._countEvent(["search", "urlbar-keyword"]);
- }
- if (selection) {
- this._countEvent(["search", "selection", source, selection.index, selection.kind]);
- }
- },
-
- countOneoffSearchEvent: function(id, type, where) {
- this._countEvent(["search-oneoff", id, type, where]);
- },
-
- countSearchSettingsEvent: function(source) {
- this._countEvent(["click-builtin-item", source, "search-settings"]);
- },
-
- countPanicEvent: function(timeId) {
- this._countEvent(["forget-button", timeId]);
- },
-
- countTabMutingEvent: function(action, reason) {
- this._countEvent(["tab-audio-control", action, reason || "no reason given"]);
- },
-
- countSyncedTabEvent: function(what, where) {
- // "what" will be, eg, "open"
- // "where" will be "toolbarbutton-subview" or "sidebar"
- this._countEvent(["synced-tabs", what, where]);
- },
-
- countSidebarEvent: function(sidebarID, action) {
- // sidebarID is the ID of the sidebar (duh!)
- // action will be "hide" or "show"
- this._countEvent(["sidebar", sidebarID, action]);
- },
-
- _logAwesomeBarSearchResult: function (url) {
- let spec = Services.search.parseSubmissionURL(url);
- if (spec.engine) {
- let matchedEngine = "default";
- if (spec.engine.name !== Services.search.currentEngine.name) {
- matchedEngine = "other";
- }
- this.countSearchEvent("autocomplete-" + matchedEngine);
- }
- },
-
- _durations: {
- customization: [],
- },
-
- onCustomizeStart: function(aWindow) {
- this._countEvent(["customize", "start"]);
- let durationMap = WINDOW_DURATION_MAP.get(aWindow);
- if (!durationMap) {
- durationMap = {};
- WINDOW_DURATION_MAP.set(aWindow, durationMap);
- }
-
- durationMap.customization = {
- start: aWindow.performance.now(),
- bucket: this._bucket,
- };
- },
-
- onCustomizeEnd: function(aWindow) {
- let durationMap = WINDOW_DURATION_MAP.get(aWindow);
- if (durationMap && "customization" in durationMap) {
- let duration = aWindow.performance.now() - durationMap.customization.start;
- this._durations.customization.push({
- duration: duration,
- bucket: durationMap.customization.bucket,
- });
- delete durationMap.customization;
- }
- },
-
- _contextMenuItemWhitelist: new Set([
- "close-without-interaction", // for closing the menu without clicking it.
- "custom-page-item", // The ID we use for page-provided items
- "unknown", // The bucket for stuff with no id.
- // Everything we know of so far (which will exclude add-on items):
- "navigation", "back", "forward", "reload", "stop", "bookmarkpage",
- "spell-no-suggestions", "spell-add-to-dictionary",
- "spell-undo-add-to-dictionary", "openlinkincurrent", "openlinkintab",
- "openlink",
- // "openlinkprivate" intentionally omitted for privacy reasons. See bug 1176391.
- "bookmarklink", "savelink",
- "marklinkMenu", "copyemail", "copylink", "media-play", "media-pause",
- "media-mute", "media-unmute", "media-playbackrate",
- "media-playbackrate-050x", "media-playbackrate-100x",
- "media-playbackrate-125x", "media-playbackrate-150x", "media-playbackrate-200x",
- "media-showcontrols", "media-hidecontrols",
- "video-fullscreen", "leave-dom-fullscreen",
- "reloadimage", "viewimage", "viewvideo", "copyimage-contents", "copyimage",
- "copyvideourl", "copyaudiourl", "saveimage", "sendimage",
- "setDesktopBackground", "viewimageinfo", "viewimagedesc", "savevideo",
- "saveaudio", "video-saveimage", "sendvideo", "sendaudio",
- "ctp-play", "ctp-hide", "savepage", "pocket", "markpageMenu",
- "viewbgimage", "undo", "cut", "copy", "paste", "delete", "selectall",
- "keywordfield", "searchselect", "frame", "showonlythisframe",
- "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
- "printframe", "viewframesource", "viewframeinfo",
- "viewpartialsource-selection", "viewpartialsource-mathml",
- "viewsource", "viewinfo", "spell-check-enabled",
- "spell-add-dictionaries-main", "spell-dictionaries",
- "spell-dictionaries-menu", "spell-add-dictionaries",
- "bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
- "media-eme-learn-more"
- ]),
-
- _contextMenuInteractions: {},
-
- registerContextMenuInteraction: function(keys, itemID) {
- if (itemID) {
- if (itemID == "openlinkprivate") {
- // Don't record anything, not even an other-item count
- // if the user chose to open in a private window. See
- // bug 1176391.
- return;
- }
-
- if (!this._contextMenuItemWhitelist.has(itemID)) {
- itemID = "other-item";
- }
- keys.push(itemID);
- }
-
- this._countEvent(keys, this._contextMenuInteractions);
- },
-
- getContextMenuInfo: function() {
- return this._contextMenuInteractions;
- },
-
- _bucket: BUCKET_DEFAULT,
- _bucketTimer: null,
-
- /**
- * Default bucket name, when no other bucket is active.
- */
- get BUCKET_DEFAULT() {
- return BUCKET_DEFAULT;
- },
-
- /**
- * Bucket prefix, for named buckets.
- */
- get BUCKET_PREFIX() {
- return BUCKET_PREFIX;
- },
-
- /**
- * Standard separator to use between different parts of a bucket name, such
- * as primary name and the time step string.
- */
- get BUCKET_SEPARATOR() {
- return BUCKET_SEPARATOR;
- },
-
- get currentBucket() {
- return this._bucket;
- },
-
- /**
- * Sets a named bucket for all countable events and select durections to be
- * put into.
- *
- * @param aName Name of bucket, or null for default bucket name (__DEFAULT__)
- */
- setBucket: function(aName) {
- if (this._bucketTimer) {
- Timer.clearTimeout(this._bucketTimer);
- this._bucketTimer = null;
- }
-
- if (aName)
- this._bucket = BUCKET_PREFIX + aName;
- else
- this._bucket = BUCKET_DEFAULT;
- },
-
- /**
- * Sets a bucket that expires at the rate of a given series of time steps.
- * Once the bucket expires, the current bucket will automatically revert to
- * the default bucket. While the bucket is expiring, it's name is postfixed
- * by '|' followed by a short string representation of the time step it's
- * currently in.
- * If any other bucket (expiring or normal) is set while an expiring bucket is
- * still expiring, the old expiring bucket stops expiring and the new bucket
- * immediately takes over.
- *
- * @param aName Name of bucket.
- * @param aTimeSteps An array of times in milliseconds to count up to before
- * reverting back to the default bucket. The array of times
- * is expected to be pre-sorted in ascending order.
- * For example, given a bucket name of 'bucket', the times:
- * [60000, 300000, 600000]
- * will result in the following buckets:
- * * bucket|1m - for the first 1 minute
- * * bucket|5m - for the following 4 minutes
- * (until 5 minutes after the start)
- * * bucket|10m - for the following 5 minutes
- * (until 10 minutes after the start)
- * * __DEFAULT__ - until a new bucket is set
- * @param aTimeOffset Time offset, in milliseconds, from which to start
- * counting. For example, if the first time step is 1000ms,
- * and the time offset is 300ms, then the next time step
- * will become active after 700ms. This affects all
- * following time steps also, meaning they will also all be
- * timed as though they started expiring 300ms before
- * setExpiringBucket was called.
- */
- setExpiringBucket: function(aName, aTimeSteps, aTimeOffset = 0) {
- if (aTimeSteps.length === 0) {
- this.setBucket(null);
- return;
- }
-
- if (this._bucketTimer) {
- Timer.clearTimeout(this._bucketTimer);
- this._bucketTimer = null;
- }
-
- // Make a copy of the time steps array, so we can safely modify it without
- // modifying the original array that external code has passed to us.
- let steps = [...aTimeSteps];
- let msec = steps.shift();
- let postfix = this._toTimeStr(msec);
- this.setBucket(aName + BUCKET_SEPARATOR + postfix);
-
- this._bucketTimer = Timer.setTimeout(() => {
- this._bucketTimer = null;
- this.setExpiringBucket(aName, steps, aTimeOffset + msec);
- }, msec - aTimeOffset);
- },
-
- /**
- * Formats a time interval, in milliseconds, to a minimal non-localized string
- * representation. Format is: 'h' for hours, 'm' for minutes, 's' for seconds,
- * 'ms' for milliseconds.
- * Examples:
- * 65 => 65ms
- * 1000 => 1s
- * 60000 => 1m
- * 61000 => 1m01s
- *
- * @param aTimeMS Time in milliseconds
- *
- * @return Minimal string representation.
- */
- _toTimeStr: function(aTimeMS) {
- let timeStr = "";
-
- function reduce(aUnitLength, aSymbol) {
- if (aTimeMS >= aUnitLength) {
- let units = Math.floor(aTimeMS / aUnitLength);
- aTimeMS = aTimeMS - (units * aUnitLength)
- timeStr += units + aSymbol;
- }
- }
-
- reduce(MS_HOUR, "h");
- reduce(MS_MINUTE, "m");
- reduce(MS_SECOND, "s");
- reduce(1, "ms");
-
- return timeStr;
- },
-};
-
-/**
- * Returns the id of the first ancestor of aNode that has an id. If aNode
- * has no parent, or no ancestor has an id, returns null.
- *
- * @param aNode the node to find the first ID'd ancestor of
- */
-function getIDBasedOnFirstIDedAncestor(aNode) {
- while (!aNode.id) {
- aNode = aNode.parentNode;
- if (!aNode) {
- return null;
- }
- }
-
- return aNode.id;
-}
diff --git a/browser/modules/BrowserUsageTelemetry.jsm b/browser/modules/BrowserUsageTelemetry.jsm
deleted file mode 100644
index 39012d2ab..000000000
--- a/browser/modules/BrowserUsageTelemetry.jsm
+++ /dev/null
@@ -1,468 +0,0 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["BrowserUsageTelemetry"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-// The upper bound for the count of the visited unique domain names.
-const MAX_UNIQUE_VISITED_DOMAINS = 100;
-
-// Observed topic names.
-const WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
-const TAB_RESTORING_TOPIC = "SSTabRestoring";
-const TELEMETRY_SUBSESSIONSPLIT_TOPIC = "internal-telemetry-after-subsession-split";
-const DOMWINDOW_OPENED_TOPIC = "domwindowopened";
-
-// Probe names.
-const MAX_TAB_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_tab_count";
-const MAX_WINDOW_COUNT_SCALAR_NAME = "browser.engagement.max_concurrent_window_count";
-const TAB_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.tab_open_event_count";
-const WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME = "browser.engagement.window_open_event_count";
-const UNIQUE_DOMAINS_COUNT_SCALAR_NAME = "browser.engagement.unique_domains_count";
-const TOTAL_URI_COUNT_SCALAR_NAME = "browser.engagement.total_uri_count";
-const UNFILTERED_URI_COUNT_SCALAR_NAME = "browser.engagement.unfiltered_uri_count";
-
-// A list of known search origins.
-const KNOWN_SEARCH_SOURCES = [
- "abouthome",
- "contextmenu",
- "newtab",
- "searchbar",
- "urlbar",
-];
-
-const KNOWN_ONEOFF_SOURCES = [
- "oneoff-urlbar",
- "oneoff-searchbar",
- "unknown", // Edge case: this is the searchbar (see bug 1195733 comment 7).
-];
-
-function getOpenTabsAndWinsCounts() {
- let tabCount = 0;
- let winCount = 0;
-
- let browserEnum = Services.wm.getEnumerator("navigator:browser");
- while (browserEnum.hasMoreElements()) {
- let win = browserEnum.getNext();
- winCount++;
- tabCount += win.gBrowser.tabs.length;
- }
-
- return { tabCount, winCount };
-}
-
-function getSearchEngineId(engine) {
- if (engine) {
- if (engine.identifier) {
- return engine.identifier;
- }
- // Due to bug 1222070, we can't directly check Services.telemetry.canRecordExtended
- // here.
- const extendedTelemetry = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
- if (engine.name && extendedTelemetry) {
- // If it's a custom search engine only report the engine name
- // if extended Telemetry is enabled.
- return "other-" + engine.name;
- }
- }
- return "other";
-}
-
-let URICountListener = {
- // A set containing the visited domains, see bug 1271310.
- _domainSet: new Set(),
- // A map to keep track of the URIs loaded from the restored tabs.
- _restoredURIsMap: new WeakMap(),
-
- isHttpURI(uri) {
- // Only consider http(s) schemas.
- return uri.schemeIs("http") || uri.schemeIs("https");
- },
-
- addRestoredURI(browser, uri) {
- if (!this.isHttpURI(uri)) {
- return;
- }
-
- this._restoredURIsMap.set(browser, uri.spec);
- },
-
- onLocationChange(browser, webProgress, request, uri, flags) {
- // Don't count this URI if it's an error page.
- if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
- return;
- }
-
- // We only care about top level loads.
- if (!webProgress.isTopLevel) {
- return;
- }
-
- // The SessionStore sets the URI of a tab first, firing onLocationChange the
- // first time, then manages content loading using its scheduler. Once content
- // loads, we will hit onLocationChange again.
- // We can catch the first case by checking for null requests: be advised that
- // this can also happen when navigating page fragments, so account for it.
- if (!request &&
- !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
- return;
- }
-
- // Track URI loads, even if they're not http(s).
- let uriSpec = null;
- try {
- uriSpec = uri.spec;
- } catch (e) {
- // If we have troubles parsing the spec, still count this as
- // an unfiltered URI.
- Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
- return;
- }
-
-
- // Don't count about:blank and similar pages, as they would artificially
- // inflate the counts.
- if (browser.ownerDocument.defaultView.gInitialPages.includes(uriSpec)) {
- return;
- }
-
- // If the URI we're loading is in the _restoredURIsMap, then it comes from a
- // restored tab. If so, let's skip it and remove it from the map as we want to
- // count page refreshes.
- if (this._restoredURIsMap.get(browser) === uriSpec) {
- this._restoredURIsMap.delete(browser);
- return;
- }
-
- // The URI wasn't from a restored tab. Count it among the unfiltered URIs.
- // If this is an http(s) URI, this also gets counted by the "total_uri_count"
- // probe.
- Services.telemetry.scalarAdd(UNFILTERED_URI_COUNT_SCALAR_NAME, 1);
-
- if (!this.isHttpURI(uri)) {
- return;
- }
-
- // Update the URI counts.
- Services.telemetry.scalarAdd(TOTAL_URI_COUNT_SCALAR_NAME, 1);
-
- // We only want to count the unique domains up to MAX_UNIQUE_VISITED_DOMAINS.
- if (this._domainSet.size == MAX_UNIQUE_VISITED_DOMAINS) {
- return;
- }
-
- // Unique domains should be aggregated by (eTLD + 1): x.test.com and y.test.com
- // are counted once as test.com.
- try {
- // Even if only considering http(s) URIs, |getBaseDomain| could still throw
- // due to the URI containing invalid characters or the domain actually being
- // an ipv4 or ipv6 address.
- this._domainSet.add(Services.eTLD.getBaseDomain(uri));
- } catch (e) {
- return;
- }
-
- Services.telemetry.scalarSet(UNIQUE_DOMAINS_COUNT_SCALAR_NAME, this._domainSet.size);
- },
-
- /**
- * Reset the counts. This should be called when breaking a session in Telemetry.
- */
- reset() {
- this._domainSet.clear();
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
- Ci.nsISupportsWeakReference]),
-};
-
-let BrowserUsageTelemetry = {
- init() {
- Services.obs.addObserver(this, WINDOWS_RESTORED_TOPIC, false);
- },
-
- /**
- * Handle subsession splits in the parent process.
- */
- afterSubsessionSplit() {
- // Scalars just got cleared due to a subsession split. We need to set the maximum
- // concurrent tab and window counts so that they reflect the correct value for the
- // new subsession.
- const counts = getOpenTabsAndWinsCounts();
- Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
- Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
-
- // Reset the URI counter.
- URICountListener.reset();
- },
-
- uninit() {
- Services.obs.removeObserver(this, DOMWINDOW_OPENED_TOPIC, false);
- Services.obs.removeObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
- Services.obs.removeObserver(this, WINDOWS_RESTORED_TOPIC, false);
- },
-
- observe(subject, topic, data) {
- switch (topic) {
- case WINDOWS_RESTORED_TOPIC:
- this._setupAfterRestore();
- break;
- case DOMWINDOW_OPENED_TOPIC:
- this._onWindowOpen(subject);
- break;
- case TELEMETRY_SUBSESSIONSPLIT_TOPIC:
- this.afterSubsessionSplit();
- break;
- }
- },
-
- handleEvent(event) {
- switch (event.type) {
- case "TabOpen":
- this._onTabOpen();
- break;
- case "unload":
- this._unregisterWindow(event.target);
- break;
- case TAB_RESTORING_TOPIC:
- // We're restoring a new tab from a previous or crashed session.
- // We don't want to track the URIs from these tabs, so let
- // |URICountListener| know about them.
- let browser = event.target.linkedBrowser;
- URICountListener.addRestoredURI(browser, browser.currentURI);
- break;
- }
- },
-
- /**
- * The main entry point for recording search related Telemetry. This includes
- * search counts and engagement measurements.
- *
- * Telemetry records only search counts per engine and action origin, but
- * nothing pertaining to the search contents themselves.
- *
- * @param {nsISearchEngine} engine
- * The engine handling the search.
- * @param {String} source
- * Where the search originated from. See KNOWN_SEARCH_SOURCES for allowed
- * values.
- * @param {Object} [details] Options object.
- * @param {Boolean} [details.isOneOff=false]
- * true if this event was generated by a one-off search.
- * @param {Boolean} [details.isSuggestion=false]
- * true if this event was generated by a suggested search.
- * @param {Boolean} [details.isAlias=false]
- * true if this event was generated by a search using an alias.
- * @param {Object} [details.type=null]
- * The object describing the event that triggered the search.
- * @throws if source is not in the known sources list.
- */
- recordSearch(engine, source, details={}) {
- const isOneOff = !!details.isOneOff;
- const countId = getSearchEngineId(engine) + "." + source;
-
- if (isOneOff) {
- if (!KNOWN_ONEOFF_SOURCES.includes(source)) {
- // Silently drop the error if this bogus call
- // came from 'urlbar' or 'searchbar'. They're
- // calling |recordSearch| twice from two different
- // code paths because they want to record the search
- // in SEARCH_COUNTS.
- if (['urlbar', 'searchbar'].includes(source)) {
- Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").add(countId);
- return;
- }
- throw new Error("Unknown source for one-off search: " + source);
- }
- } else {
- if (!KNOWN_SEARCH_SOURCES.includes(source)) {
- throw new Error("Unknown source for search: " + source);
- }
- Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").add(countId);
- }
-
- // Dispatch the search signal to other handlers.
- this._handleSearchAction(engine, source, details);
- },
-
- _recordSearch(engine, source, action = null) {
- let scalarKey = action ? "search_" + action : "search";
- Services.telemetry.keyedScalarAdd("browser.engagement.navigation." + source,
- scalarKey, 1);
- Services.telemetry.recordEvent("navigation", "search", source, action,
- { engine: getSearchEngineId(engine) });
- },
-
- _handleSearchAction(engine, source, details) {
- switch (source) {
- case "urlbar":
- case "oneoff-urlbar":
- case "searchbar":
- case "oneoff-searchbar":
- case "unknown": // Edge case: this is the searchbar (see bug 1195733 comment 7).
- this._handleSearchAndUrlbar(engine, source, details);
- break;
- case "abouthome":
- this._recordSearch(engine, "about_home", "enter");
- break;
- case "newtab":
- this._recordSearch(engine, "about_newtab", "enter");
- break;
- case "contextmenu":
- this._recordSearch(engine, "contextmenu");
- break;
- }
- },
-
- /**
- * This function handles the "urlbar", "urlbar-oneoff", "searchbar" and
- * "searchbar-oneoff" sources.
- */
- _handleSearchAndUrlbar(engine, source, details) {
- // We want "urlbar" and "urlbar-oneoff" (and similar cases) to go in the same
- // scalar, but in a different key.
-
- // When using one-offs in the searchbar we get an "unknown" source. See bug
- // 1195733 comment 7 for the context. Fix-up the label here.
- const sourceName =
- (source === "unknown") ? "searchbar" : source.replace("oneoff-", "");
-
- const isOneOff = !!details.isOneOff;
- if (isOneOff) {
- // We will receive a signal from the "urlbar"/"searchbar" even when the
- // search came from "oneoff-urlbar". That's because both signals
- // are propagated from search.xml. Skip it if that's the case.
- // Moreover, we skip the "unknown" source that comes from the searchbar
- // when performing searches from the default search engine. See bug 1195733
- // comment 7 for context.
- if (["urlbar", "searchbar", "unknown"].includes(source)) {
- return;
- }
-
- // If that's a legit one-off search signal, record it using the relative key.
- this._recordSearch(engine, sourceName, "oneoff");
- return;
- }
-
- // The search was not a one-off. It was a search with the default search engine.
- if (details.isSuggestion) {
- // It came from a suggested search, so count it as such.
- this._recordSearch(engine, sourceName, "suggestion");
- return;
- } else if (details.isAlias) {
- // This one came from a search that used an alias.
- this._recordSearch(engine, sourceName, "alias");
- return;
- }
-
- // The search signal was generated by typing something and pressing enter.
- this._recordSearch(engine, sourceName, "enter");
- },
-
- /**
- * This gets called shortly after the SessionStore has finished restoring
- * windows and tabs. It counts the open tabs and adds listeners to all the
- * windows.
- */
- _setupAfterRestore() {
- // Make sure to catch new chrome windows and subsession splits.
- Services.obs.addObserver(this, DOMWINDOW_OPENED_TOPIC, false);
- Services.obs.addObserver(this, TELEMETRY_SUBSESSIONSPLIT_TOPIC, false);
-
- // Attach the tabopen handlers to the existing Windows.
- let browserEnum = Services.wm.getEnumerator("navigator:browser");
- while (browserEnum.hasMoreElements()) {
- this._registerWindow(browserEnum.getNext());
- }
-
- // Get the initial tab and windows max counts.
- const counts = getOpenTabsAndWinsCounts();
- Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, counts.tabCount);
- Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
- },
-
- /**
- * Adds listeners to a single chrome window.
- */
- _registerWindow(win) {
- win.addEventListener("unload", this);
- win.addEventListener("TabOpen", this, true);
-
- // Don't include URI and domain counts when in private mode.
- if (PrivateBrowsingUtils.isWindowPrivate(win)) {
- return;
- }
- win.gBrowser.tabContainer.addEventListener(TAB_RESTORING_TOPIC, this);
- win.gBrowser.addTabsProgressListener(URICountListener);
- },
-
- /**
- * Removes listeners from a single chrome window.
- */
- _unregisterWindow(win) {
- win.removeEventListener("unload", this);
- win.removeEventListener("TabOpen", this, true);
-
- // Don't include URI and domain counts when in private mode.
- if (PrivateBrowsingUtils.isWindowPrivate(win.defaultView)) {
- return;
- }
- win.defaultView.gBrowser.tabContainer.removeEventListener(TAB_RESTORING_TOPIC, this);
- win.defaultView.gBrowser.removeTabsProgressListener(URICountListener);
- },
-
- /**
- * Updates the tab counts.
- * @param {Number} [newTabCount=0] The count of the opened tabs across all windows. This
- * is computed manually if not provided.
- */
- _onTabOpen(tabCount = 0) {
- // Use the provided tab count if available. Otherwise, go on and compute it.
- tabCount = tabCount || getOpenTabsAndWinsCounts().tabCount;
- // Update the "tab opened" count and its maximum.
- Services.telemetry.scalarAdd(TAB_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
- Services.telemetry.scalarSetMaximum(MAX_TAB_COUNT_SCALAR_NAME, tabCount);
- },
-
- /**
- * Tracks the window count and registers the listeners for the tab count.
- * @param{Object} win The window object.
- */
- _onWindowOpen(win) {
- // Make sure to have a |nsIDOMWindow|.
- if (!(win instanceof Ci.nsIDOMWindow)) {
- return;
- }
-
- let onLoad = () => {
- win.removeEventListener("load", onLoad, false);
-
- // Ignore non browser windows.
- if (win.document.documentElement.getAttribute("windowtype") != "navigator:browser") {
- return;
- }
-
- this._registerWindow(win);
- // Track the window open event and check the maximum.
- const counts = getOpenTabsAndWinsCounts();
- Services.telemetry.scalarAdd(WINDOW_OPEN_EVENT_COUNT_SCALAR_NAME, 1);
- Services.telemetry.scalarSetMaximum(MAX_WINDOW_COUNT_SCALAR_NAME, counts.winCount);
-
- // We won't receive the "TabOpen" event for the first tab within a new window.
- // Account for that.
- this._onTabOpen(counts.tabCount);
- };
- win.addEventListener("load", onLoad, false);
- },
-};
diff --git a/browser/modules/CastingApps.jsm b/browser/modules/CastingApps.jsm
deleted file mode 100644
index 6f32753e8..000000000
--- a/browser/modules/CastingApps.jsm
+++ /dev/null
@@ -1,164 +0,0 @@
-// -*- Mode: js; 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";
-this.EXPORTED_SYMBOLS = ["CastingApps"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/SimpleServiceDiscovery.jsm");
-
-
-var CastingApps = {
- _sendEventToVideo: function (element, data) {
- let event = element.ownerDocument.createEvent("CustomEvent");
- event.initCustomEvent("media-videoCasting", false, true, JSON.stringify(data));
- element.dispatchEvent(event);
- },
-
- makeURI: function (url, charset, baseURI) {
- return Services.io.newURI(url, charset, baseURI);
- },
-
- getVideo: function (element) {
- if (!element) {
- return null;
- }
-
- let extensions = SimpleServiceDiscovery.getSupportedExtensions();
- let types = SimpleServiceDiscovery.getSupportedMimeTypes();
-
- // Grab the poster attribute from the <video>
- let posterURL = element.poster;
-
- // First, look to see if the <video> has a src attribute
- let sourceURL = element.src;
-
- // If empty, try the currentSrc
- if (!sourceURL) {
- sourceURL = element.currentSrc;
- }
-
- if (sourceURL) {
- // Use the file extension to guess the mime type
- let sourceURI = this.makeURI(sourceURL, null, this.makeURI(element.baseURI));
- if (this.allowableExtension(sourceURI, extensions)) {
- return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI};
- }
- }
-
- // Next, look to see if there is a <source> child element that meets
- // our needs
- let sourceNodes = element.getElementsByTagName("source");
- for (let sourceNode of sourceNodes) {
- let sourceURI = this.makeURI(sourceNode.src, null, this.makeURI(sourceNode.baseURI));
-
- // Using the type attribute is our ideal way to guess the mime type. Otherwise,
- // fallback to using the file extension to guess the mime type
- if (this.allowableMimeType(sourceNode.type, types) || this.allowableExtension(sourceURI, extensions)) {
- return { element: element, source: sourceURI.spec, poster: posterURL, sourceURI: sourceURI, type: sourceNode.type };
- }
- }
-
- return null;
- },
-
- sendVideoToService: function (videoElement, service) {
- if (!service)
- return;
-
- let video = this.getVideo(videoElement);
- if (!video) {
- return;
- }
-
- // Make sure we have a player app for the given service
- let app = SimpleServiceDiscovery.findAppForService(service);
- if (!app)
- return;
-
- video.title = videoElement.ownerGlobal.top.document.title;
- if (video.element) {
- // If the video is currently playing on the device, pause it
- if (!video.element.paused) {
- video.element.pause();
- }
- }
-
- app.stop(() => {
- app.start(started => {
- if (!started) {
- Cu.reportError("CastingApps: Unable to start app");
- return;
- }
-
- app.remoteMedia(remoteMedia => {
- if (!remoteMedia) {
- Cu.reportError("CastingApps: Failed to create remotemedia");
- return;
- }
-
- this.session = {
- service: service,
- app: app,
- remoteMedia: remoteMedia,
- data: {
- title: video.title,
- source: video.source,
- poster: video.poster
- },
- videoRef: Cu.getWeakReference(video.element)
- };
- }, this);
- });
- });
- },
-
- getServicesForVideo: function (videoElement) {
- let video = this.getVideo(videoElement);
- if (!video) {
- return {};
- }
-
- let filteredServices = SimpleServiceDiscovery.services.filter(service => {
- return this.allowableExtension(video.sourceURI, service.extensions) ||
- this.allowableMimeType(video.type, service.types);
- });
-
- return filteredServices;
- },
-
- getServicesForMirroring: function () {
- return SimpleServiceDiscovery.services.filter(service => service.mirror);
- },
-
- // RemoteMedia callback API methods
- onRemoteMediaStart: function (remoteMedia) {
- if (!this.session) {
- return;
- }
-
- remoteMedia.load(this.session.data);
-
- let video = this.session.videoRef.get();
- if (video) {
- this._sendEventToVideo(video, { active: true });
- }
- },
-
- onRemoteMediaStop: function (remoteMedia) {
- },
-
- onRemoteMediaStatus: function (remoteMedia) {
- },
-
- allowableExtension: function (uri, extensions) {
- return (uri instanceof Ci.nsIURL) && extensions.indexOf(uri.fileExtension) != -1;
- },
-
- allowableMimeType: function (type, types) {
- return types.indexOf(type) != -1;
- }
-};
diff --git a/browser/modules/ContentClick.jsm b/browser/modules/ContentClick.jsm
deleted file mode 100644
index 40101d5d3..000000000
--- a/browser/modules/ContentClick.jsm
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "ContentClick" ];
-
-Cu.import("resource:///modules/PlacesUIUtils.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-var ContentClick = {
- init: function() {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("Content:Click", this);
- },
-
- receiveMessage: function (message) {
- switch (message.name) {
- case "Content:Click":
- this.contentAreaClick(message.json, message.target)
- break;
- }
- },
-
- contentAreaClick: function (json, browser) {
- // This is heavily based on contentAreaClick from browser.js (Bug 903016)
- // The json is set up in a way to look like an Event.
- let window = browser.ownerGlobal;
-
- if (!json.href) {
- // Might be middle mouse navigation.
- if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
- !Services.prefs.getBoolPref("general.autoScroll")) {
- window.middleMousePaste(json);
- }
- return;
- }
-
- if (json.bookmark) {
- // This is the Opera convention for a special link that, when clicked,
- // allows to add a sidebar panel. The link's title attribute contains
- // the title that should be used for the sidebar panel.
- PlacesUIUtils.showBookmarkDialog({ action: "add"
- , type: "bookmark"
- , uri: Services.io.newURI(json.href, null, null)
- , title: json.title
- , loadBookmarkInSidebar: true
- , hiddenRows: [ "description"
- , "location"
- , "keyword" ]
- }, window);
- return;
- }
-
- // Note: We don't need the sidebar code here.
-
- // Mark the page as a user followed link. This is done so that history can
- // distinguish automatic embed visits from user activated ones. For example
- // pages loaded in frames are embed visits and lost with the session, while
- // visits across frames should be preserved.
- try {
- if (!PrivateBrowsingUtils.isWindowPrivate(window))
- PlacesUIUtils.markPageAsFollowedLink(json.href);
- } catch (ex) { /* Skip invalid URIs. */ }
-
- // This part is based on handleLinkClick.
- var where = window.whereToOpenLink(json);
- if (where == "current")
- return;
-
- // Todo(903022): code for where == save
-
- let params = {
- charset: browser.characterSet,
- referrerURI: browser.documentURI,
- referrerPolicy: json.referrerPolicy,
- noReferrer: json.noReferrer,
- allowMixedContent: json.allowMixedContent,
- isContentWindowPrivate: json.isContentWindowPrivate,
- originPrincipal: json.originPrincipal,
- triggeringPrincipal: json.triggeringPrincipal,
- };
-
- // The new tab/window must use the same userContextId.
- if (json.originAttributes.userContextId) {
- params.userContextId = json.originAttributes.userContextId;
- }
-
- window.openLinkIn(json.href, where, params);
- }
-};
diff --git a/browser/modules/ContentCrashHandlers.jsm b/browser/modules/ContentCrashHandlers.jsm
deleted file mode 100644
index 488cc4f26..000000000
--- a/browser/modules/ContentCrashHandlers.jsm
+++ /dev/null
@@ -1,922 +0,0 @@
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "TabCrashHandler",
- "PluginCrashReporter",
- "UnsubmittedCrashHandler" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "CrashSubmit",
- "resource://gre/modules/CrashSubmit.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
- "resource://gre/modules/RemotePageManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
- "resource:///modules/sessionstore/SessionStore.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
- "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
- "resource://gre/modules/PluralForm.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
- const url = "chrome://browser/locale/browser.properties";
- return Services.strings.createBundle(url);
-});
-
-// We don't process crash reports older than 28 days, so don't bother
-// submitting them
-const PENDING_CRASH_REPORT_DAYS = 28;
-const DAY = 24 * 60 * 60 * 1000; // milliseconds
-const DAYS_TO_SUPPRESS = 30;
-const MAX_UNSEEN_CRASHED_CHILD_IDS = 20;
-
-this.TabCrashHandler = {
- _crashedTabCount: 0,
- childMap: new Map(),
- browserMap: new WeakMap(),
- unseenCrashedChildIDs: [],
- crashedBrowserQueues: new Map(),
-
- get prefs() {
- delete this.prefs;
- return this.prefs = Services.prefs.getBranch("browser.tabs.crashReporting.");
- },
-
- init: function () {
- if (this.initialized)
- return;
- this.initialized = true;
-
- Services.obs.addObserver(this, "ipc:content-shutdown", false);
- Services.obs.addObserver(this, "oop-frameloader-crashed", false);
-
- this.pageListener = new RemotePages("about:tabcrashed");
- // LOAD_BACKGROUND pages don't fire load events, so the about:tabcrashed
- // content will fire up its own message when its initial scripts have
- // finished running.
- this.pageListener.addMessageListener("Load", this.receiveMessage.bind(this));
- this.pageListener.addMessageListener("RemotePage:Unload", this.receiveMessage.bind(this));
- this.pageListener.addMessageListener("closeTab", this.receiveMessage.bind(this));
- this.pageListener.addMessageListener("restoreTab", this.receiveMessage.bind(this));
- this.pageListener.addMessageListener("restoreAll", this.receiveMessage.bind(this));
- },
-
- observe: function (aSubject, aTopic, aData) {
- switch (aTopic) {
- case "ipc:content-shutdown": {
- aSubject.QueryInterface(Ci.nsIPropertyBag2);
-
- if (!aSubject.get("abnormal")) {
- return;
- }
-
- let childID = aSubject.get("childID");
- let dumpID = aSubject.get("dumpID");
-
- if (!dumpID) {
- Services.telemetry
- .getHistogramById("FX_CONTENT_CRASH_DUMP_UNAVAILABLE")
- .add(1);
- }
-
- if (!this.flushCrashedBrowserQueue(childID)) {
- this.unseenCrashedChildIDs.push(childID);
- // The elements in unseenCrashedChildIDs will only be removed if
- // the tab crash page is shown. However, ipc:content-shutdown might
- // be fired for processes for which we'll never show the tab crash
- // page - for example, the thumbnailing process. Another case to
- // consider is if the user is configured to submit backlogged crash
- // reports automatically, and a background tab crashes. In that case,
- // we will never show the tab crash page, and never remove the element
- // from the list.
- //
- // Instead of trying to account for all of those cases, we prevent
- // this list from getting too large by putting a reasonable upper
- // limit on how many childIDs we track. It's unlikely that this
- // array would ever get so large as to be unwieldy (that'd be a lot
- // or crashes!), but a leak is a leak.
- if (this.unseenCrashedChildIDs.length > MAX_UNSEEN_CRASHED_CHILD_IDS) {
- this.unseenCrashedChildIDs.shift();
- }
- }
-
- break;
- }
- case "oop-frameloader-crashed": {
- aSubject.QueryInterface(Ci.nsIFrameLoader);
-
- let browser = aSubject.ownerElement;
- if (!browser) {
- return;
- }
-
- this.browserMap.set(browser.permanentKey, aSubject.childID);
- break;
- }
- }
- },
-
- receiveMessage: function(message) {
- let browser = message.target.browser;
- let gBrowser = browser.ownerGlobal.gBrowser;
- let tab = gBrowser.getTabForBrowser(browser);
-
- switch (message.name) {
- case "Load": {
- this.onAboutTabCrashedLoad(message);
- break;
- }
-
- case "RemotePage:Unload": {
- this.onAboutTabCrashedUnload(message);
- break;
- }
-
- case "closeTab": {
- this.maybeSendCrashReport(message);
- gBrowser.removeTab(tab, { animate: true });
- break;
- }
-
- case "restoreTab": {
- this.maybeSendCrashReport(message);
- SessionStore.reviveCrashedTab(tab);
- break;
- }
-
- case "restoreAll": {
- this.maybeSendCrashReport(message);
- SessionStore.reviveAllCrashedTabs();
- break;
- }
- }
- },
-
- /**
- * This should be called once a content process has finished
- * shutting down abnormally. Any tabbrowser browsers that were
- * selected at the time of the crash will then be sent to
- * the crashed tab page.
- *
- * @param childID (int)
- * The childID of the content process that just crashed.
- * @returns boolean
- * True if one or more browsers were sent to the tab crashed
- * page.
- */
- flushCrashedBrowserQueue(childID) {
- let browserQueue = this.crashedBrowserQueues.get(childID);
- if (!browserQueue) {
- return false;
- }
-
- this.crashedBrowserQueues.delete(childID);
-
- let sentBrowser = false;
- for (let weakBrowser of browserQueue) {
- let browser = weakBrowser.get();
- if (browser) {
- this.sendToTabCrashedPage(browser);
- sentBrowser = true;
- }
- }
-
- return sentBrowser;
- },
-
- /**
- * Called by a tabbrowser when it notices that its selected browser
- * has crashed. This will queue the browser to show the tab crash
- * page once the content process has finished tearing down.
- *
- * @param browser (<xul:browser>)
- * The selected browser that just crashed.
- */
- onSelectedBrowserCrash(browser) {
- if (!browser.isRemoteBrowser) {
- Cu.reportError("Selected crashed browser is not remote.")
- return;
- }
- if (!browser.frameLoader) {
- Cu.reportError("Selected crashed browser has no frameloader.");
- return;
- }
-
- let childID = browser.frameLoader.childID;
- let browserQueue = this.crashedBrowserQueues.get(childID);
- if (!browserQueue) {
- browserQueue = [];
- this.crashedBrowserQueues.set(childID, browserQueue);
- }
- // It's probably unnecessary to store this browser as a
- // weak reference, since the content process should complete
- // its teardown in the same tick of the event loop, and then
- // this queue will be flushed. The weak reference is to avoid
- // leaking browsers in case anything goes wrong during this
- // teardown process.
- browserQueue.push(Cu.getWeakReference(browser));
- },
-
- /**
- * This method is exposed for SessionStore to call if the user selects
- * a tab which will restore on demand. It's possible that the tab
- * is in this state because it recently crashed. If that's the case, then
- * it's also possible that the user has not seen the tab crash page for
- * that particular crash, in which case, we might show it to them instead
- * of restoring the tab.
- *
- * @param browser (<xul:browser>)
- * A browser from a browser tab that the user has just selected
- * to restore on demand.
- * @returns (boolean)
- * True if TabCrashHandler will send the user to the tab crash
- * page instead.
- */
- willShowCrashedTab(browser) {
- let childID = this.browserMap.get(browser.permanentKey);
- // We will only show the tab crash page if:
- // 1) We are aware that this browser crashed
- // 2) We know we've never shown the tab crash page for the
- // crash yet
- // 3) The user is not configured to automatically submit backlogged
- // crash reports. If they are, we'll send the crash report
- // immediately.
- if (childID &&
- this.unseenCrashedChildIDs.indexOf(childID) != -1) {
- if (UnsubmittedCrashHandler.autoSubmit) {
- let dumpID = this.childMap.get(childID);
- if (dumpID) {
- UnsubmittedCrashHandler.submitReports([dumpID]);
- }
- } else {
- this.sendToTabCrashedPage(browser);
- return true;
- }
- }
-
- return false;
- },
-
- /**
- * We show a special page to users when a normal browser tab has crashed.
- * This method should be called to send a browser to that page once the
- * process has completely closed.
- *
- * @param browser (<xul:browser>)
- * The browser that has recently crashed.
- */
- sendToTabCrashedPage(browser) {
- let title = browser.contentTitle;
- let uri = browser.currentURI;
- let gBrowser = browser.ownerGlobal.gBrowser;
- let tab = gBrowser.getTabForBrowser(browser);
- // The tab crashed page is non-remote by default.
- gBrowser.updateBrowserRemoteness(browser, false);
-
- browser.setAttribute("crashedPageTitle", title);
- browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
- browser.removeAttribute("crashedPageTitle");
- tab.setAttribute("crashed", true);
- },
-
- /**
- * Submits a crash report from about:tabcrashed, if the crash
- * reporter is enabled and a crash report can be found.
- */
- maybeSendCrashReport(message) {
- /*** STUB ***/
- return;
- },
-
- removeSubmitCheckboxesForSameCrash: function(childID) {
- let enumerator = Services.wm.getEnumerator("navigator:browser");
- while (enumerator.hasMoreElements()) {
- let window = enumerator.getNext();
- if (!window.gMultiProcessBrowser)
- continue;
-
- for (let browser of window.gBrowser.browsers) {
- if (browser.isRemoteBrowser)
- continue;
-
- let doc = browser.contentDocument;
- if (!doc.documentURI.startsWith("about:tabcrashed"))
- continue;
-
- if (this.browserMap.get(browser.permanentKey) == childID) {
- this.browserMap.delete(browser.permanentKey);
- let ports = this.pageListener.portsForBrowser(browser);
- if (ports.length) {
- // For about:tabcrashed, we don't expect subframes. We can
- // assume sending to the first port is sufficient.
- ports[0].sendAsyncMessage("CrashReportSent");
- }
- }
- }
- }
- },
-
- onAboutTabCrashedLoad: function (message) {
- this._crashedTabCount++;
-
- // Broadcast to all about:tabcrashed pages a count of
- // how many about:tabcrashed pages exist, so that they
- // can decide whether or not to display the "Restore All
- // Crashed Tabs" button.
- this.pageListener.sendAsyncMessage("UpdateCount", {
- count: this._crashedTabCount,
- });
-
- let browser = message.target.browser;
-
- let childID = this.browserMap.get(browser.permanentKey);
- let index = this.unseenCrashedChildIDs.indexOf(childID);
- if (index != -1) {
- this.unseenCrashedChildIDs.splice(index, 1);
- }
-
- let dumpID = this.getDumpID(browser);
- if (!dumpID) {
- message.target.sendAsyncMessage("SetCrashReportAvailable", {
- hasReport: false,
- });
- return;
- }
-
- let requestAutoSubmit = !UnsubmittedCrashHandler.autoSubmit;
- let requestEmail = this.prefs.getBoolPref("requestEmail");
- let sendReport = this.prefs.getBoolPref("sendReport");
- let includeURL = this.prefs.getBoolPref("includeURL");
- let emailMe = this.prefs.getBoolPref("emailMe");
-
- let data = {
- hasReport: true,
- sendReport,
- includeURL,
- emailMe,
- requestAutoSubmit,
- requestEmail,
- };
-
- if (emailMe) {
- data.email = this.prefs.getCharPref("email", "");
- }
-
- // Make sure to only count once even if there are multiple windows
- // that will all show about:tabcrashed.
- if (this._crashedTabCount == 1) {
- Services.telemetry.getHistogramById("FX_CONTENT_CRASH_PRESENTED").add(1);
- }
-
- message.target.sendAsyncMessage("SetCrashReportAvailable", data);
- },
-
- onAboutTabCrashedUnload(message) {
- if (!this._crashedTabCount) {
- Cu.reportError("Can not decrement crashed tab count to below 0");
- return;
- }
- this._crashedTabCount--;
-
- // Broadcast to all about:tabcrashed pages a count of
- // how many about:tabcrashed pages exist, so that they
- // can decide whether or not to display the "Restore All
- // Crashed Tabs" button.
- this.pageListener.sendAsyncMessage("UpdateCount", {
- count: this._crashedTabCount,
- });
-
- let browser = message.target.browser;
- let childID = this.browserMap.get(browser.permanentKey);
-
- // Make sure to only count once even if there are multiple windows
- // that will all show about:tabcrashed.
- if (this._crashedTabCount == 0 && childID) {
- Services.telemetry.getHistogramById("FX_CONTENT_CRASH_NOT_SUBMITTED").add(1);
- }
- },
-
- /**
- * For some <xul:browser>, return a crash report dump ID for that browser
- * if we have been informed of one. Otherwise, return null.
- */
- getDumpID(browser) {
- /*** STUB ***/
- return null;
- },
-}
-
-/**
- * This component is responsible for scanning the pending
- * crash report directory for reports, and (if enabled), to
- * prompt the user to submit those reports. It might also
- * submit those reports automatically without prompting if
- * the user has opted in.
- */
-this.UnsubmittedCrashHandler = {
- get prefs() {
- delete this.prefs;
- return this.prefs =
- Services.prefs.getBranch("browser.crashReports.unsubmittedCheck.");
- },
-
- get enabled() {
- return this.prefs.getBoolPref("enabled");
- },
-
- // showingNotification is set to true once a notification
- // is successfully shown, and then set back to false if
- // the notification is dismissed by an action by the user.
- showingNotification: false,
- // suppressed is true if we've determined that we've shown
- // the notification too many times across too many days without
- // user interaction, so we're suppressing the notification for
- // some number of days. See the documentation for
- // shouldShowPendingSubmissionsNotification().
- suppressed: false,
-
- init() {
- if (this.initialized) {
- return;
- }
-
- this.initialized = true;
-
- // UnsubmittedCrashHandler can be initialized but still be disabled.
- // This is intentional, as this makes simulating UnsubmittedCrashHandler's
- // reactions to browser startup and shutdown easier in test automation.
- //
- // UnsubmittedCrashHandler, when initialized but not enabled, is inert.
- if (this.enabled) {
- if (this.prefs.prefHasUserValue("suppressUntilDate")) {
- if (this.prefs.getCharPref("suppressUntilDate") > this.dateString()) {
- // We'll be suppressing any notifications until after suppressedDate,
- // so there's no need to do anything more.
- this.suppressed = true;
- return;
- }
-
- // We're done suppressing, so we don't need this pref anymore.
- this.prefs.clearUserPref("suppressUntilDate");
- }
-
- Services.obs.addObserver(this, "browser-delayed-startup-finished",
- false);
- Services.obs.addObserver(this, "profile-before-change",
- false);
- }
- },
-
- uninit() {
- if (!this.initialized) {
- return;
- }
-
- this.initialized = false;
-
- if (!this.enabled) {
- return;
- }
-
- if (this.suppressed) {
- this.suppressed = false;
- // No need to do any more clean-up, since we were suppressed.
- return;
- }
-
- if (this.showingNotification) {
- this.prefs.setBoolPref("shutdownWhileShowing", true);
- this.showingNotification = false;
- }
-
- try {
- Services.obs.removeObserver(this, "browser-delayed-startup-finished");
- } catch (e) {
- // The browser-delayed-startup-finished observer might have already
- // fired and removed itself, so if this fails, it's okay.
- if (e.result != Cr.NS_ERROR_FAILURE) {
- throw e;
- }
- }
-
- Services.obs.removeObserver(this, "profile-before-change");
- },
-
- observe(subject, topic, data) {
- switch (topic) {
- case "browser-delayed-startup-finished": {
- Services.obs.removeObserver(this, topic);
- this.checkForUnsubmittedCrashReports();
- break;
- }
- case "profile-before-change": {
- this.uninit();
- break;
- }
- }
- },
-
- /**
- * Scans the profile directory for unsubmitted crash reports
- * within the past PENDING_CRASH_REPORT_DAYS days. If it
- * finds any, it will, if necessary, attempt to open a notification
- * bar to prompt the user to submit them.
- *
- * @returns Promise
- * Resolves with the <xul:notification> after it tries to
- * show a notification on the most recent browser window.
- * If a notification cannot be shown, will resolve with null.
- */
- checkForUnsubmittedCrashReports: Task.async(function*() {
- let dateLimit = new Date();
- dateLimit.setDate(dateLimit.getDate() - PENDING_CRASH_REPORT_DAYS);
-
- let reportIDs = [];
- try {
- reportIDs = yield CrashSubmit.pendingIDsAsync(dateLimit);
- } catch (e) {
- Cu.reportError(e);
- return null;
- }
-
- if (reportIDs.length) {
- if (this.autoSubmit) {
- this.submitReports(reportIDs);
- } else if (this.shouldShowPendingSubmissionsNotification()) {
- return this.showPendingSubmissionsNotification(reportIDs);
- }
- }
- return null;
- }),
-
- /**
- * Returns true if the notification should be shown.
- * shouldShowPendingSubmissionsNotification makes this decision
- * by looking at whether or not the user has seen the notification
- * over several days without ever interacting with it. If this occurs
- * too many times, we suppress the notification for DAYS_TO_SUPPRESS
- * days.
- *
- * @returns bool
- */
- shouldShowPendingSubmissionsNotification() {
- if (!this.prefs.prefHasUserValue("shutdownWhileShowing")) {
- return true;
- }
-
- let shutdownWhileShowing = this.prefs.getBoolPref("shutdownWhileShowing");
- this.prefs.clearUserPref("shutdownWhileShowing");
-
- if (!this.prefs.prefHasUserValue("lastShownDate")) {
- // This isn't expected, but we're being defensive here. We'll
- // opt for showing the notification in this case.
- return true;
- }
-
- let lastShownDate = this.prefs.getCharPref("lastShownDate");
- if (this.dateString() > lastShownDate && shutdownWhileShowing) {
- // We're on a newer day then when we last showed the
- // notification without closing it. We don't want to do
- // this too many times, so we'll decrement a counter for
- // this situation. Too many of these, and we'll assume the
- // user doesn't know or care about unsubmitted notifications,
- // and we'll suppress the notification for a while.
- let chances = this.prefs.getIntPref("chancesUntilSuppress");
- if (--chances < 0) {
- // We're out of chances!
- this.prefs.clearUserPref("chancesUntilSuppress");
- // We'll suppress for DAYS_TO_SUPPRESS days.
- let suppressUntil =
- this.dateString(new Date(Date.now() + (DAY * DAYS_TO_SUPPRESS)));
- this.prefs.setCharPref("suppressUntilDate", suppressUntil);
- return false;
- }
- this.prefs.setIntPref("chancesUntilSuppress", chances);
- }
-
- return true;
- },
-
- /**
- * Given an array of unsubmitted crash report IDs, try to open
- * up a notification asking the user to submit them.
- *
- * @param reportIDs (Array<string>)
- * The Array of report IDs to offer the user to send.
- * @returns The <xul:notification> if one is shown. null otherwise.
- */
- showPendingSubmissionsNotification(reportIDs) {
- let count = reportIDs.length;
- if (!count) {
- return null;
- }
-
- let messageTemplate =
- gNavigatorBundle.GetStringFromName("pendingCrashReports2.label");
-
- let message = PluralForm.get(count, messageTemplate).replace("#1", count);
-
- let notification = this.show({
- notificationID: "pending-crash-reports",
- message,
- reportIDs,
- onAction: () => {
- this.showingNotification = false;
- },
- });
-
- if (notification) {
- this.showingNotification = true;
- this.prefs.setCharPref("lastShownDate", this.dateString());
- }
-
- return notification;
- },
-
- /**
- * Returns a string representation of a Date in the format
- * YYYYMMDD.
- *
- * @param someDate (Date, optional)
- * The Date to convert to the string. If not provided,
- * defaults to today's date.
- * @returns String
- */
- dateString(someDate = new Date()) {
- let year = String(someDate.getFullYear()).padStart(4, "0");
- let month = String(someDate.getMonth() + 1).padStart(2, "0");
- let day = String(someDate.getDate()).padStart(2, "0");
- return year + month + day;
- },
-
- /**
- * Attempts to show a notification bar to the user in the most
- * recent browser window asking them to submit some crash report
- * IDs. If a notification cannot be shown (for example, there
- * is no browser window), this method exits silently.
- *
- * The notification will allow the user to submit their crash
- * reports. If the user dismissed the notification, the crash
- * reports will be marked to be ignored (though they can
- * still be manually submitted via about:crashes).
- *
- * @param JS Object
- * An Object with the following properties:
- *
- * notificationID (string)
- * The ID for the notification to be opened.
- *
- * message (string)
- * The message to be displayed in the notification.
- *
- * reportIDs (Array<string>)
- * The array of report IDs to offer to the user.
- *
- * onAction (function, optional)
- * A callback to fire once the user performs an
- * action on the notification bar (this includes
- * dismissing the notification).
- *
- * @returns The <xul:notification> if one is shown. null otherwise.
- */
- show({ notificationID, message, reportIDs, onAction }) {
- let chromeWin = RecentWindow.getMostRecentBrowserWindow();
- if (!chromeWin) {
- // Can't show a notification in this case. We'll hopefully
- // get another opportunity to have the user submit their
- // crash reports later.
- return null;
- }
-
- let nb = chromeWin.document.getElementById("global-notificationbox");
- let notification = nb.getNotificationWithValue(notificationID);
- if (notification) {
- return null;
- }
-
- let buttons = [{
- label: gNavigatorBundle.GetStringFromName("pendingCrashReports.send"),
- callback: () => {
- this.submitReports(reportIDs);
- if (onAction) {
- onAction();
- }
- },
- },
- {
- label: gNavigatorBundle.GetStringFromName("pendingCrashReports.alwaysSend"),
- callback: () => {
- this.autoSubmit = true;
- this.submitReports(reportIDs);
- if (onAction) {
- onAction();
- }
- },
- },
- {
- label: gNavigatorBundle.GetStringFromName("pendingCrashReports.viewAll"),
- callback: function() {
- chromeWin.openUILinkIn("about:crashes", "tab");
- return true;
- },
- }];
-
- let eventCallback = (eventType) => {
- if (eventType == "dismissed") {
- // The user intentionally dismissed the notification,
- // which we interpret as meaning that they don't care
- // to submit the reports. We'll ignore these particular
- // reports going forward.
- reportIDs.forEach(function(reportID) {
- CrashSubmit.ignore(reportID);
- });
- if (onAction) {
- onAction();
- }
- }
- };
-
- return nb.appendNotification(message, notificationID,
- "chrome://browser/skin/tab-crashed.svg",
- nb.PRIORITY_INFO_HIGH, buttons,
- eventCallback);
- },
-
- get autoSubmit() {
- return Services.prefs
- .getBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit2");
- },
-
- set autoSubmit(val) {
- Services.prefs.setBoolPref("browser.crashReports.unsubmittedCheck.autoSubmit2",
- val);
- },
-
- /**
- * Attempt to submit reports to the crash report server. Each
- * report will have the "SubmittedFromInfobar" extra key set
- * to true.
- *
- * @param reportIDs (Array<string>)
- * The array of reportIDs to submit.
- */
- submitReports(reportIDs) {
- for (let reportID of reportIDs) {
- CrashSubmit.submit(reportID, {
- extraExtraKeyVals: {
- "SubmittedFromInfobar": true,
- },
- });
- }
- },
-};
-
-this.PluginCrashReporter = {
- /**
- * Makes the PluginCrashReporter ready to hear about and
- * submit crash reports.
- */
- init() {
- if (this.initialized) {
- return;
- }
-
- this.initialized = true;
- this.crashReports = new Map();
-
- Services.obs.addObserver(this, "plugin-crashed", false);
- Services.obs.addObserver(this, "gmp-plugin-crash", false);
- Services.obs.addObserver(this, "profile-after-change", false);
- },
-
- uninit() {
- Services.obs.removeObserver(this, "plugin-crashed", false);
- Services.obs.removeObserver(this, "gmp-plugin-crash", false);
- Services.obs.removeObserver(this, "profile-after-change", false);
- this.initialized = false;
- },
-
- observe(subject, topic, data) {
- switch (topic) {
- case "plugin-crashed": {
- let propertyBag = subject;
- if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
- !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
- !propertyBag.hasKey("runID") ||
- !propertyBag.hasKey("pluginDumpID")) {
- Cu.reportError("PluginCrashReporter can not read plugin information.");
- return;
- }
-
- let runID = propertyBag.getPropertyAsUint32("runID");
- let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
- let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
- if (pluginDumpID) {
- this.crashReports.set(runID, { pluginDumpID, browserDumpID });
- }
- break;
- }
- case "gmp-plugin-crash": {
- let propertyBag = subject;
- if (!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
- !propertyBag.hasKey("pluginID") ||
- !propertyBag.hasKey("pluginDumpID") ||
- !propertyBag.hasKey("pluginName")) {
- Cu.reportError("PluginCrashReporter can not read plugin information.");
- return;
- }
-
- let pluginID = propertyBag.getPropertyAsUint32("pluginID");
- let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
- if (pluginDumpID) {
- this.crashReports.set(pluginID, { pluginDumpID });
- }
-
- // Only the parent process gets the gmp-plugin-crash observer
- // notification, so we need to inform any content processes that
- // the GMP has crashed.
- if (Cc["@mozilla.org/parentprocessmessagemanager;1"]) {
- let pluginName = propertyBag.getPropertyAsAString("pluginName");
- let mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
- .getService(Ci.nsIMessageListenerManager);
- mm.broadcastAsyncMessage("gmp-plugin-crash",
- { pluginName, pluginID });
- }
- break;
- }
- case "profile-after-change":
- this.uninit();
- break;
- }
- },
-
- /**
- * Submit a crash report for a crashed NPAPI plugin.
- *
- * @param runID
- * The runID of the plugin that crashed. A run ID is a unique
- * identifier for a particular run of a plugin process - and is
- * analogous to a process ID (though it is managed by Gecko instead
- * of the operating system).
- * @param keyVals
- * An object whose key-value pairs will be merged
- * with the ".extra" file submitted with the report.
- * The properties of htis object will override properties
- * of the same name in the .extra file.
- */
- submitCrashReport(runID, keyVals) {
- if (!this.crashReports.has(runID)) {
- Cu.reportError(`Could not find plugin dump IDs for run ID ${runID}.` +
- `It is possible that a report was already submitted.`);
- return;
- }
-
- keyVals = keyVals || {};
- let { pluginDumpID, browserDumpID } = this.crashReports.get(runID);
-
- let submissionPromise = CrashSubmit.submit(pluginDumpID, {
- recordSubmission: true,
- extraExtraKeyVals: keyVals,
- });
-
- if (browserDumpID)
- CrashSubmit.submit(browserDumpID);
-
- this.broadcastState(runID, "submitting");
-
- submissionPromise.then(() => {
- this.broadcastState(runID, "success");
- }, () => {
- this.broadcastState(runID, "failed");
- });
-
- this.crashReports.delete(runID);
- },
-
- broadcastState(runID, state) {
- let enumerator = Services.wm.getEnumerator("navigator:browser");
- while (enumerator.hasMoreElements()) {
- let window = enumerator.getNext();
- let mm = window.messageManager;
- mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
- { runID, state });
- }
- },
-
- hasCrashReport(runID) {
- return this.crashReports.has(runID);
- },
-};
diff --git a/browser/modules/ContentLinkHandler.jsm b/browser/modules/ContentLinkHandler.jsm
deleted file mode 100644
index 443cae2da..000000000
--- a/browser/modules/ContentLinkHandler.jsm
+++ /dev/null
@@ -1,147 +0,0 @@
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "ContentLinkHandler" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Feeds",
- "resource:///modules/Feeds.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
- "resource://gre/modules/BrowserUtils.jsm");
-
-const SIZES_TELEMETRY_ENUM = {
- NO_SIZES: 0,
- ANY: 1,
- DIMENSION: 2,
- INVALID: 3,
-};
-
-this.ContentLinkHandler = {
- init: function(chromeGlobal) {
- chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
- this.onLinkEvent(event, chromeGlobal);
- }, false);
- chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
- this.onLinkEvent(event, chromeGlobal);
- }, false);
- },
-
- onLinkEvent: function(event, chromeGlobal) {
- var link = event.originalTarget;
- var rel = link.rel && link.rel.toLowerCase();
- if (!link || !link.ownerDocument || !rel || !link.href)
- return;
-
- // Ignore sub-frames (bugs 305472, 479408).
- let window = link.ownerGlobal;
- if (window != window.top)
- return;
-
- var feedAdded = false;
- var iconAdded = false;
- var searchAdded = false;
- var rels = {};
- for (let relString of rel.split(/\s+/))
- rels[relString] = true;
-
- for (let relVal in rels) {
- switch (relVal) {
- case "feed":
- case "alternate":
- if (!feedAdded && event.type == "DOMLinkAdded") {
- if (!rels.feed && rels.alternate && rels.stylesheet)
- break;
-
- if (Feeds.isValidFeed(link, link.ownerDocument.nodePrincipal, "feed" in rels)) {
- chromeGlobal.sendAsyncMessage("Link:AddFeed",
- {type: link.type,
- href: link.href,
- title: link.title});
- feedAdded = true;
- }
- }
- break;
- case "icon":
- if (iconAdded || !Services.prefs.getBoolPref("browser.chrome.site_icons"))
- break;
-
- var uri = this.getLinkIconURI(link);
- if (!uri)
- break;
-
- // Telemetry probes for measuring the sizes attribute
- // usage and available dimensions.
- let sizeHistogramTypes = Services.telemetry.
- getHistogramById("LINK_ICON_SIZES_ATTR_USAGE");
- let sizeHistogramDimension = Services.telemetry.
- getHistogramById("LINK_ICON_SIZES_ATTR_DIMENSION");
- let sizesType;
- if (link.sizes.length) {
- for (let size of link.sizes) {
- if (size.toLowerCase() == "any") {
- sizesType = SIZES_TELEMETRY_ENUM.ANY;
- break;
- } else {
- let re = /^([1-9][0-9]*)x[1-9][0-9]*$/i;
- let values = re.exec(size);
- if (values && values.length > 1) {
- sizesType = SIZES_TELEMETRY_ENUM.DIMENSION;
- sizeHistogramDimension.add(parseInt(values[1]));
- } else {
- sizesType = SIZES_TELEMETRY_ENUM.INVALID;
- break;
- }
- }
- }
- } else {
- sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
- }
- sizeHistogramTypes.add(sizesType);
-
- chromeGlobal.sendAsyncMessage(
- "Link:SetIcon",
- {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
- iconAdded = true;
- break;
- case "search":
- if (!searchAdded && event.type == "DOMLinkAdded") {
- var type = link.type && link.type.toLowerCase();
- type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
-
- let re = /^(?:https?|ftp):/i;
- if (type == "application/opensearchdescription+xml" && link.title &&
- re.test(link.href))
- {
- let engine = { title: link.title, href: link.href };
- chromeGlobal.sendAsyncMessage("Link:AddSearch",
- {engine: engine,
- url: link.ownerDocument.documentURI});
- searchAdded = true;
- }
- }
- break;
- }
- }
- },
-
- getLinkIconURI: function(aLink) {
- let targetDoc = aLink.ownerDocument;
- var uri = BrowserUtils.makeURI(aLink.href, targetDoc.characterSet);
- try {
- uri.userPass = "";
- } catch (e) {
- // some URIs are immutable
- }
- return uri;
- },
-};
diff --git a/browser/modules/ContentObservers.jsm b/browser/modules/ContentObservers.jsm
deleted file mode 100644
index 9d627ddc2..000000000
--- a/browser/modules/ContentObservers.jsm
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-
-/**
- * This module is for small observers that we want to register once per content
- * process, usually in order to forward content-based observer service notifications
- * to the chrome process through message passing. Using a JSM avoids having them
- * in content.js and thereby registering N observers for N open tabs, which is bad
- * for perf.
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-var gEMEUIObserver = function(subject, topic, data) {
- let win = subject.top;
- let mm = getMessageManagerForWindow(win);
- if (mm) {
- mm.sendAsyncMessage("EMEVideo:ContentMediaKeysRequest", data);
- }
-};
-
-var gDecoderDoctorObserver = function(subject, topic, data) {
- let win = subject.top;
- let mm = getMessageManagerForWindow(win);
- if (mm) {
- mm.sendAsyncMessage("DecoderDoctor:Notification", data);
- }
-};
-
-function getMessageManagerForWindow(aContentWindow) {
- let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .sameTypeRootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor);
- try {
- // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
- return ir.getInterface(Ci.nsIContentFrameMessageManager);
- } catch (e) {
- if (e.result == Cr.NS_NOINTERFACE) {
- return null;
- }
- throw e;
- }
-}
-
-Services.obs.addObserver(gEMEUIObserver, "mediakeys-request", false);
-Services.obs.addObserver(gDecoderDoctorObserver, "decoder-doctor-notification", false);
diff --git a/browser/modules/ContentSearch.jsm b/browser/modules/ContentSearch.jsm
deleted file mode 100644
index 91b0b9ac8..000000000
--- a/browser/modules/ContentSearch.jsm
+++ /dev/null
@@ -1,566 +0,0 @@
-/* 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/. */
-/* globals XPCOMUtils, Services, Task, Promise, SearchSuggestionController, FormHistory, PrivateBrowsingUtils */
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
- "ContentSearch",
-];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
- "resource://gre/modules/FormHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "SearchSuggestionController",
- "resource://gre/modules/SearchSuggestionController.jsm");
-
-const INBOUND_MESSAGE = "ContentSearch";
-const OUTBOUND_MESSAGE = INBOUND_MESSAGE;
-const MAX_LOCAL_SUGGESTIONS = 3;
-const MAX_SUGGESTIONS = 6;
-
-/**
- * ContentSearch receives messages named INBOUND_MESSAGE and sends messages
- * named OUTBOUND_MESSAGE. The data of each message is expected to look like
- * { type, data }. type is the message's type (or subtype if you consider the
- * type of the message itself to be INBOUND_MESSAGE), and data is data that is
- * specific to the type.
- *
- * Inbound messages have the following types:
- *
- * AddFormHistoryEntry
- * Adds an entry to the search form history.
- * data: the entry, a string
- * GetSuggestions
- * Retrieves an array of search suggestions given a search string.
- * data: { engineName, searchString, [remoteTimeout] }
- * GetState
- * Retrieves the current search engine state.
- * data: null
- * GetStrings
- * Retrieves localized search UI strings.
- * data: null
- * ManageEngines
- * Opens the search engine management window.
- * data: null
- * RemoveFormHistoryEntry
- * Removes an entry from the search form history.
- * data: the entry, a string
- * Search
- * Performs a search.
- * Any GetSuggestions messages in the queue from the same target will be
- * cancelled.
- * data: { engineName, searchString, healthReportKey, searchPurpose }
- * SetCurrentEngine
- * Sets the current engine.
- * data: the name of the engine
- * SpeculativeConnect
- * Speculatively connects to an engine.
- * data: the name of the engine
- *
- * Outbound messages have the following types:
- *
- * CurrentEngine
- * Broadcast when the current engine changes.
- * data: see _currentEngineObj
- * CurrentState
- * Broadcast when the current search state changes.
- * data: see currentStateObj
- * State
- * Sent in reply to GetState.
- * data: see currentStateObj
- * Strings
- * Sent in reply to GetStrings
- * data: Object containing string names and values for the current locale.
- * Suggestions
- * Sent in reply to GetSuggestions.
- * data: see _onMessageGetSuggestions
- * SuggestionsCancelled
- * Sent in reply to GetSuggestions when pending GetSuggestions events are
- * cancelled.
- * data: null
- */
-
-this.ContentSearch = {
-
- // Inbound events are queued and processed in FIFO order instead of handling
- // them immediately, which would result in non-FIFO responses due to the
- // asynchrononicity added by converting image data URIs to ArrayBuffers.
- _eventQueue: [],
- _currentEventPromise: null,
-
- // This is used to handle search suggestions. It maps xul:browsers to objects
- // { controller, previousFormHistoryResult }. See _onMessageGetSuggestions.
- _suggestionMap: new WeakMap(),
-
- // Resolved when we finish shutting down.
- _destroyedPromise: null,
-
- // The current controller and browser in _onMessageGetSuggestions. Allows
- // fetch cancellation from _cancelSuggestions.
- _currentSuggestion: null,
-
- init: function () {
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- addMessageListener(INBOUND_MESSAGE, this);
- Services.obs.addObserver(this, "browser-search-engine-modified", false);
- Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
- Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
- this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
- },
-
- get searchSuggestionUIStrings() {
- if (this._searchSuggestionUIStrings) {
- return this._searchSuggestionUIStrings;
- }
- this._searchSuggestionUIStrings = {};
- let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
- let stringNames = ["searchHeader", "searchPlaceholder", "searchForSomethingWith",
- "searchWithHeader", "searchSettings"];
-
- for (let name of stringNames) {
- this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
- }
- return this._searchSuggestionUIStrings;
- },
-
- destroy: function () {
- if (this._destroyedPromise) {
- return this._destroyedPromise;
- }
-
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- removeMessageListener(INBOUND_MESSAGE, this);
- Services.obs.removeObserver(this, "browser-search-engine-modified");
- Services.obs.removeObserver(this, "shutdown-leaks-before-check");
-
- this._eventQueue.length = 0;
- this._destroyedPromise = Promise.resolve(this._currentEventPromise);
- return this._destroyedPromise;
- },
-
- /**
- * Focuses the search input in the page with the given message manager.
- * @param messageManager
- * The MessageManager object of the selected browser.
- */
- focusInput: function (messageManager) {
- messageManager.sendAsyncMessage(OUTBOUND_MESSAGE, {
- type: "FocusInput"
- });
- },
-
- receiveMessage: function (msg) {
- // Add a temporary event handler that exists only while the message is in
- // the event queue. If the message's source docshell changes browsers in
- // the meantime, then we need to update msg.target. event.detail will be
- // the docshell's new parent <xul:browser> element.
- msg.handleEvent = event => {
- let browserData = this._suggestionMap.get(msg.target);
- if (browserData) {
- this._suggestionMap.delete(msg.target);
- this._suggestionMap.set(event.detail, browserData);
- }
- msg.target.removeEventListener("SwapDocShells", msg, true);
- msg.target = event.detail;
- msg.target.addEventListener("SwapDocShells", msg, true);
- };
- msg.target.addEventListener("SwapDocShells", msg, true);
-
- // Search requests cause cancellation of all Suggestion requests from the
- // same browser.
- if (msg.data.type === "Search") {
- this._cancelSuggestions(msg);
- }
-
- this._eventQueue.push({
- type: "Message",
- data: msg,
- });
- this._processEventQueue();
- },
-
- observe: function (subj, topic, data) {
- switch (topic) {
- case "nsPref:changed":
- case "browser-search-engine-modified":
- this._eventQueue.push({
- type: "Observe",
- data: data,
- });
- this._processEventQueue();
- break;
- case "shutdown-leaks-before-check":
- subj.wrappedJSObject.client.addBlocker(
- "ContentSearch: Wait until the service is destroyed", () => this.destroy());
- break;
- }
- },
-
- removeFormHistoryEntry: function (msg, entry) {
- let browserData = this._suggestionDataForBrowser(msg.target);
- if (browserData && browserData.previousFormHistoryResult) {
- let { previousFormHistoryResult } = browserData;
- for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
- if (previousFormHistoryResult.getValueAt(i) === entry) {
- previousFormHistoryResult.removeValueAt(i, true);
- break;
- }
- }
- }
- },
-
- performSearch: function (msg, data) {
- this._ensureDataHasProperties(data, [
- "engineName",
- "searchString",
- "healthReportKey",
- "searchPurpose",
- ]);
- let engine = Services.search.getEngineByName(data.engineName);
- let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
- let browser = msg.target;
- let win = browser.ownerGlobal;
- if (!win) {
- // The browser may have been closed between the time its content sent the
- // message and the time we handle it.
- return;
- }
- let where = win.whereToOpenLink(data.originalEvent);
-
- // There is a chance that by the time we receive the search message, the user
- // has switched away from the tab that triggered the search. If, based on the
- // event, we need to load the search in the same tab that triggered it (i.e.
- // where === "current"), openUILinkIn will not work because that tab is no
- // longer the current one. For this case we manually load the URI.
- if (where === "current") {
- browser.loadURIWithFlags(submission.uri.spec,
- Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
- submission.postData);
- } else {
- let params = {
- postData: submission.postData,
- inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
- };
- win.openUILinkIn(submission.uri.spec, where, params);
- }
- win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey,
- { selection: data.selection });
- return;
- },
-
- getSuggestions: Task.async(function* (engineName, searchString, browser, remoteTimeout=null) {
- let engine = Services.search.getEngineByName(engineName);
- if (!engine) {
- throw new Error("Unknown engine name: " + engineName);
- }
-
- let browserData = this._suggestionDataForBrowser(browser, true);
- let { controller } = browserData;
- let ok = SearchSuggestionController.engineOffersSuggestions(engine);
- controller.maxLocalResults = ok ? MAX_LOCAL_SUGGESTIONS : MAX_SUGGESTIONS;
- controller.maxRemoteResults = ok ? MAX_SUGGESTIONS : 0;
- controller.remoteTimeout = remoteTimeout || undefined;
- let priv = PrivateBrowsingUtils.isBrowserPrivate(browser);
- // fetch() rejects its promise if there's a pending request, but since we
- // process our event queue serially, there's never a pending request.
- this._currentSuggestion = { controller: controller, target: browser };
- let suggestions = yield controller.fetch(searchString, priv, engine);
- this._currentSuggestion = null;
-
- // suggestions will be null if the request was cancelled
- let result = {};
- if (!suggestions) {
- return result;
- }
-
- // Keep the form history result so RemoveFormHistoryEntry can remove entries
- // from it. Keeping only one result isn't foolproof because the client may
- // try to remove an entry from one set of suggestions after it has requested
- // more but before it's received them. In that case, the entry may not
- // appear in the new suggestions. But that should happen rarely.
- browserData.previousFormHistoryResult = suggestions.formHistoryResult;
- result = {
- engineName,
- term: suggestions.term,
- local: suggestions.local,
- remote: suggestions.remote,
- };
- return result;
- }),
-
- addFormHistoryEntry: Task.async(function* (browser, entry="") {
- let isPrivate = false;
- try {
- // isBrowserPrivate assumes that the passed-in browser has all the normal
- // properties, which won't be true if the browser has been destroyed.
- // That may be the case here due to the asynchronous nature of messaging.
- isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser.target);
- } catch (err) {
- return false;
- }
- if (isPrivate || entry === "") {
- return false;
- }
- let browserData = this._suggestionDataForBrowser(browser.target, true);
- FormHistory.update({
- op: "bump",
- fieldname: browserData.controller.formHistoryParam,
- value: entry,
- }, {
- handleCompletion: () => {},
- handleError: err => {
- Cu.reportError("Error adding form history entry: " + err);
- },
- });
- return true;
- }),
-
- currentStateObj: Task.async(function* (uriFlag=false) {
- let state = {
- engines: [],
- currentEngine: yield this._currentEngineObj(),
- };
- if (uriFlag) {
- state.currentEngine.iconBuffer = Services.search.currentEngine.getIconURLBySize(16, 16);
- }
- let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
- let hiddenList = pref ? pref.split(",") : [];
- for (let engine of Services.search.getVisibleEngines()) {
- let uri = engine.getIconURLBySize(16, 16);
- let iconBuffer = uri;
- if (!uriFlag) {
- iconBuffer = yield this._arrayBufferFromDataURI(uri);
- }
- state.engines.push({
- name: engine.name,
- iconBuffer,
- hidden: hiddenList.indexOf(engine.name) !== -1,
- });
- }
- return state;
- }),
-
- _processEventQueue: function () {
- if (this._currentEventPromise || !this._eventQueue.length) {
- return;
- }
-
- let event = this._eventQueue.shift();
-
- this._currentEventPromise = Task.spawn(function* () {
- try {
- yield this["_on" + event.type](event.data);
- } catch (err) {
- Cu.reportError(err);
- } finally {
- this._currentEventPromise = null;
- this._processEventQueue();
- }
- }.bind(this));
- },
-
- _cancelSuggestions: function (msg) {
- let cancelled = false;
- // cancel active suggestion request
- if (this._currentSuggestion && this._currentSuggestion.target === msg.target) {
- this._currentSuggestion.controller.stop();
- cancelled = true;
- }
- // cancel queued suggestion requests
- for (let i = 0; i < this._eventQueue.length; i++) {
- let m = this._eventQueue[i].data;
- if (msg.target === m.target && m.data.type === "GetSuggestions") {
- this._eventQueue.splice(i, 1);
- cancelled = true;
- i--;
- }
- }
- if (cancelled) {
- this._reply(msg, "SuggestionsCancelled");
- }
- },
-
- _onMessage: Task.async(function* (msg) {
- let methodName = "_onMessage" + msg.data.type;
- if (methodName in this) {
- yield this._initService();
- yield this[methodName](msg, msg.data.data);
- if (!Cu.isDeadWrapper(msg.target)) {
- msg.target.removeEventListener("SwapDocShells", msg, true);
- }
- }
- }),
-
- _onMessageGetState: function (msg, data) {
- return this.currentStateObj().then(state => {
- this._reply(msg, "State", state);
- });
- },
-
- _onMessageGetStrings: function (msg, data) {
- this._reply(msg, "Strings", this.searchSuggestionUIStrings);
- },
-
- _onMessageSearch: function (msg, data) {
- this.performSearch(msg, data);
- },
-
- _onMessageSetCurrentEngine: function (msg, data) {
- Services.search.currentEngine = Services.search.getEngineByName(data);
- },
-
- _onMessageManageEngines: function (msg, data) {
- let browserWin = msg.target.ownerGlobal;
- browserWin.openPreferences("paneSearch");
- },
-
- _onMessageGetSuggestions: Task.async(function* (msg, data) {
- this._ensureDataHasProperties(data, [
- "engineName",
- "searchString",
- ]);
- let {engineName, searchString} = data;
- let suggestions = yield this.getSuggestions(engineName, searchString, msg.target);
-
- this._reply(msg, "Suggestions", {
- engineName: data.engineName,
- searchString: suggestions.term,
- formHistory: suggestions.local,
- remote: suggestions.remote,
- });
- }),
-
- _onMessageAddFormHistoryEntry: Task.async(function* (msg, entry) {
- yield this.addFormHistoryEntry(msg, entry);
- }),
-
- _onMessageRemoveFormHistoryEntry: function (msg, entry) {
- this.removeFormHistoryEntry(msg, entry);
- },
-
- _onMessageSpeculativeConnect: function (msg, engineName) {
- let engine = Services.search.getEngineByName(engineName);
- if (!engine) {
- throw new Error("Unknown engine name: " + engineName);
- }
- if (msg.target.contentWindow) {
- engine.speculativeConnect({
- window: msg.target.contentWindow,
- });
- }
- },
-
- _onObserve: Task.async(function* (data) {
- if (data === "engine-current") {
- let engine = yield this._currentEngineObj();
- this._broadcast("CurrentEngine", engine);
- }
- else if (data !== "engine-default") {
- // engine-default is always sent with engine-current and isn't otherwise
- // relevant to content searches.
- let state = yield this.currentStateObj();
- this._broadcast("CurrentState", state);
- }
- }),
-
- _suggestionDataForBrowser: function (browser, create=false) {
- let data = this._suggestionMap.get(browser);
- if (!data && create) {
- // Since one SearchSuggestionController instance is meant to be used per
- // autocomplete widget, this means that we assume each xul:browser has at
- // most one such widget.
- data = {
- controller: new SearchSuggestionController(),
- };
- this._suggestionMap.set(browser, data);
- }
- return data;
- },
-
- _reply: function (msg, type, data) {
- // We reply asyncly to messages, and by the time we reply the browser we're
- // responding to may have been destroyed. messageManager is null then.
- if (!Cu.isDeadWrapper(msg.target) && msg.target.messageManager) {
- msg.target.messageManager.sendAsyncMessage(...this._msgArgs(type, data));
- }
- },
-
- _broadcast: function (type, data) {
- Cc["@mozilla.org/globalmessagemanager;1"].
- getService(Ci.nsIMessageListenerManager).
- broadcastAsyncMessage(...this._msgArgs(type, data));
- },
-
- _msgArgs: function (type, data) {
- return [OUTBOUND_MESSAGE, {
- type: type,
- data: data,
- }];
- },
-
- _currentEngineObj: Task.async(function* () {
- let engine = Services.search.currentEngine;
- let favicon = engine.getIconURLBySize(16, 16);
- let placeholder = this._stringBundle.formatStringFromName(
- "searchWithEngine", [engine.name], 1);
- let obj = {
- name: engine.name,
- placeholder: placeholder,
- iconBuffer: yield this._arrayBufferFromDataURI(favicon),
- };
- return obj;
- }),
-
- _arrayBufferFromDataURI: function (uri) {
- if (!uri) {
- return Promise.resolve(null);
- }
- let deferred = Promise.defer();
- let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
- createInstance(Ci.nsIXMLHttpRequest);
- xhr.open("GET", uri, true);
- xhr.responseType = "arraybuffer";
- xhr.onload = () => {
- deferred.resolve(xhr.response);
- };
- xhr.onerror = xhr.onabort = xhr.ontimeout = () => {
- deferred.resolve(null);
- };
- try {
- // This throws if the URI is erroneously encoded.
- xhr.send();
- }
- catch (err) {
- return Promise.resolve(null);
- }
- return deferred.promise;
- },
-
- _ensureDataHasProperties: function (data, requiredProperties) {
- for (let prop of requiredProperties) {
- if (!(prop in data)) {
- throw new Error("Message data missing required property: " + prop);
- }
- }
- },
-
- _initService: function () {
- if (!this._initServicePromise) {
- let deferred = Promise.defer();
- this._initServicePromise = deferred.promise;
- Services.search.init(() => deferred.resolve());
- }
- return this._initServicePromise;
- },
-};
diff --git a/browser/modules/ContentWebRTC.jsm b/browser/modules/ContentWebRTC.jsm
deleted file mode 100644
index fd50176a0..000000000
--- a/browser/modules/ContentWebRTC.jsm
+++ /dev/null
@@ -1,393 +0,0 @@
-/* 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";
-
-const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-this.EXPORTED_SYMBOLS = [ "ContentWebRTC" ];
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
- "@mozilla.org/mediaManagerService;1",
- "nsIMediaManagerService");
-
-const kBrowserURL = "chrome://browser/content/browser.xul";
-
-this.ContentWebRTC = {
- _initialized: false,
-
- init: function() {
- if (this._initialized)
- return;
-
- this._initialized = true;
- Services.obs.addObserver(handleGUMRequest, "getUserMedia:request", false);
- Services.obs.addObserver(handlePCRequest, "PeerConnection:request", false);
- Services.obs.addObserver(updateIndicators, "recording-device-events", false);
- Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
-
- if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT)
- Services.obs.addObserver(processShutdown, "content-child-shutdown", false);
- },
-
- uninit: function() {
- Services.obs.removeObserver(handleGUMRequest, "getUserMedia:request");
- Services.obs.removeObserver(handlePCRequest, "PeerConnection:request");
- Services.obs.removeObserver(updateIndicators, "recording-device-events");
- Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
-
- if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT)
- Services.obs.removeObserver(processShutdown, "content-child-shutdown");
-
- this._initialized = false;
- },
-
- // Called only for 'unload' to remove pending gUM prompts in reloaded frames.
- handleEvent: function(aEvent) {
- let contentWindow = aEvent.target.defaultView;
- let mm = getMessageManagerForWindow(contentWindow);
- for (let key of contentWindow.pendingGetUserMediaRequests.keys()) {
- mm.sendAsyncMessage("webrtc:CancelRequest", key);
- }
- for (let key of contentWindow.pendingPeerConnectionRequests.keys()) {
- mm.sendAsyncMessage("rtcpeer:CancelRequest", key);
- }
- },
-
- receiveMessage: function(aMessage) {
- switch (aMessage.name) {
- case "rtcpeer:Allow":
- case "rtcpeer:Deny": {
- let callID = aMessage.data.callID;
- let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
- forgetPCRequest(contentWindow, callID);
- let topic = (aMessage.name == "rtcpeer:Allow") ? "PeerConnection:response:allow" :
- "PeerConnection:response:deny";
- Services.obs.notifyObservers(null, topic, callID);
- break;
- }
- case "webrtc:Allow": {
- let callID = aMessage.data.callID;
- let contentWindow = Services.wm.getOuterWindowWithId(aMessage.data.windowID);
- let devices = contentWindow.pendingGetUserMediaRequests.get(callID);
- forgetGUMRequest(contentWindow, callID);
-
- let allowedDevices = Cc["@mozilla.org/array;1"]
- .createInstance(Ci.nsIMutableArray);
- for (let deviceIndex of aMessage.data.devices)
- allowedDevices.appendElement(devices[deviceIndex], /* weak =*/ false);
-
- Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", callID);
- break;
- }
- case "webrtc:Deny":
- denyGUMRequest(aMessage.data);
- break;
- case "webrtc:StopSharing":
- Services.obs.notifyObservers(null, "getUserMedia:revoke", aMessage.data);
- break;
- }
- }
-};
-
-function handlePCRequest(aSubject, aTopic, aData) {
- let { windowID, innerWindowID, callID, isSecure } = aSubject;
- let contentWindow = Services.wm.getOuterWindowWithId(windowID);
-
- let mm = getMessageManagerForWindow(contentWindow);
- if (!mm) {
- // Workaround for Bug 1207784. To use WebRTC, add-ons right now use
- // hiddenWindow.mozRTCPeerConnection which is only privileged on OSX. Other
- // platforms end up here without a message manager.
- // TODO: Remove once there's a better way (1215591).
-
- // Skip permission check in the absence of a message manager.
- Services.obs.notifyObservers(null, "PeerConnection:response:allow", callID);
- return;
- }
-
- if (!contentWindow.pendingPeerConnectionRequests) {
- setupPendingListsInitially(contentWindow);
- }
- contentWindow.pendingPeerConnectionRequests.add(callID);
-
- let request = {
- windowID: windowID,
- innerWindowID: innerWindowID,
- callID: callID,
- documentURI: contentWindow.document.documentURI,
- secure: isSecure,
- };
- mm.sendAsyncMessage("rtcpeer:Request", request);
-}
-
-function handleGUMRequest(aSubject, aTopic, aData) {
- let constraints = aSubject.getConstraints();
- let secure = aSubject.isSecure;
- let contentWindow = Services.wm.getOuterWindowWithId(aSubject.windowID);
-
- contentWindow.navigator.mozGetUserMediaDevices(
- constraints,
- function (devices) {
- // If the window has been closed while we were waiting for the list of
- // devices, there's nothing to do in the callback anymore.
- if (contentWindow.closed)
- return;
-
- prompt(contentWindow, aSubject.windowID, aSubject.callID,
- constraints, devices, secure);
- },
- function (error) {
- // bug 827146 -- In the future, the UI should catch NotFoundError
- // and allow the user to plug in a device, instead of immediately failing.
- denyGUMRequest({callID: aSubject.callID}, error);
- },
- aSubject.innerWindowID,
- aSubject.callID);
-}
-
-function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {
- let audioDevices = [];
- let videoDevices = [];
- let devices = [];
-
- // MediaStreamConstraints defines video as 'boolean or MediaTrackConstraints'.
- let video = aConstraints.video || aConstraints.picture;
- let audio = aConstraints.audio;
- let sharingScreen = video && typeof(video) != "boolean" &&
- video.mediaSource != "camera";
- let sharingAudio = audio && typeof(audio) != "boolean" &&
- audio.mediaSource != "microphone";
- for (let device of aDevices) {
- device = device.QueryInterface(Ci.nsIMediaDevice);
- switch (device.type) {
- case "audio":
- // Check that if we got a microphone, we have not requested an audio
- // capture, and if we have requested an audio capture, we are not
- // getting a microphone instead.
- if (audio && (device.mediaSource == "microphone") != sharingAudio) {
- audioDevices.push({name: device.name, deviceIndex: devices.length,
- id: device.rawId, mediaSource: device.mediaSource});
- devices.push(device);
- }
- break;
- case "video":
- // Verify that if we got a camera, we haven't requested a screen share,
- // or that if we requested a screen share we aren't getting a camera.
- if (video && (device.mediaSource == "camera") != sharingScreen) {
- let deviceObject = {name: device.name, deviceIndex: devices.length,
- id: device.rawId, mediaSource: device.mediaSource};
- if (device.scary)
- deviceObject.scary = true;
- videoDevices.push(deviceObject);
- devices.push(device);
- }
- break;
- }
- }
-
- let requestTypes = [];
- if (videoDevices.length)
- requestTypes.push(sharingScreen ? "Screen" : "Camera");
- if (audioDevices.length)
- requestTypes.push(sharingAudio ? "AudioCapture" : "Microphone");
-
- if (!requestTypes.length) {
- denyGUMRequest({callID: aCallID}, "NotFoundError");
- return;
- }
-
- if (!aContentWindow.pendingGetUserMediaRequests) {
- setupPendingListsInitially(aContentWindow);
- }
- aContentWindow.pendingGetUserMediaRequests.set(aCallID, devices);
-
- let request = {
- callID: aCallID,
- windowID: aWindowID,
- origin: aContentWindow.origin,
- documentURI: aContentWindow.document.documentURI,
- secure: aSecure,
- requestTypes: requestTypes,
- sharingScreen: sharingScreen,
- sharingAudio: sharingAudio,
- audioDevices: audioDevices,
- videoDevices: videoDevices
- };
-
- let mm = getMessageManagerForWindow(aContentWindow);
- mm.sendAsyncMessage("webrtc:Request", request);
-}
-
-function denyGUMRequest(aData, aError) {
- let msg = null;
- if (aError) {
- msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
- msg.data = aError;
- }
- Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aData.callID);
-
- if (!aData.windowID)
- return;
- let contentWindow = Services.wm.getOuterWindowWithId(aData.windowID);
- if (contentWindow.pendingGetUserMediaRequests)
- forgetGUMRequest(contentWindow, aData.callID);
-}
-
-function forgetGUMRequest(aContentWindow, aCallID) {
- aContentWindow.pendingGetUserMediaRequests.delete(aCallID);
- forgetPendingListsEventually(aContentWindow);
-}
-
-function forgetPCRequest(aContentWindow, aCallID) {
- aContentWindow.pendingPeerConnectionRequests.delete(aCallID);
- forgetPendingListsEventually(aContentWindow);
-}
-
-function setupPendingListsInitially(aContentWindow) {
- if (aContentWindow.pendingGetUserMediaRequests) {
- return;
- }
- aContentWindow.pendingGetUserMediaRequests = new Map();
- aContentWindow.pendingPeerConnectionRequests = new Set();
- aContentWindow.addEventListener("unload", ContentWebRTC);
-}
-
-function forgetPendingListsEventually(aContentWindow) {
- if (aContentWindow.pendingGetUserMediaRequests.size ||
- aContentWindow.pendingPeerConnectionRequests.size) {
- return;
- }
- aContentWindow.pendingGetUserMediaRequests = null;
- aContentWindow.pendingPeerConnectionRequests = null;
- aContentWindow.removeEventListener("unload", ContentWebRTC);
-}
-
-function updateIndicators(aSubject, aTopic, aData) {
- if (aSubject instanceof Ci.nsIPropertyBag &&
- aSubject.getProperty("requestURL") == kBrowserURL) {
- // Ignore notifications caused by the browser UI showing previews.
- return;
- }
-
- let contentWindowArray = MediaManagerService.activeMediaCaptureWindows;
- let count = contentWindowArray.length;
-
- let state = {
- showGlobalIndicator: count > 0,
- showCameraIndicator: false,
- showMicrophoneIndicator: false,
- showScreenSharingIndicator: ""
- };
-
- let cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
- .getService(Ci.nsIMessageSender);
- cpmm.sendAsyncMessage("webrtc:UpdatingIndicators");
-
- // If several iframes in the same page use media streams, it's possible to
- // have the same top level window several times. We use a Set to avoid
- // sending duplicate notifications.
- let contentWindows = new Set();
- for (let i = 0; i < count; ++i) {
- contentWindows.add(contentWindowArray.queryElementAt(i, Ci.nsISupports).top);
- }
-
- for (let contentWindow of contentWindows) {
- if (contentWindow.document.documentURI == kBrowserURL) {
- // There may be a preview shown at the same time as other streams.
- continue;
- }
-
- let tabState = getTabStateForContentWindow(contentWindow);
- if (tabState.camera)
- state.showCameraIndicator = true;
- if (tabState.microphone)
- state.showMicrophoneIndicator = true;
- if (tabState.screen) {
- if (tabState.screen == "Screen") {
- state.showScreenSharingIndicator = "Screen";
- }
- else if (tabState.screen == "Window") {
- if (state.showScreenSharingIndicator != "Screen")
- state.showScreenSharingIndicator = "Window";
- }
- else if (tabState.screen == "Application") {
- if (!state.showScreenSharingIndicator)
- state.showScreenSharingIndicator = "Application";
- }
- else if (tabState.screen == "Browser") {
- if (!state.showScreenSharingIndicator)
- state.showScreenSharingIndicator = "Browser";
- }
- }
- let mm = getMessageManagerForWindow(contentWindow);
- mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
- }
-
- cpmm.sendAsyncMessage("webrtc:UpdateGlobalIndicators", state);
-}
-
-function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
- let contentWindow = Services.wm.getOuterWindowWithId(aData).top;
- if (contentWindow.document.documentURI == kBrowserURL) {
- // Ignore notifications caused by the browser UI showing previews.
- return;
- }
-
- let tabState = getTabStateForContentWindow(contentWindow);
- if (!tabState.camera && !tabState.microphone && !tabState.screen)
- tabState = {windowId: tabState.windowId};
-
- let mm = getMessageManagerForWindow(contentWindow);
- if (mm)
- mm.sendAsyncMessage("webrtc:UpdateBrowserIndicators", tabState);
-}
-
-function getTabStateForContentWindow(aContentWindow) {
- let camera = {}, microphone = {}, screen = {}, window = {}, app = {}, browser = {};
- MediaManagerService.mediaCaptureWindowState(aContentWindow, camera, microphone,
- screen, window, app, browser);
- let tabState = {camera: camera.value, microphone: microphone.value};
- if (screen.value)
- tabState.screen = "Screen";
- else if (window.value)
- tabState.screen = "Window";
- else if (app.value)
- tabState.screen = "Application";
- else if (browser.value)
- tabState.screen = "Browser";
-
- tabState.windowId = getInnerWindowIDForWindow(aContentWindow);
- tabState.documentURI = aContentWindow.document.documentURI;
-
- return tabState;
-}
-
-function getInnerWindowIDForWindow(aContentWindow) {
- return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .currentInnerWindowID;
-}
-
-function getMessageManagerForWindow(aContentWindow) {
- let ir = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .sameTypeRootTreeItem
- .QueryInterface(Ci.nsIInterfaceRequestor);
- try {
- // If e10s is disabled, this throws NS_NOINTERFACE for closed tabs.
- return ir.getInterface(Ci.nsIContentFrameMessageManager);
- } catch (e) {
- if (e.result == Cr.NS_NOINTERFACE) {
- return null;
- }
- throw e;
- }
-}
-
-function processShutdown() {
- ContentWebRTC.uninit();
-}
diff --git a/browser/modules/DirectoryLinksProvider.jsm b/browser/modules/DirectoryLinksProvider.jsm
deleted file mode 100644
index 117564099..000000000
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ /dev/null
@@ -1,1255 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["DirectoryLinksProvider"];
-
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const Cu = Components.utils;
-const ParserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
-
-Cu.importGlobalProperties(["XMLHttpRequest"]);
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
- "resource://gre/modules/NewTabUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "OS",
- "resource://gre/modules/osfile.jsm")
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
- "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
- "resource://gre/modules/UpdateUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "eTLD",
- "@mozilla.org/network/effective-tld-service;1",
- "nsIEffectiveTLDService");
-XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
- return new TextDecoder();
-});
-XPCOMUtils.defineLazyGetter(this, "gCryptoHash", function () {
- return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
-});
-XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = 'utf8';
- return converter;
-});
-
-
-// The filename where directory links are stored locally
-const DIRECTORY_LINKS_FILE = "directoryLinks.json";
-const DIRECTORY_LINKS_TYPE = "application/json";
-
-// The preference that tells whether to match the OS locale
-const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
-
-// The preference that tells what locale the user selected
-const PREF_SELECTED_LOCALE = "general.useragent.locale";
-
-// The preference that tells where to obtain directory links
-const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
-
-// The preference that tells where to send click/view pings
-const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
-
-// The preference that tells if newtab is enhanced
-const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
-
-// Only allow link urls that are http(s)
-const ALLOWED_LINK_SCHEMES = new Set(["http", "https"]);
-
-// Only allow link image urls that are https or data
-const ALLOWED_IMAGE_SCHEMES = new Set(["https", "data"]);
-
-// Only allow urls to Mozilla's CDN or empty (for data URIs)
-const ALLOWED_URL_BASE = new Set(["mozilla.net", ""]);
-
-// The frecency of a directory link
-const DIRECTORY_FRECENCY = 1000;
-
-// The frecency of a suggested link
-const SUGGESTED_FRECENCY = Infinity;
-
-// The filename where frequency cap data stored locally
-const FREQUENCY_CAP_FILE = "frequencyCap.json";
-
-// Default settings for daily and total frequency caps
-const DEFAULT_DAILY_FREQUENCY_CAP = 3;
-const DEFAULT_TOTAL_FREQUENCY_CAP = 10;
-
-// Default timeDelta to prune unused frequency cap objects
-// currently set to 10 days in milliseconds
-const DEFAULT_PRUNE_TIME_DELTA = 10*24*60*60*1000;
-
-// The min number of visible (not blocked) history tiles to have before showing suggested tiles
-const MIN_VISIBLE_HISTORY_TILES = 8;
-
-// The max number of visible (not blocked) history tiles to test for inadjacency
-const MAX_VISIBLE_HISTORY_TILES = 15;
-
-// Allowed ping actions remotely stored as columns: case-insensitive [a-z0-9_]
-const PING_ACTIONS = ["block", "click", "pin", "sponsored", "sponsored_link", "unpin", "view"];
-
-// Location of inadjacent sites json
-const INADJACENCY_SOURCE = "chrome://browser/content/newtab/newTab.inadjacent.json";
-
-// Fake URL to keep track of last block of a suggested tile in the frequency cap object
-const FAKE_SUGGESTED_BLOCK_URL = "ignore://suggested_block";
-
-// Time before suggested tile is allowed to play again after block - default to 1 day
-const AFTER_SUGGESTED_BLOCK_DECAY_TIME = 24*60*60*1000;
-
-/**
- * Singleton that serves as the provider of directory links.
- * Directory links are a hard-coded set of links shown if a user's link
- * inventory is empty.
- */
-var DirectoryLinksProvider = {
-
- __linksURL: null,
-
- _observers: new Set(),
-
- // links download deferred, resolved upon download completion
- _downloadDeferred: null,
-
- // download default interval is 24 hours in milliseconds
- _downloadIntervalMS: 86400000,
-
- /**
- * A mapping from eTLD+1 to an enhanced link objects
- */
- _enhancedLinks: new Map(),
-
- /**
- * A mapping from site to a list of suggested link objects
- */
- _suggestedLinks: new Map(),
-
- /**
- * Frequency Cap object - maintains daily and total tile counts, and frequency cap settings
- */
- _frequencyCaps: {},
-
- /**
- * A set of top sites that we can provide suggested links for
- */
- _topSitesWithSuggestedLinks: new Set(),
-
- /**
- * lookup Set of inadjacent domains
- */
- _inadjacentSites: new Set(),
-
- /**
- * This flag is set if there is a suggested tile configured to avoid
- * inadjacent sites in new tab
- */
- _avoidInadjacentSites: false,
-
- /**
- * This flag is set if _avoidInadjacentSites is true and there is
- * an inadjacent site in the new tab
- */
- _newTabHasInadjacentSite: false,
-
- get _observedPrefs() {
- return Object.freeze({
- enhanced: PREF_NEWTAB_ENHANCED,
- linksURL: PREF_DIRECTORY_SOURCE,
- matchOSLocale: PREF_MATCH_OS_LOCALE,
- prefSelectedLocale: PREF_SELECTED_LOCALE,
- });
- },
-
- get _linksURL() {
- if (!this.__linksURL) {
- try {
- this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
- this.__linksURLModified = Services.prefs.prefHasUserValue(this._observedPrefs["linksURL"]);
- }
- catch (e) {
- Cu.reportError("Error fetching directory links url from prefs: " + e);
- }
- }
- return this.__linksURL;
- },
-
- /**
- * Gets the currently selected locale for display.
- * @return the selected locale or "en-US" if none is selected
- */
- get locale() {
- let matchOS;
- try {
- matchOS = Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE);
- }
- catch (e) {}
-
- if (matchOS) {
- return Services.locale.getLocaleComponentForUserAgent();
- }
-
- try {
- let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
- Ci.nsIPrefLocalizedString);
- if (locale) {
- return locale.data;
- }
- }
- catch (e) {}
-
- try {
- return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
- }
- catch (e) {}
-
- return "en-US";
- },
-
- /**
- * Set appropriate default ping behavior controlled by enhanced pref
- */
- _setDefaultEnhanced: function DirectoryLinksProvider_setDefaultEnhanced() {
- if (!Services.prefs.prefHasUserValue(PREF_NEWTAB_ENHANCED)) {
- let enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
- try {
- // Default to not enhanced if DNT is set to tell websites to not track
- if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled")) {
- enhanced = false;
- }
- }
- catch (ex) {}
- Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, enhanced);
- }
- },
-
- observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) {
- if (aTopic == "nsPref:changed") {
- switch (aData) {
- // Re-set the default in case the user clears the pref
- case this._observedPrefs.enhanced:
- this._setDefaultEnhanced();
- break;
-
- case this._observedPrefs.linksURL:
- delete this.__linksURL;
- // fallthrough
-
- // Force directory download on changes to fetch related prefs
- case this._observedPrefs.matchOSLocale:
- case this._observedPrefs.prefSelectedLocale:
- this._fetchAndCacheLinksIfNecessary(true);
- break;
- }
- }
- },
-
- _addPrefsObserver: function DirectoryLinksProvider_addObserver() {
- for (let pref in this._observedPrefs) {
- let prefName = this._observedPrefs[pref];
- Services.prefs.addObserver(prefName, this, false);
- }
- },
-
- _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
- for (let pref in this._observedPrefs) {
- let prefName = this._observedPrefs[pref];
- Services.prefs.removeObserver(prefName, this);
- }
- },
-
- _cacheSuggestedLinks: function(link) {
- // Don't cache links that don't have the expected 'frecent_sites'
- if (!link.frecent_sites) {
- return;
- }
-
- for (let suggestedSite of link.frecent_sites) {
- let suggestedMap = this._suggestedLinks.get(suggestedSite) || new Map();
- suggestedMap.set(link.url, link);
- this._setupStartEndTime(link);
- this._suggestedLinks.set(suggestedSite, suggestedMap);
- }
- },
-
- _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
- // Replace with the same display locale used for selecting links data
- uri = uri.replace("%LOCALE%", this.locale);
- uri = uri.replace("%CHANNEL%", UpdateUtils.UpdateChannel);
-
- return this._downloadJsonData(uri).then(json => {
- return OS.File.writeAtomic(this._directoryFilePath, json, {tmpPath: this._directoryFilePath + ".tmp"});
- });
- },
-
- /**
- * Downloads a links with json content
- * @param download uri
- * @return promise resolved to json string, "{}" returned if status != 200
- */
- _downloadJsonData: function DirectoryLinksProvider__downloadJsonData(uri) {
- let deferred = Promise.defer();
- let xmlHttp = this._newXHR();
-
- xmlHttp.onload = function(aResponse) {
- let json = this.responseText;
- if (this.status && this.status != 200) {
- json = "{}";
- }
- deferred.resolve(json);
- };
-
- xmlHttp.onerror = function(e) {
- deferred.reject("Fetching " + uri + " results in error code: " + e.target.status);
- };
-
- try {
- xmlHttp.open("GET", uri);
- // Override the type so XHR doesn't complain about not well-formed XML
- xmlHttp.overrideMimeType(DIRECTORY_LINKS_TYPE);
- // Set the appropriate request type for servers that require correct types
- xmlHttp.setRequestHeader("Content-Type", DIRECTORY_LINKS_TYPE);
- xmlHttp.send();
- } catch (e) {
- deferred.reject("Error fetching " + uri);
- Cu.reportError(e);
- }
- return deferred.promise;
- },
-
- /**
- * Downloads directory links if needed
- * @return promise resolved immediately if no download needed, or upon completion
- */
- _fetchAndCacheLinksIfNecessary: function DirectoryLinksProvider_fetchAndCacheLinksIfNecessary(forceDownload=false) {
- if (this._downloadDeferred) {
- // fetching links already - just return the promise
- return this._downloadDeferred.promise;
- }
-
- if (forceDownload || this._needsDownload) {
- this._downloadDeferred = Promise.defer();
- this._fetchAndCacheLinks(this._linksURL).then(() => {
- // the new file was successfully downloaded and cached, so update a timestamp
- this._lastDownloadMS = Date.now();
- this._downloadDeferred.resolve();
- this._downloadDeferred = null;
- this._callObservers("onManyLinksChanged")
- },
- error => {
- this._downloadDeferred.resolve();
- this._downloadDeferred = null;
- this._callObservers("onDownloadFail");
- });
- return this._downloadDeferred.promise;
- }
-
- // download is not needed
- return Promise.resolve();
- },
-
- /**
- * @return true if download is needed, false otherwise
- */
- get _needsDownload () {
- // fail if last download occured less then 24 hours ago
- if ((Date.now() - this._lastDownloadMS) > this._downloadIntervalMS) {
- return true;
- }
- return false;
- },
-
- /**
- * Create a new XMLHttpRequest that is anonymous, i.e., doesn't send cookies
- */
- _newXHR() {
- return new XMLHttpRequest({mozAnon: true});
- },
-
- /**
- * Reads directory links file and parses its content
- * @return a promise resolved to an object with keys 'directory' and 'suggested',
- * each containing a valid list of links,
- * or {'directory': [], 'suggested': []} if read or parse fails.
- */
- _readDirectoryLinksFile: function DirectoryLinksProvider_readDirectoryLinksFile() {
- let emptyOutput = {directory: [], suggested: [], enhanced: []};
- return OS.File.read(this._directoryFilePath).then(binaryData => {
- let output;
- try {
- let json = gTextDecoder.decode(binaryData);
- let linksObj = JSON.parse(json);
- output = {directory: linksObj.directory || [],
- suggested: linksObj.suggested || [],
- enhanced: linksObj.enhanced || []};
- }
- catch (e) {
- Cu.reportError(e);
- }
- return output || emptyOutput;
- },
- error => {
- Cu.reportError(error);
- return emptyOutput;
- });
- },
-
- /**
- * Translates link.time_limits to UTC miliseconds and sets
- * link.startTime and link.endTime properties in link object
- */
- _setupStartEndTime: function DirectoryLinksProvider_setupStartEndTime(link) {
- // set start/end limits. Use ISO_8601 format: '2014-01-10T20:20:20.600Z'
- // (details here http://en.wikipedia.org/wiki/ISO_8601)
- // Note that if timezone is missing, FX will interpret as local time
- // meaning that the server can sepecify any time, but if the capmaign
- // needs to start at same time across multiple timezones, the server
- // omits timezone indicator
- if (!link.time_limits) {
- return;
- }
-
- let parsedTime;
- if (link.time_limits.start) {
- parsedTime = Date.parse(link.time_limits.start);
- if (parsedTime && !isNaN(parsedTime)) {
- link.startTime = parsedTime;
- }
- }
- if (link.time_limits.end) {
- parsedTime = Date.parse(link.time_limits.end);
- if (parsedTime && !isNaN(parsedTime)) {
- link.endTime = parsedTime;
- }
- }
- },
-
- /*
- * Handles campaign timeout
- */
- _onCampaignTimeout: function DirectoryLinksProvider_onCampaignTimeout() {
- // _campaignTimeoutID is invalid here, so just set it to null
- this._campaignTimeoutID = null;
- this._updateSuggestedTile();
- },
-
- /*
- * Clears capmpaign timeout
- */
- _clearCampaignTimeout: function DirectoryLinksProvider_clearCampaignTimeout() {
- if (this._campaignTimeoutID) {
- clearTimeout(this._campaignTimeoutID);
- this._campaignTimeoutID = null;
- }
- },
-
- /**
- * Setup capmpaign timeout to recompute suggested tiles upon
- * reaching soonest start or end time for the campaign
- * @param timeout in milliseconds
- */
- _setupCampaignTimeCheck: function DirectoryLinksProvider_setupCampaignTimeCheck(timeout) {
- // sanity check
- if (!timeout || timeout <= 0) {
- return;
- }
- this._clearCampaignTimeout();
- // setup next timeout
- this._campaignTimeoutID = setTimeout(this._onCampaignTimeout.bind(this), timeout);
- },
-
- /**
- * Test link for campaign time limits: checks if link falls within start/end time
- * and returns an object containing a use flag and the timeoutDate milliseconds
- * when the link has to be re-checked for campaign start-ready or end-reach
- * @param link
- * @return object {use: true or false, timeoutDate: milliseconds or null}
- */
- _testLinkForCampaignTimeLimits: function DirectoryLinksProvider_testLinkForCampaignTimeLimits(link) {
- let currentTime = Date.now();
- // test for start time first
- if (link.startTime && link.startTime > currentTime) {
- // not yet ready for start
- return {use: false, timeoutDate: link.startTime};
- }
- // otherwise check for end time
- if (link.endTime) {
- // passed end time
- if (link.endTime <= currentTime) {
- return {use: false};
- }
- // otherwise link is still ok, but we need to set timeoutDate
- return {use: true, timeoutDate: link.endTime};
- }
- // if we are here, the link is ok and no timeoutDate needed
- return {use: true};
- },
-
- /**
- * Handles block on suggested tile: updates fake block url with current timestamp
- */
- handleSuggestedTileBlock: function DirectoryLinksProvider_handleSuggestedTileBlock() {
- this._updateFrequencyCapSettings({url: FAKE_SUGGESTED_BLOCK_URL});
- this._writeFrequencyCapFile();
- this._updateSuggestedTile();
- },
-
- /**
- * Checks if suggested tile is being blocked for the rest of "decay time"
- * @return True if blocked, false otherwise
- */
- _isSuggestedTileBlocked: function DirectoryLinksProvider__isSuggestedTileBlocked() {
- let capObject = this._frequencyCaps[FAKE_SUGGESTED_BLOCK_URL];
- if (!capObject || !capObject.lastUpdated) {
- // user never blocked suggested tile or lastUpdated is missing
- return false;
- }
- // otherwise, make sure that enough time passed after suggested tile was blocked
- return (capObject.lastUpdated + AFTER_SUGGESTED_BLOCK_DECAY_TIME) > Date.now();
- },
-
- /**
- * Report some action on a newtab page (view, click)
- * @param sites Array of sites shown on newtab page
- * @param action String of the behavior to report
- * @param triggeringSiteIndex optional Int index of the site triggering action
- * @return download promise
- */
- reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) {
- // Check if the suggested tile was shown
- if (action == "view") {
- sites.slice(0, triggeringSiteIndex + 1).filter(s => s).forEach(site => {
- let {targetedSite, url} = site.link;
- if (targetedSite) {
- this._addFrequencyCapView(url);
- }
- });
- }
- // any click action on a suggested tile should stop that tile suggestion
- // click/block - user either removed a tile or went to a landing page
- // pin - tile turned into history tile, should no longer be suggested
- // unpin - the tile was pinned before, should not matter
- else {
- // suggested tile has targetedSite, or frecent_sites if it was pinned
- let {frecent_sites, targetedSite, url} = sites[triggeringSiteIndex].link;
- if (frecent_sites || targetedSite) {
- this._setFrequencyCapClick(url);
- }
- }
-
- let newtabEnhanced = false;
- let pingEndPoint = "";
- try {
- newtabEnhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
- pingEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_PING);
- }
- catch (ex) {}
-
- // Bug 1240245 - We no longer send pings, but frequency capping and fetching
- // tests depend on the following actions, so references to PING remain.
- let invalidAction = PING_ACTIONS.indexOf(action) == -1;
- if (!newtabEnhanced || pingEndPoint == "" || invalidAction) {
- return Promise.resolve();
- }
-
- return Task.spawn(function* () {
- // since we updated views/clicks we need write _frequencyCaps to disk
- yield this._writeFrequencyCapFile();
- // Use this as an opportunity to potentially fetch new links
- yield this._fetchAndCacheLinksIfNecessary();
- }.bind(this));
- },
-
- /**
- * Get the enhanced link object for a link (whether history or directory)
- */
- getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
- // Use the provided link if it's already enhanced
- return link.enhancedImageURI && link ? link :
- this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
- },
-
- /**
- * Check if a url's scheme is in a Set of allowed schemes and if the base
- * domain is allowed.
- * @param url to check
- * @param allowed Set of allowed schemes
- * @param checkBase boolean to check the base domain
- */
- isURLAllowed(url, allowed, checkBase) {
- // Assume no url is an allowed url
- if (!url) {
- return true;
- }
-
- let scheme = "", base = "";
- try {
- // A malformed url will not be allowed
- let uri = Services.io.newURI(url, null, null);
- scheme = uri.scheme;
-
- // URIs without base domains will be allowed
- base = Services.eTLD.getBaseDomain(uri);
- }
- catch (ex) {}
- // Require a scheme match and the base only if desired
- return allowed.has(scheme) && (!checkBase || ALLOWED_URL_BASE.has(base));
- },
-
- _escapeChars(text) {
- let charMap = {
- '&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- '"': '&quot;',
- "'": '&#039;'
- };
-
- return text.replace(/[&<>"']/g, (character) => charMap[character]);
- },
-
- /**
- * Gets the current set of directory links.
- * @param aCallback The function that the array of links is passed to.
- */
- getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
- this._readDirectoryLinksFile().then(rawLinks => {
- // Reset the cache of suggested tiles and enhanced images for this new set of links
- this._enhancedLinks.clear();
- this._suggestedLinks.clear();
- this._clearCampaignTimeout();
- this._avoidInadjacentSites = false;
-
- // Only check base domain for images when using the default pref
- let checkBase = !this.__linksURLModified;
- let validityFilter = function(link) {
- // Make sure the link url is allowed and images too if they exist
- return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES, false) &&
- (!link.imageURI ||
- this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES, checkBase)) &&
- (!link.enhancedImageURI ||
- this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES, checkBase));
- }.bind(this);
-
- rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
- // Suggested sites must have an adgroup name.
- if (!link.adgroup_name) {
- return;
- }
-
- let sanitizeFlags = ParserUtils.SanitizerCidEmbedsOnly |
- ParserUtils.SanitizerDropForms |
- ParserUtils.SanitizerDropNonCSSPresentation;
-
- link.explanation = this._escapeChars(link.explanation ? ParserUtils.convertToPlainText(link.explanation, sanitizeFlags, 0) : "");
- link.targetedName = this._escapeChars(ParserUtils.convertToPlainText(link.adgroup_name, sanitizeFlags, 0));
- link.lastVisitDate = rawLinks.suggested.length - position;
- // check if link wants to avoid inadjacent sites
- if (link.check_inadjacency) {
- this._avoidInadjacentSites = true;
- }
-
- // We cache suggested tiles here but do not push any of them in the links list yet.
- // The decision for which suggested tile to include will be made separately.
- this._cacheSuggestedLinks(link);
- this._updateFrequencyCapSettings(link);
- });
-
- rawLinks.enhanced.filter(validityFilter).forEach((link, position) => {
- link.lastVisitDate = rawLinks.enhanced.length - position;
-
- // Stash the enhanced image for the site
- if (link.enhancedImageURI) {
- this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link);
- }
- });
-
- let links = rawLinks.directory.filter(validityFilter).map((link, position) => {
- link.lastVisitDate = rawLinks.directory.length - position;
- link.frecency = DIRECTORY_FRECENCY;
- return link;
- });
-
- // Allow for one link suggestion on top of the default directory links
- this.maxNumLinks = links.length + 1;
-
- // prune frequency caps of outdated urls
- this._pruneFrequencyCapUrls();
- // write frequency caps object to disk asynchronously
- this._writeFrequencyCapFile();
-
- return links;
- }).catch(ex => {
- Cu.reportError(ex);
- return [];
- }).then(links => {
- aCallback(links);
- this._populatePlacesLinks();
- });
- },
-
- init: function DirectoryLinksProvider_init() {
- this._setDefaultEnhanced();
- this._addPrefsObserver();
- // setup directory file path and last download timestamp
- this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
- this._lastDownloadMS = 0;
-
- // setup frequency cap file path
- this._frequencyCapFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, FREQUENCY_CAP_FILE);
- // setup inadjacent sites URL
- this._inadjacentSitesUrl = INADJACENCY_SOURCE;
-
- NewTabUtils.placesProvider.addObserver(this);
- NewTabUtils.links.addObserver(this);
-
- return Task.spawn(function*() {
- // get the last modified time of the links file if it exists
- let doesFileExists = yield OS.File.exists(this._directoryFilePath);
- if (doesFileExists) {
- let fileInfo = yield OS.File.stat(this._directoryFilePath);
- this._lastDownloadMS = Date.parse(fileInfo.lastModificationDate);
- }
- // read frequency cap file
- yield this._readFrequencyCapFile();
- // fetch directory on startup without force
- yield this._fetchAndCacheLinksIfNecessary();
- // fecth inadjacent sites on startup
- yield this._loadInadjacentSites();
- }.bind(this));
- },
-
- _handleManyLinksChanged: function() {
- this._topSitesWithSuggestedLinks.clear();
- this._suggestedLinks.forEach((suggestedLinks, site) => {
- if (NewTabUtils.isTopPlacesSite(site)) {
- this._topSitesWithSuggestedLinks.add(site);
- }
- });
- this._updateSuggestedTile();
- },
-
- /**
- * Updates _topSitesWithSuggestedLinks based on the link that was changed.
- *
- * @return true if _topSitesWithSuggestedLinks was modified, false otherwise.
- */
- _handleLinkChanged: function(aLink) {
- let changedLinkSite = NewTabUtils.extractSite(aLink.url);
- let linkStored = this._topSitesWithSuggestedLinks.has(changedLinkSite);
-
- if (!NewTabUtils.isTopPlacesSite(changedLinkSite) && linkStored) {
- this._topSitesWithSuggestedLinks.delete(changedLinkSite);
- return true;
- }
-
- if (this._suggestedLinks.has(changedLinkSite) &&
- NewTabUtils.isTopPlacesSite(changedLinkSite) && !linkStored) {
- this._topSitesWithSuggestedLinks.add(changedLinkSite);
- return true;
- }
-
- // always run _updateSuggestedTile if aLink is inadjacent
- // and there are tiles configured to avoid it
- if (this._avoidInadjacentSites && this._isInadjacentLink(aLink)) {
- return true;
- }
-
- return false;
- },
-
- _populatePlacesLinks: function () {
- NewTabUtils.links.populateProviderCache(NewTabUtils.placesProvider, () => {
- this._handleManyLinksChanged();
- });
- },
-
- onDeleteURI: function(aProvider, aLink) {
- let {url} = aLink;
- // remove clicked flag for that url and
- // call observer upon disk write completion
- this._removeTileClick(url).then(() => {
- this._callObservers("onDeleteURI", url);
- });
- },
-
- onClearHistory: function() {
- // remove all clicked flags and call observers upon file write
- this._removeAllTileClicks().then(() => {
- this._callObservers("onClearHistory");
- });
- },
-
- onLinkChanged: function (aProvider, aLink) {
- // Make sure NewTabUtils.links handles the notification first.
- setTimeout(() => {
- if (this._handleLinkChanged(aLink) || this._shouldUpdateSuggestedTile()) {
- this._updateSuggestedTile();
- }
- }, 0);
- },
-
- onManyLinksChanged: function () {
- // Make sure NewTabUtils.links handles the notification first.
- setTimeout(() => {
- this._handleManyLinksChanged();
- }, 0);
- },
-
- _getCurrentTopSiteCount: function() {
- let visibleTopSiteCount = 0;
- let newTabLinks = NewTabUtils.links.getLinks();
- for (let link of newTabLinks.slice(0, MIN_VISIBLE_HISTORY_TILES)) {
- // compute visibleTopSiteCount for suggested tiles
- if (link && (link.type == "history" || link.type == "enhanced")) {
- visibleTopSiteCount++;
- }
- }
- // since newTabLinks are available, set _newTabHasInadjacentSite here
- // note that _shouldUpdateSuggestedTile is called by _updateSuggestedTile
- this._newTabHasInadjacentSite = this._avoidInadjacentSites && this._checkForInadjacentSites(newTabLinks);
-
- return visibleTopSiteCount;
- },
-
- _shouldUpdateSuggestedTile: function() {
- let sortedLinks = NewTabUtils.getProviderLinks(this);
-
- let mostFrecentLink = {};
- if (sortedLinks && sortedLinks.length) {
- mostFrecentLink = sortedLinks[0]
- }
-
- let currTopSiteCount = this._getCurrentTopSiteCount();
- if ((!mostFrecentLink.targetedSite && currTopSiteCount >= MIN_VISIBLE_HISTORY_TILES) ||
- (mostFrecentLink.targetedSite && currTopSiteCount < MIN_VISIBLE_HISTORY_TILES)) {
- // If mostFrecentLink has a targetedSite then mostFrecentLink is a suggested link.
- // If we have enough history links (8+) to show a suggested tile and we are not
- // already showing one, then we should update (to *attempt* to add a suggested tile).
- // OR if we don't have enough history to show a suggested tile (<8) and we are
- // currently showing one, we should update (to remove it).
- return true;
- }
-
- return false;
- },
-
- /**
- * Chooses and returns a suggested tile based on a user's top sites
- * that we have an available suggested tile for.
- *
- * @return the chosen suggested tile, or undefined if there isn't one
- */
- _updateSuggestedTile: function() {
- let sortedLinks = NewTabUtils.getProviderLinks(this);
-
- if (!sortedLinks) {
- // If NewTabUtils.links.resetCache() is called before getting here,
- // sortedLinks may be undefined.
- return undefined;
- }
-
- // Delete the current suggested tile, if one exists.
- let initialLength = sortedLinks.length;
- if (initialLength) {
- let mostFrecentLink = sortedLinks[0];
- if (mostFrecentLink.targetedSite) {
- this._callObservers("onLinkChanged", {
- url: mostFrecentLink.url,
- frecency: SUGGESTED_FRECENCY,
- lastVisitDate: mostFrecentLink.lastVisitDate,
- type: mostFrecentLink.type,
- }, 0, true);
- }
- }
-
- if (this._topSitesWithSuggestedLinks.size == 0 ||
- !this._shouldUpdateSuggestedTile() ||
- this._isSuggestedTileBlocked()) {
- // There are no potential suggested links we can show or not
- // enough history for a suggested tile, or suggested tile was
- // recently blocked and wait time interval has not decayed yet
- return undefined;
- }
-
- // Create a flat list of all possible links we can show as suggested.
- // Note that many top sites may map to the same suggested links, but we only
- // want to count each suggested link once (based on url), thus possibleLinks is a map
- // from url to suggestedLink. Thus, each link has an equal chance of being chosen at
- // random from flattenedLinks if it appears only once.
- let nextTimeout;
- let possibleLinks = new Map();
- let targetedSites = new Map();
- this._topSitesWithSuggestedLinks.forEach(topSiteWithSuggestedLink => {
- let suggestedLinksMap = this._suggestedLinks.get(topSiteWithSuggestedLink);
- suggestedLinksMap.forEach((suggestedLink, url) => {
- // Skip this link if we've shown it too many times already
- if (!this._testFrequencyCapLimits(url)) {
- return;
- }
-
- // as we iterate suggestedLinks, check for campaign start/end
- // time limits, and set nextTimeout to the closest timestamp
- let {use, timeoutDate} = this._testLinkForCampaignTimeLimits(suggestedLink);
- // update nextTimeout is necessary
- if (timeoutDate && (!nextTimeout || nextTimeout > timeoutDate)) {
- nextTimeout = timeoutDate;
- }
- // Skip link if it falls outside campaign time limits
- if (!use) {
- return;
- }
-
- // Skip link if it avoids inadjacent sites and newtab has one
- if (suggestedLink.check_inadjacency && this._newTabHasInadjacentSite) {
- return;
- }
-
- possibleLinks.set(url, suggestedLink);
-
- // Keep a map of URL to targeted sites. We later use this to show the user
- // what site they visited to trigger this suggestion.
- if (!targetedSites.get(url)) {
- targetedSites.set(url, []);
- }
- targetedSites.get(url).push(topSiteWithSuggestedLink);
- })
- });
-
- // setup timeout check for starting or ending campaigns
- if (nextTimeout) {
- this._setupCampaignTimeCheck(nextTimeout - Date.now());
- }
-
- // We might have run out of possible links to show
- let numLinks = possibleLinks.size;
- if (numLinks == 0) {
- return undefined;
- }
-
- let flattenedLinks = [...possibleLinks.values()];
-
- // Choose our suggested link at random
- let suggestedIndex = Math.floor(Math.random() * numLinks);
- let chosenSuggestedLink = flattenedLinks[suggestedIndex];
-
- // Add the suggested link to the front with some extra values
- this._callObservers("onLinkChanged", Object.assign({
- frecency: SUGGESTED_FRECENCY,
-
- // Choose the first site a user has visited as the target. In the future,
- // this should be the site with the highest frecency. However, we currently
- // store frecency by URL not by site.
- targetedSite: targetedSites.get(chosenSuggestedLink.url).length ?
- targetedSites.get(chosenSuggestedLink.url)[0] : null
- }, chosenSuggestedLink));
- return chosenSuggestedLink;
- },
-
- /**
- * Loads inadjacent sites
- * @return a promise resolved when lookup Set for sites is built
- */
- _loadInadjacentSites: function DirectoryLinksProvider_loadInadjacentSites() {
- return this._downloadJsonData(this._inadjacentSitesUrl).then(jsonString => {
- let jsonObject = {};
- try {
- jsonObject = JSON.parse(jsonString);
- }
- catch (e) {
- Cu.reportError(e);
- }
-
- this._inadjacentSites = new Set(jsonObject.domains);
- });
- },
-
- /**
- * Genegrates hash suitable for looking up inadjacent site
- * @param value to hsh
- * @return hased value, base64-ed
- */
- _generateHash: function DirectoryLinksProvider_generateHash(value) {
- let byteArr = gUnicodeConverter.convertToByteArray(value);
- gCryptoHash.init(gCryptoHash.MD5);
- gCryptoHash.update(byteArr, byteArr.length);
- return gCryptoHash.finish(true);
- },
-
- /**
- * Checks if link belongs to inadjacent domain
- * @param link to check
- * @return true for inadjacent domains, false otherwise
- */
- _isInadjacentLink: function DirectoryLinksProvider_isInadjacentLink(link) {
- let baseDomain = link.baseDomain || NewTabUtils.extractSite(link.url || "");
- if (!baseDomain) {
- return false;
- }
- // check if hashed domain is inadjacent
- return this._inadjacentSites.has(this._generateHash(baseDomain));
- },
-
- /**
- * Checks if new tab has inadjacent site
- * @param new tab links (or nothing, in which case NewTabUtils.links.getLinks() is called
- * @return true if new tab shows has inadjacent site
- */
- _checkForInadjacentSites: function DirectoryLinksProvider_checkForInadjacentSites(newTabLink) {
- let links = newTabLink || NewTabUtils.links.getLinks();
- for (let link of links.slice(0, MAX_VISIBLE_HISTORY_TILES)) {
- // check links against inadjacent list - specifically include ALL link types
- if (this._isInadjacentLink(link)) {
- return true;
- }
- }
- return false;
- },
-
- /**
- * Reads json file, parses its content, and returns resulting object
- * @param json file path
- * @param json object to return in case file read or parse fails
- * @return a promise resolved to a valid object or undefined upon error
- */
- _readJsonFile: Task.async(function* (filePath, nullObject) {
- let jsonObj;
- try {
- let binaryData = yield OS.File.read(filePath);
- let json = gTextDecoder.decode(binaryData);
- jsonObj = JSON.parse(json);
- }
- catch (e) {}
- return jsonObj || nullObject;
- }),
-
- /**
- * Loads frequency cap object from file and parses its content
- * @return a promise resolved upon load completion
- * on error or non-exstent file _frequencyCaps is set to empty object
- */
- _readFrequencyCapFile: Task.async(function* () {
- // set _frequencyCaps object to file's content or empty object
- this._frequencyCaps = yield this._readJsonFile(this._frequencyCapFilePath, {});
- }),
-
- /**
- * Saves frequency cap object to file
- * @return a promise resolved upon file i/o completion
- */
- _writeFrequencyCapFile: function DirectoryLinksProvider_writeFrequencyCapFile() {
- let json = JSON.stringify(this._frequencyCaps || {});
- return OS.File.writeAtomic(this._frequencyCapFilePath, json, {tmpPath: this._frequencyCapFilePath + ".tmp"});
- },
-
- /**
- * Clears frequency cap object and writes empty json to file
- * @return a promise resolved upon file i/o completion
- */
- _clearFrequencyCap: function DirectoryLinksProvider_clearFrequencyCap() {
- this._frequencyCaps = {};
- return this._writeFrequencyCapFile();
- },
-
- /**
- * updates frequency cap configuration for a link
- */
- _updateFrequencyCapSettings: function DirectoryLinksProvider_updateFrequencyCapSettings(link) {
- let capsObject = this._frequencyCaps[link.url];
- if (!capsObject) {
- // create an object with empty counts
- capsObject = {
- dailyViews: 0,
- totalViews: 0,
- lastShownDate: 0,
- };
- this._frequencyCaps[link.url] = capsObject;
- }
- // set last updated timestamp
- capsObject.lastUpdated = Date.now();
- // check for link configuration
- if (link.frequency_caps) {
- capsObject.dailyCap = link.frequency_caps.daily || DEFAULT_DAILY_FREQUENCY_CAP;
- capsObject.totalCap = link.frequency_caps.total || DEFAULT_TOTAL_FREQUENCY_CAP;
- }
- else {
- // fallback to defaults
- capsObject.dailyCap = DEFAULT_DAILY_FREQUENCY_CAP;
- capsObject.totalCap = DEFAULT_TOTAL_FREQUENCY_CAP;
- }
- },
-
- /**
- * Prunes frequency cap objects for outdated links
- * @param timeDetla milliseconds
- * all cap objects with lastUpdated less than (now() - timeDelta)
- * will be removed. This is done to remove frequency cap objects
- * for unused tile urls
- */
- _pruneFrequencyCapUrls: function DirectoryLinksProvider_pruneFrequencyCapUrls(timeDelta = DEFAULT_PRUNE_TIME_DELTA) {
- let timeThreshold = Date.now() - timeDelta;
- Object.keys(this._frequencyCaps).forEach(url => {
- // remove url if it is not ignorable and wasn't updated for a while
- if (!url.startsWith("ignore") && this._frequencyCaps[url].lastUpdated <= timeThreshold) {
- delete this._frequencyCaps[url];
- }
- });
- },
-
- /**
- * Checks if supplied timestamp happened today
- * @param timestamp in milliseconds
- * @return true if the timestamp was made today, false otherwise
- */
- _wasToday: function DirectoryLinksProvider_wasToday(timestamp) {
- let showOn = new Date(timestamp);
- let today = new Date();
- // call timestamps identical if both day and month are same
- return showOn.getDate() == today.getDate() &&
- showOn.getMonth() == today.getMonth() &&
- showOn.getYear() == today.getYear();
- },
-
- /**
- * adds some number of views for a url
- * @param url String url of the suggested link
- */
- _addFrequencyCapView: function DirectoryLinksProvider_addFrequencyCapView(url) {
- let capObject = this._frequencyCaps[url];
- // sanity check
- if (!capObject) {
- return;
- }
-
- // if the day is new: reset the daily counter and lastShownDate
- if (!this._wasToday(capObject.lastShownDate)) {
- capObject.dailyViews = 0;
- // update lastShownDate
- capObject.lastShownDate = Date.now();
- }
-
- // bump both daily and total counters
- capObject.totalViews++;
- capObject.dailyViews++;
-
- // if any of the caps is reached - update suggested tiles
- if (capObject.totalViews >= capObject.totalCap ||
- capObject.dailyViews >= capObject.dailyCap) {
- this._updateSuggestedTile();
- }
- },
-
- /**
- * Sets clicked flag for link url
- * @param url String url of the suggested link
- */
- _setFrequencyCapClick(url) {
- let capObject = this._frequencyCaps[url];
- // sanity check
- if (!capObject) {
- return;
- }
- capObject.clicked = true;
- // and update suggested tiles, since current tile became invalid
- this._updateSuggestedTile();
- },
-
- /**
- * Tests frequency cap limits for link url
- * @param url String url of the suggested link
- * @return true if link is viewable, false otherwise
- */
- _testFrequencyCapLimits: function DirectoryLinksProvider_testFrequencyCapLimits(url) {
- let capObject = this._frequencyCaps[url];
- // sanity check: if url is missing - do not show this tile
- if (!capObject) {
- return false;
- }
-
- // check for clicked set or total views reached
- if (capObject.clicked || capObject.totalViews >= capObject.totalCap) {
- return false;
- }
-
- // otherwise check if link is over daily views limit
- if (this._wasToday(capObject.lastShownDate) &&
- capObject.dailyViews >= capObject.dailyCap) {
- return false;
- }
-
- // we passed all cap tests: return true
- return true;
- },
-
- /**
- * Removes clicked flag from frequency cap entry for tile landing url
- * @param url String url of the suggested link
- * @return promise resolved upon disk write completion
- */
- _removeTileClick: function DirectoryLinksProvider_removeTileClick(url = "") {
- // remove trailing slash, to accomodate Places sending site urls ending with '/'
- let noTrailingSlashUrl = url.replace(/\/$/, "");
- let capObject = this._frequencyCaps[url] || this._frequencyCaps[noTrailingSlashUrl];
- // return resolved promise if capObject is not found
- if (!capObject) {
- return Promise.resolve();
- }
- // otherwise remove clicked flag
- delete capObject.clicked;
- return this._writeFrequencyCapFile();
- },
-
- /**
- * Removes all clicked flags from frequency cap object
- * @return promise resolved upon disk write completion
- */
- _removeAllTileClicks: function DirectoryLinksProvider_removeAllTileClicks() {
- Object.keys(this._frequencyCaps).forEach(url => {
- delete this._frequencyCaps[url].clicked;
- });
- return this._writeFrequencyCapFile();
- },
-
- /**
- * Return the object to its pre-init state
- */
- reset: function DirectoryLinksProvider_reset() {
- delete this.__linksURL;
- this._removePrefsObserver();
- this._removeObservers();
- },
-
- addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
- this._observers.add(aObserver);
- },
-
- removeObserver: function DirectoryLinksProvider_removeObserver(aObserver) {
- this._observers.delete(aObserver);
- },
-
- _callObservers(methodName, ...args) {
- for (let obs of this._observers) {
- if (typeof(obs[methodName]) == "function") {
- try {
- obs[methodName](this, ...args);
- } catch (err) {
- Cu.reportError(err);
- }
- }
- }
- },
-
- _removeObservers: function() {
- this._observers.clear();
- }
-};
diff --git a/browser/modules/E10SUtils.jsm b/browser/modules/E10SUtils.jsm
deleted file mode 100644
index 7ed51ee96..000000000
--- a/browser/modules/E10SUtils.jsm
+++ /dev/null
@@ -1,128 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["E10SUtils"];
-
-const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-function getAboutModule(aURL) {
- // Needs to match NS_GetAboutModuleName
- let moduleName = aURL.path.replace(/[#?].*/, "").toLowerCase();
- let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
- try {
- return Cc[contract].getService(Ci.nsIAboutModule);
- }
- catch (e) {
- // Either the about module isn't defined or it is broken. In either case
- // ignore it.
- return null;
- }
-}
-
-this.E10SUtils = {
- canLoadURIInProcess: function(aURL, aProcess) {
- // loadURI in browser.xml treats null as about:blank
- if (!aURL)
- aURL = "about:blank";
-
- // Javascript urls can load in any process, they apply to the current document
- if (aURL.startsWith("javascript:"))
- return true;
-
- let processIsRemote = aProcess == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
-
- let canLoadRemote = true;
- let mustLoadRemote = true;
-
- if (aURL.startsWith("about:")) {
- let url = Services.io.newURI(aURL, null, null);
- let module = getAboutModule(url);
- // If the module doesn't exist then an error page will be loading, that
- // should be ok to load in either process
- if (module) {
- let flags = module.getURIFlags(url);
- canLoadRemote = !!(flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD);
- mustLoadRemote = !!(flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD);
- }
- }
-
- if (aURL.startsWith("chrome:")) {
- let url;
- try {
- // This can fail for invalid Chrome URIs, in which case we will end up
- // not loading anything anyway.
- url = Services.io.newURI(aURL, null, null);
- } catch (ex) {
- canLoadRemote = true;
- mustLoadRemote = false;
- }
- if (url) {
- let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
- getService(Ci.nsIXULChromeRegistry);
- canLoadRemote = chromeReg.canLoadURLRemotely(url);
- mustLoadRemote = chromeReg.mustLoadURLRemotely(url);
- }
- }
-
- if (aURL.startsWith("moz-extension:")) {
- canLoadRemote = false;
- mustLoadRemote = false;
- }
-
- if (aURL.startsWith("view-source:")) {
- return this.canLoadURIInProcess(aURL.substr("view-source:".length), aProcess);
- }
-
- if (mustLoadRemote)
- return processIsRemote;
-
- if (!canLoadRemote && processIsRemote)
- return false;
-
- return true;
- },
-
- shouldLoadURI: function(aDocShell, aURI, aReferrer) {
- // Inner frames should always load in the current process
- if (aDocShell.QueryInterface(Ci.nsIDocShellTreeItem).sameTypeParent)
- return true;
-
- // If the URI can be loaded in the current process then continue
- return this.canLoadURIInProcess(aURI.spec, Services.appinfo.processType);
- },
-
- redirectLoad: function(aDocShell, aURI, aReferrer, aFreshProcess) {
- // Retarget the load to the correct process
- let messageManager = aDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIContentFrameMessageManager);
- let sessionHistory = aDocShell.getInterface(Ci.nsIWebNavigation).sessionHistory;
-
- messageManager.sendAsyncMessage("Browser:LoadURI", {
- loadOptions: {
- uri: aURI.spec,
- flags: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
- referrer: aReferrer ? aReferrer.spec : null,
- reloadInFreshProcess: !!aFreshProcess,
- },
- historyIndex: sessionHistory.requestedIndex,
- });
- return false;
- },
-
- wrapHandlingUserInput: function(aWindow, aIsHandling, aCallback) {
- var handlingUserInput;
- try {
- handlingUserInput = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .setHandlingUserInput(aIsHandling);
- aCallback();
- } finally {
- handlingUserInput.destruct();
- }
- },
-};
diff --git a/browser/modules/Feeds.jsm b/browser/modules/Feeds.jsm
deleted file mode 100644
index 179d2b83d..000000000
--- a/browser/modules/Feeds.jsm
+++ /dev/null
@@ -1,104 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = [ "Feeds" ];
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
- "resource://gre/modules/BrowserUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
- "resource:///modules/RecentWindow.jsm");
-
-const { interfaces: Ci, classes: Cc } = Components;
-
-this.Feeds = {
- init() {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("WCCR:registerProtocolHandler", this);
- mm.addMessageListener("WCCR:registerContentHandler", this);
-
- Services.ppmm.addMessageListener("WCCR:setAutoHandler", this);
- Services.ppmm.addMessageListener("FeedConverter:addLiveBookmark", this);
- },
-
- receiveMessage(aMessage) {
- let data = aMessage.data;
- switch (aMessage.name) {
- case "WCCR:registerProtocolHandler": {
- let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
- getService(Ci.nsIWebContentHandlerRegistrar);
- registrar.registerProtocolHandler(data.protocol, data.uri, data.title,
- aMessage.target);
- break;
- }
-
- case "WCCR:registerContentHandler": {
- let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
- getService(Ci.nsIWebContentHandlerRegistrar);
- registrar.registerContentHandler(data.contentType, data.uri, data.title,
- aMessage.target);
- break;
- }
-
- case "WCCR:setAutoHandler": {
- let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
- getService(Ci.nsIWebContentConverterService);
- registrar.setAutoHandler(data.contentType, data.handler);
- break;
- }
-
- case "FeedConverter:addLiveBookmark": {
- let topWindow = RecentWindow.getMostRecentBrowserWindow();
- topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title, data.subtitle)
- .catch(Components.utils.reportError);
- break;
- }
- }
- },
-
- /**
- * isValidFeed: checks whether the given data represents a valid feed.
- *
- * @param aLink
- * An object representing a feed with title, href and type.
- * @param aPrincipal
- * The principal of the document, used for security check.
- * @param aIsFeed
- * Whether this is already a known feed or not, if true only a security
- * check will be performed.
- */
- isValidFeed: function(aLink, aPrincipal, aIsFeed) {
- if (!aLink || !aPrincipal)
- return false;
-
- var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
- if (!aIsFeed) {
- aIsFeed = (type == "application/rss+xml" ||
- type == "application/atom+xml");
- }
-
- if (aIsFeed) {
- // re-create the principal as it may be a CPOW.
- // once this can't be a CPOW anymore, we should just use aPrincipal instead
- // of creating a new one.
- let principalURI = BrowserUtils.makeURIFromCPOW(aPrincipal.URI);
- let principalToCheck =
- Services.scriptSecurityManager.createCodebasePrincipal(principalURI, aPrincipal.originAttributes);
- try {
- BrowserUtils.urlSecurityCheck(aLink.href, principalToCheck,
- Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
- return type || "application/rss+xml";
- }
- catch (ex) {
- }
- }
-
- return null;
- },
-
-};
diff --git a/browser/modules/FormSubmitObserver.jsm b/browser/modules/FormSubmitObserver.jsm
deleted file mode 100644
index 058794a54..000000000
--- a/browser/modules/FormSubmitObserver.jsm
+++ /dev/null
@@ -1,235 +0,0 @@
-/* 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/. */
-
-/*
- * Handles the validation callback from nsIFormFillController and
- * the display of the help panel on invalid elements.
- */
-
-"use strict";
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-var HTMLInputElement = Ci.nsIDOMHTMLInputElement;
-var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
-var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
-var HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
-
-this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/BrowserUtils.jsm");
-
-function FormSubmitObserver(aWindow, aTabChildGlobal) {
- this.init(aWindow, aTabChildGlobal);
-}
-
-FormSubmitObserver.prototype =
-{
- _validationMessage: "",
- _content: null,
- _element: null,
-
- /*
- * Public apis
- */
-
- init: function(aWindow, aTabChildGlobal)
- {
- this._content = aWindow;
- this._tab = aTabChildGlobal;
- this._mm =
- this._content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDocShell)
- .sameTypeRootTreeItem
- .QueryInterface(Ci.nsIDocShell)
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIContentFrameMessageManager);
-
- // nsIFormSubmitObserver callback about invalid forms. See HTMLFormElement
- // for details.
- Services.obs.addObserver(this, "invalidformsubmit", false);
- this._tab.addEventListener("pageshow", this, false);
- this._tab.addEventListener("unload", this, false);
- },
-
- uninit: function()
- {
- Services.obs.removeObserver(this, "invalidformsubmit");
- this._content.removeEventListener("pageshow", this, false);
- this._content.removeEventListener("unload", this, false);
- this._mm = null;
- this._element = null;
- this._content = null;
- this._tab = null;
- },
-
- /*
- * Events
- */
-
- handleEvent: function (aEvent) {
- switch (aEvent.type) {
- case "pageshow":
- if (this._isRootDocumentEvent(aEvent)) {
- this._hidePopup();
- }
- break;
- case "unload":
- this.uninit();
- break;
- case "input":
- this._onInput(aEvent);
- break;
- case "blur":
- this._onBlur(aEvent);
- break;
- }
- },
-
- /*
- * nsIFormSubmitObserver
- */
-
- notifyInvalidSubmit : function (aFormElement, aInvalidElements)
- {
- // We are going to handle invalid form submission attempt by focusing the
- // first invalid element and show the corresponding validation message in a
- // panel attached to the element.
- if (!aInvalidElements.length) {
- return;
- }
-
- // Insure that this is the FormSubmitObserver associated with the
- // element / window this notification is about.
- let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
- if (this._content != element.ownerGlobal.top.document.defaultView) {
- return;
- }
-
- if (!(element instanceof HTMLInputElement ||
- element instanceof HTMLTextAreaElement ||
- element instanceof HTMLSelectElement ||
- element instanceof HTMLButtonElement)) {
- return;
- }
-
- // Update validation message before showing notification
- this._validationMessage = element.validationMessage;
-
- // Don't connect up to the same element more than once.
- if (this._element == element) {
- this._showPopup(element);
- return;
- }
- this._element = element;
-
- element.focus();
-
- // Watch for input changes which may change the validation message.
- element.addEventListener("input", this, false);
-
- // Watch for focus changes so we can disconnect our listeners and
- // hide the popup.
- element.addEventListener("blur", this, false);
-
- this._showPopup(element);
- },
-
- /*
- * Internal
- */
-
- /*
- * Handles input changes on the form element we've associated a popup
- * with. Updates the validation message or closes the popup if form data
- * becomes valid.
- */
- _onInput: function (aEvent) {
- let element = aEvent.originalTarget;
-
- // If the form input is now valid, hide the popup.
- if (element.validity.valid) {
- this._hidePopup();
- return;
- }
-
- // If the element is still invalid for a new reason, we should update
- // the popup error message.
- if (this._validationMessage != element.validationMessage) {
- this._validationMessage = element.validationMessage;
- this._showPopup(element);
- }
- },
-
- /*
- * Blur event handler in which we disconnect from the form element and
- * hide the popup.
- */
- _onBlur: function (aEvent) {
- aEvent.originalTarget.removeEventListener("input", this, false);
- aEvent.originalTarget.removeEventListener("blur", this, false);
- this._element = null;
- this._hidePopup();
- },
-
- /*
- * Send the show popup message to chrome with appropriate position
- * information. Can be called repetitively to update the currently
- * displayed popup position and text.
- */
- _showPopup: function (aElement) {
- // Collect positional information and show the popup
- let panelData = {};
-
- panelData.message = this._validationMessage;
-
- // Note, this is relative to the browser and needs to be translated
- // in chrome.
- panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
-
- // We want to show the popup at the middle of checkbox and radio buttons
- // and where the content begin for the other elements.
- let offset = 0;
-
- if (aElement.tagName == 'INPUT' &&
- (aElement.type == 'radio' || aElement.type == 'checkbox')) {
- panelData.position = "bottomcenter topleft";
- } else {
- let win = aElement.ownerGlobal;
- let style = win.getComputedStyle(aElement, null);
- if (style.direction == 'rtl') {
- offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
- } else {
- offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
- }
- let zoomFactor = this._getWindowUtils().fullZoom;
- panelData.offset = Math.round(offset * zoomFactor);
- panelData.position = "after_start";
- }
- this._mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
- },
-
- _hidePopup: function () {
- this._mm.sendAsyncMessage("FormValidation:HidePopup", {});
- },
-
- _getWindowUtils: function () {
- return this._content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- },
-
- _isRootDocumentEvent: function (aEvent) {
- if (this._content == null) {
- return true;
- }
- let target = aEvent.originalTarget;
- return (target == this._content.document ||
- (target.ownerDocument && target.ownerDocument == this._content.document));
- },
-
- QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
-};
diff --git a/browser/modules/FormValidationHandler.jsm b/browser/modules/FormValidationHandler.jsm
deleted file mode 100644
index e7e7b14c3..000000000
--- a/browser/modules/FormValidationHandler.jsm
+++ /dev/null
@@ -1,157 +0,0 @@
-/* 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/. */
-
-/*
- * Chrome side handling of form validation popup.
- */
-
-"use strict";
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-this.EXPORTED_SYMBOLS = [ "FormValidationHandler" ];
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-var FormValidationHandler =
-{
- _panel: null,
- _anchor: null,
-
- /*
- * Public apis
- */
-
- init: function () {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("FormValidation:ShowPopup", this);
- mm.addMessageListener("FormValidation:HidePopup", this);
- },
-
- uninit: function () {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.removeMessageListener("FormValidation:ShowPopup", this);
- mm.removeMessageListener("FormValidation:HidePopup", this);
- this._panel = null;
- this._anchor = null;
- },
-
- hidePopup: function () {
- this._hidePopup();
- },
-
- /*
- * Events
- */
-
- receiveMessage: function (aMessage) {
- let window = aMessage.target.ownerGlobal;
- let json = aMessage.json;
- let tabBrowser = window.gBrowser;
- switch (aMessage.name) {
- case "FormValidation:ShowPopup":
- // target is the <browser>, make sure we're receiving a message
- // from the foreground tab.
- if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
- return;
- }
- this._showPopup(window, json);
- break;
- case "FormValidation:HidePopup":
- this._hidePopup();
- break;
- }
- },
-
- observe: function (aSubject, aTopic, aData) {
- this._hidePopup();
- },
-
- handleEvent: function (aEvent) {
- switch (aEvent.type) {
- case "FullZoomChange":
- case "TextZoomChange":
- case "ZoomChangeUsingMouseWheel":
- case "scroll":
- this._hidePopup();
- break;
- case "popuphiding":
- this._onPopupHiding(aEvent);
- break;
- }
- },
-
- /*
- * Internal
- */
-
- _onPopupHiding: function (aEvent) {
- aEvent.originalTarget.removeEventListener("popuphiding", this, true);
- let tabBrowser = aEvent.originalTarget.ownerDocument.getElementById("content");
- tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
- tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this, false);
- tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this, false);
- tabBrowser.selectedBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this, false);
-
- this._panel.hidden = true;
- this._panel = null;
- this._anchor.hidden = true;
- this._anchor = null;
- },
-
- /*
- * Shows the form validation popup at a specified position or updates the
- * messaging and position if the popup is already displayed.
- *
- * @aWindow - the chrome window
- * @aPanelData - Object that contains popup information
- * aPanelData stucture detail:
- * contentRect - the bounding client rect of the target element. If
- * content is remote, this is relative to the browser, otherwise its
- * relative to the window.
- * position - popup positional string constants.
- * message - the form element validation message text.
- */
- _showPopup: function (aWindow, aPanelData) {
- let previouslyShown = !!this._panel;
- this._panel = aWindow.document.getElementById("invalid-form-popup");
- this._panel.firstChild.textContent = aPanelData.message;
- this._panel.hidden = false;
-
- let tabBrowser = aWindow.gBrowser;
- this._anchor = tabBrowser.popupAnchor;
- this._anchor.left = aPanelData.contentRect.left;
- this._anchor.top = aPanelData.contentRect.top;
- this._anchor.width = aPanelData.contentRect.width;
- this._anchor.height = aPanelData.contentRect.height;
- this._anchor.hidden = false;
-
- // Display the panel if it isn't already visible.
- if (!previouslyShown) {
- // Cleanup after the popup is hidden
- this._panel.addEventListener("popuphiding", this, true);
-
- // Hide if the user scrolls the page
- tabBrowser.selectedBrowser.addEventListener("scroll", this, true);
- tabBrowser.selectedBrowser.addEventListener("FullZoomChange", this, false);
- tabBrowser.selectedBrowser.addEventListener("TextZoomChange", this, false);
- tabBrowser.selectedBrowser.addEventListener("ZoomChangeUsingMouseWheel", this, false);
-
- // Open the popup
- this._panel.openPopup(this._anchor, aPanelData.position, 0, 0, false);
- }
- },
-
- /*
- * Hide the popup if currently displayed. Will fire an event to onPopupHiding
- * above if visible.
- */
- _hidePopup: function () {
- if (this._panel) {
- this._panel.hidePopup();
- }
- }
-};
diff --git a/browser/modules/HiddenFrame.jsm b/browser/modules/HiddenFrame.jsm
deleted file mode 100644
index 7676ae189..000000000
--- a/browser/modules/HiddenFrame.jsm
+++ /dev/null
@@ -1,86 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["HiddenFrame"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/PromiseUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
-
-/**
- * An hidden frame object. It takes care of creating an IFRAME and attaching it the
- * |hiddenDOMWindow|.
- */
-function HiddenFrame() {}
-
-HiddenFrame.prototype = {
- _frame: null,
- _deferred: null,
- _retryTimerId: null,
-
- get hiddenDOMDocument() {
- return Services.appShell.hiddenDOMWindow.document;
- },
-
- get isReady() {
- return this.hiddenDOMDocument.readyState === "complete";
- },
-
- /**
- * Gets the |contentWindow| of the hidden frame. Creates the frame if needed.
- * @returns Promise Returns a promise which is resolved when the hidden frame has finished
- * loading.
- */
- get: function () {
- if (!this._deferred) {
- this._deferred = PromiseUtils.defer();
- this._create();
- }
-
- return this._deferred.promise;
- },
-
- destroy: function () {
- clearTimeout(this._retryTimerId);
-
- if (this._frame) {
- if (!Cu.isDeadWrapper(this._frame)) {
- this._frame.removeEventListener("load", this, true);
- this._frame.remove();
- }
-
- this._frame = null;
- this._deferred = null;
- }
- },
-
- handleEvent: function () {
- let contentWindow = this._frame.contentWindow;
- if (contentWindow.location.href === XUL_PAGE) {
- this._frame.removeEventListener("load", this, true);
- this._deferred.resolve(contentWindow);
- } else {
- contentWindow.location = XUL_PAGE;
- }
- },
-
- _create: function () {
- if (this.isReady) {
- let doc = this.hiddenDOMDocument;
- this._frame = doc.createElementNS(HTML_NS, "iframe");
- this._frame.addEventListener("load", this, true);
- doc.documentElement.appendChild(this._frame);
- } else {
- // Check again if |hiddenDOMDocument| is ready as soon as possible.
- this._retryTimerId = setTimeout(this._create.bind(this), 0);
- }
- }
-};
diff --git a/browser/modules/LaterRun.jsm b/browser/modules/LaterRun.jsm
deleted file mode 100644
index 4c93a904a..000000000
--- a/browser/modules/LaterRun.jsm
+++ /dev/null
@@ -1,172 +0,0 @@
-/* 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";
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-this.EXPORTED_SYMBOLS = ["LaterRun"];
-
-Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setInterval", "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "clearInterval", "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", "resource://gre/modules/RecentWindow.jsm");
-
-const kEnabledPref = "browser.laterrun.enabled";
-const kPagePrefRoot = "browser.laterrun.pages.";
-// Number of sessions we've been active in
-const kSessionCountPref = "browser.laterrun.bookkeeping.sessionCount";
-// Time the profile was created at:
-const kProfileCreationTime = "browser.laterrun.bookkeeping.profileCreationTime";
-
-// After 50 sessions or 1 month since install, assume we will no longer be
-// interested in showing anything to "new" users
-const kSelfDestructSessionLimit = 50;
-const kSelfDestructHoursLimit = 31 * 24;
-
-class Page {
- constructor({pref, minimumHoursSinceInstall, minimumSessionCount, requireBoth, url}) {
- this.pref = pref;
- this.minimumHoursSinceInstall = minimumHoursSinceInstall || 0;
- this.minimumSessionCount = minimumSessionCount || 1;
- this.requireBoth = requireBoth || false;
- this.url = url;
- }
-
- get hasRun() {
- return Preferences.get(this.pref + "hasRun", false);
- }
-
- applies(sessionInfo) {
- if (this.hasRun) {
- return false;
- }
- if (this.requireBoth) {
- return sessionInfo.sessionCount >= this.minimumSessionCount &&
- sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall;
- }
- return sessionInfo.sessionCount >= this.minimumSessionCount ||
- sessionInfo.hoursSinceInstall >= this.minimumHoursSinceInstall;
- }
-}
-
-let LaterRun = {
- init() {
- if (!this.enabled) {
- return;
- }
- // If this is the first run, set the time we were installed
- if (!Preferences.has(kProfileCreationTime)) {
- // We need to store seconds in order to fit within int prefs.
- Preferences.set(kProfileCreationTime, Math.floor(Date.now() / 1000));
- }
- this.sessionCount++;
-
- if (this.hoursSinceInstall > kSelfDestructHoursLimit ||
- this.sessionCount > kSelfDestructSessionLimit) {
- this.selfDestruct();
- return;
- }
- },
-
- // The enabled, hoursSinceInstall and sessionCount properties mirror the
- // preferences system, and are here for convenience.
- get enabled() {
- return Preferences.get(kEnabledPref, false);
- },
-
- set enabled(val) {
- let wasEnabled = this.enabled;
- Preferences.set(kEnabledPref, val);
- if (val && !wasEnabled) {
- this.init();
- }
- },
-
- get hoursSinceInstall() {
- let installStamp = Preferences.get(kProfileCreationTime, Date.now() / 1000);
- return Math.floor((Date.now() / 1000 - installStamp) / 3600);
- },
-
- get sessionCount() {
- if (this._sessionCount) {
- return this._sessionCount;
- }
- return this._sessionCount = Preferences.get(kSessionCountPref, 0);
- },
-
- set sessionCount(val) {
- this._sessionCount = val;
- Preferences.set(kSessionCountPref, val);
- },
-
- // Because we don't want to keep incrementing this indefinitely for no reason,
- // we will turn ourselves off after a set amount of time/sessions (see top of
- // file).
- selfDestruct() {
- Preferences.set(kEnabledPref, false);
- },
-
- // Create an array of Page objects based on the currently set prefs
- readPages() {
- // Enumerate all the pages.
- let allPrefsForPages = Services.prefs.getChildList(kPagePrefRoot);
- let pageDataStore = new Map();
- for (let pref of allPrefsForPages) {
- let [slug, prop] = pref.substring(kPagePrefRoot.length).split(".");
- if (!pageDataStore.has(slug)) {
- pageDataStore.set(slug, {pref: pref.substring(0, pref.length - prop.length)});
- }
- let defaultPrefValue = 0;
- if (prop == "requireBoth" || prop == "hasRun") {
- defaultPrefValue = false;
- } else if (prop == "url") {
- defaultPrefValue = "";
- }
- pageDataStore.get(slug)[prop] = Preferences.get(pref, defaultPrefValue);
- }
- let rv = [];
- for (let [, pageData] of pageDataStore) {
- if (pageData.url) {
- let uri = null;
- try {
- let urlString = Services.urlFormatter.formatURL(pageData.url.trim());
- uri = Services.io.newURI(urlString, null, null);
- } catch (ex) {
- Cu.reportError("Invalid LaterRun page URL " + pageData.url + " ignored.");
- continue;
- }
- if (!uri.schemeIs("https")) {
- Cu.reportError("Insecure LaterRun page URL " + uri.spec + " ignored.");
- } else {
- pageData.url = uri.spec;
- rv.push(new Page(pageData));
- }
- }
- }
- return rv;
- },
-
- // Return a URL for display as a 'later run' page if its criteria are matched,
- // or null otherwise.
- // NB: will only return one page at a time; if multiple pages match, it's up
- // to the preference service which one gets shown first, and the next one
- // will be shown next startup instead.
- getURL() {
- if (!this.enabled) {
- return null;
- }
- let pages = this.readPages();
- let page = pages.find(page => page.applies(this));
- if (page) {
- Services.prefs.setBoolPref(page.pref + "hasRun", true);
- return page.url;
- }
- return null;
- },
-};
-
-LaterRun.init();
diff --git a/browser/modules/NetworkPrioritizer.jsm b/browser/modules/NetworkPrioritizer.jsm
deleted file mode 100644
index 770a30e09..000000000
--- a/browser/modules/NetworkPrioritizer.jsm
+++ /dev/null
@@ -1,194 +0,0 @@
-/* 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/. */
-
-/*
- * This module adjusts network priority for tabs in a way that gives 'important'
- * tabs a higher priority. There are 3 levels of priority. Each is listed below
- * with the priority adjustment used.
- *
- * Highest (-10): Selected tab in the focused window.
- * Medium (0): Background tabs in the focused window.
- * Selected tab in background windows.
- * Lowest (+10): Background tabs in background windows.
- */
-
-this.EXPORTED_SYMBOLS = ["trackBrowserWindow"];
-
-const Ci = Components.interfaces;
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-
-// Lazy getters
-XPCOMUtils.defineLazyServiceGetter(this, "_focusManager",
- "@mozilla.org/focus-manager;1",
- "nsIFocusManager");
-
-
-// Constants
-const TAB_EVENTS = ["TabBrowserInserted", "TabSelect", "TabRemotenessChange"];
-const WINDOW_EVENTS = ["activate", "unload"];
-// lower value means higher priority
-const PRIORITY_DELTA = Ci.nsISupportsPriority.PRIORITY_NORMAL - Ci.nsISupportsPriority.PRIORITY_LOW;
-
-
-// Variables
-var _lastFocusedWindow = null;
-var _windows = [];
-// this is used for restoring the priority after TabRemotenessChange
-var _priorityBackup = new WeakMap();
-
-
-// Exported symbol
-this.trackBrowserWindow = function trackBrowserWindow(aWindow) {
- WindowHelper.addWindow(aWindow);
-}
-
-
-// Global methods
-function _handleEvent(aEvent) {
- switch (aEvent.type) {
- case "TabBrowserInserted":
- BrowserHelper.onOpen(aEvent.target.linkedBrowser);
- break;
- case "TabSelect":
- BrowserHelper.onSelect(aEvent.target.linkedBrowser);
- break;
- case "activate":
- WindowHelper.onActivate(aEvent.target);
- break;
- case "TabRemotenessChange":
- BrowserHelper.onRemotenessChange(aEvent.target.linkedBrowser);
- break;
- case "unload":
- WindowHelper.removeWindow(aEvent.currentTarget);
- break;
- }
-}
-
-
-// Methods that impact a browser. Put into single object for organization.
-var BrowserHelper = {
- onOpen: function NP_BH_onOpen(aBrowser) {
- _priorityBackup.set(aBrowser.permanentKey, Ci.nsISupportsPriority.PRIORITY_NORMAL);
-
- // If the tab is in the focused window, leave priority as it is
- if (aBrowser.ownerGlobal != _lastFocusedWindow)
- this.decreasePriority(aBrowser);
- },
-
- onSelect: function NP_BH_onSelect(aBrowser) {
- let windowEntry = WindowHelper.getEntry(aBrowser.ownerGlobal);
- if (windowEntry.lastSelectedBrowser)
- this.decreasePriority(windowEntry.lastSelectedBrowser);
- this.increasePriority(aBrowser);
-
- windowEntry.lastSelectedBrowser = aBrowser;
- },
-
- onRemotenessChange: function (aBrowser) {
- aBrowser.setPriority(_priorityBackup.get(aBrowser.permanentKey));
- },
-
- increasePriority: function NP_BH_increasePriority(aBrowser) {
- aBrowser.adjustPriority(PRIORITY_DELTA);
- _priorityBackup.set(aBrowser.permanentKey,
- _priorityBackup.get(aBrowser.permanentKey) + PRIORITY_DELTA);
- },
-
- decreasePriority: function NP_BH_decreasePriority(aBrowser) {
- aBrowser.adjustPriority(PRIORITY_DELTA * -1);
- _priorityBackup.set(aBrowser.permanentKey,
- _priorityBackup.get(aBrowser.permanentKey) - PRIORITY_DELTA);
- }
-};
-
-
-// Methods that impact a window. Put into single object for organization.
-var WindowHelper = {
- addWindow: function NP_WH_addWindow(aWindow) {
- // Build internal data object
- _windows.push({ window: aWindow, lastSelectedBrowser: null });
-
- // Add event listeners
- TAB_EVENTS.forEach(function(event) {
- aWindow.gBrowser.tabContainer.addEventListener(event, _handleEvent, false);
- });
- WINDOW_EVENTS.forEach(function(event) {
- aWindow.addEventListener(event, _handleEvent, false);
- });
-
- // This gets called AFTER activate event, so if this is the focused window
- // we want to activate it. Otherwise, deprioritize it.
- if (aWindow == _focusManager.activeWindow)
- this.handleFocusedWindow(aWindow);
- else
- this.decreasePriority(aWindow);
-
- // Select the selected tab
- BrowserHelper.onSelect(aWindow.gBrowser.selectedBrowser);
- },
-
- removeWindow: function NP_WH_removeWindow(aWindow) {
- if (aWindow == _lastFocusedWindow)
- _lastFocusedWindow = null;
-
- // Delete this window from our tracking
- _windows.splice(this.getEntryIndex(aWindow), 1);
-
- // Remove the event listeners
- TAB_EVENTS.forEach(function(event) {
- aWindow.gBrowser.tabContainer.removeEventListener(event, _handleEvent, false);
- });
- WINDOW_EVENTS.forEach(function(event) {
- aWindow.removeEventListener(event, _handleEvent, false);
- });
- },
-
- onActivate: function NP_WH_onActivate(aWindow, aHasFocus) {
- // If this window was the last focused window, we don't need to do anything
- if (aWindow == _lastFocusedWindow)
- return;
-
- // handleFocusedWindow will deprioritize the current window
- this.handleFocusedWindow(aWindow);
-
- // Lastly we should increase priority for this window
- this.increasePriority(aWindow);
- },
-
- handleFocusedWindow: function NP_WH_handleFocusedWindow(aWindow) {
- // If we have a last focused window, we need to deprioritize it first
- if (_lastFocusedWindow)
- this.decreasePriority(_lastFocusedWindow);
-
- // aWindow is now focused
- _lastFocusedWindow = aWindow;
- },
-
- // Auxiliary methods
- increasePriority: function NP_WH_increasePriority(aWindow) {
- aWindow.gBrowser.browsers.forEach(function(aBrowser) {
- BrowserHelper.increasePriority(aBrowser);
- });
- },
-
- decreasePriority: function NP_WH_decreasePriority(aWindow) {
- aWindow.gBrowser.browsers.forEach(function(aBrowser) {
- BrowserHelper.decreasePriority(aBrowser);
- });
- },
-
- getEntry: function NP_WH_getEntry(aWindow) {
- return _windows[this.getEntryIndex(aWindow)];
- },
-
- getEntryIndex: function NP_WH_getEntryAtIndex(aWindow) {
- // Assumes that every object has a unique window & it's in the array
- for (let i = 0; i < _windows.length; i++)
- if (_windows[i].window == aWindow)
- return i;
- }
-};
-
diff --git a/browser/modules/PermissionUI.jsm b/browser/modules/PermissionUI.jsm
deleted file mode 100644
index 5fa0f9f06..000000000
--- a/browser/modules/PermissionUI.jsm
+++ /dev/null
@@ -1,595 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = [
- "PermissionUI",
-];
-
-/**
- * PermissionUI is responsible for exposing both a prototype
- * PermissionPrompt that can be used by arbitrary browser
- * components and add-ons, but also hosts the implementations of
- * built-in permission prompts.
- *
- * If you're developing a feature that requires web content to ask
- * for special permissions from the user, this module is for you.
- *
- * Suppose a system add-on wants to add a new prompt for a new request
- * for getting more low-level access to the user's sound card, and the
- * permission request is coming up from content by way of the
- * nsContentPermissionHelper. The system add-on could then do the following:
- *
- * Cu.import("resource://gre/modules/Integration.jsm");
- * Cu.import("resource:///modules/PermissionUI.jsm");
- *
- * const SoundCardIntegration = (base) => ({
- * __proto__: base,
- * createPermissionPrompt(type, request) {
- * if (type != "sound-api") {
- * return super.createPermissionPrompt(...arguments);
- * }
- *
- * return {
- * __proto__: PermissionUI.PermissionPromptForRequestPrototype,
- * get permissionKey() {
- * return "sound-permission";
- * }
- * // etc - see the documentation for PermissionPrompt for
- * // a better idea of what things one can and should override.
- * }
- * },
- * });
- *
- * // Add-on startup:
- * Integration.contentPermission.register(SoundCardIntegration);
- * // ...
- * // Add-on shutdown:
- * Integration.contentPermission.unregister(SoundCardIntegration);
- *
- * Note that PermissionPromptForRequestPrototype must be used as the
- * prototype, since the prompt is wrapping an nsIContentPermissionRequest,
- * and going through nsIContentPermissionPrompt.
- *
- * It is, however, possible to take advantage of PermissionPrompt without
- * having to go through nsIContentPermissionPrompt or with a
- * nsIContentPermissionRequest. The PermissionPromptPrototype can be
- * imported, subclassed, and have prompt() called directly, without
- * the caller having called into createPermissionPrompt.
- */
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
- return Services.strings
- .createBundle('chrome://branding/locale/brand.properties');
-});
-
-XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
- return Services.strings
- .createBundle('chrome://browser/locale/browser.properties');
-});
-
-this.PermissionUI = {};
-
-/**
- * PermissionPromptPrototype should be subclassed by callers that
- * want to display prompts to the user. See each method and property
- * below for guidance on what to override.
- *
- * Note that if you're creating a prompt for an
- * nsIContentPermissionRequest, you'll want to subclass
- * PermissionPromptForRequestPrototype instead.
- */
-this.PermissionPromptPrototype = {
- /**
- * Returns the associated <xul:browser> for the request. This should
- * work for the e10s and non-e10s case.
- *
- * Subclasses must override this.
- *
- * @return {<xul:browser>}
- */
- get browser() {
- throw new Error("Not implemented.");
- },
-
- /**
- * Returns the nsIPrincipal associated with the request.
- *
- * Subclasses must override this.
- *
- * @return {nsIPrincipal}
- */
- get principal() {
- throw new Error("Not implemented.");
- },
-
- /**
- * If the nsIPermissionManager is being queried and written
- * to for this permission request, set this to the key to be
- * used. If this is undefined, user permissions will not be
- * read from or written to.
- *
- * Note that if a permission is set, in any follow-up
- * prompting within the expiry window of that permission,
- * the prompt will be skipped and the allow or deny choice
- * will be selected automatically.
- */
- get permissionKey() {
- return undefined;
- },
-
- /**
- * These are the options that will be passed to the
- * PopupNotification when it is shown. See the documentation
- * for PopupNotification for more details.
- *
- * Note that prompt() will automatically set displayURI to
- * be the URI of the requesting pricipal, unless the displayURI is exactly
- * set to false.
- */
- get popupOptions() {
- return {};
- },
-
- /**
- * PopupNotification requires a unique ID to open the notification.
- * You must return a unique ID string here, for which PopupNotification
- * will then create a <xul:popupnotification> node with the ID
- * "<notificationID>-notification".
- *
- * If there's a custom <xul:popupnotification> you're hoping to show,
- * then you need to make sure its ID has the "-notification" suffix,
- * and then return the prefix here.
- *
- * See PopupNotification.jsm for more details.
- *
- * @return {string}
- * The unique ID that will be used to as the
- * "<unique ID>-notification" ID for the <xul:popupnotification>
- * to use or create.
- */
- get notificationID() {
- throw new Error("Not implemented.");
- },
-
- /**
- * The ID of the element to anchor the PopupNotification to.
- *
- * @return {string}
- */
- get anchorID() {
- return "default-notification-icon";
- },
-
- /**
- * The message to show the user in the PopupNotification. This
- * is usually a string describing the permission that is being
- * requested.
- *
- * Subclasses must override this.
- *
- * @return {string}
- */
- get message() {
- throw new Error("Not implemented.");
- },
-
- /**
- * This will be called if the request is to be cancelled.
- *
- * Subclasses only need to override this if they provide a
- * permissionKey.
- */
- cancel() {
- throw new Error("Not implemented.")
- },
-
- /**
- * This will be called if the request is to be allowed.
- *
- * Subclasses only need to override this if they provide a
- * permissionKey.
- */
- allow() {
- throw new Error("Not implemented.");
- },
-
- /**
- * The actions that will be displayed in the PopupNotification
- * via a dropdown menu. The first item in this array will be
- * the default selection. Each action is an Object with the
- * following properties:
- *
- * label (string):
- * The label that will be displayed for this choice.
- * accessKey (string):
- * The access key character that will be used for this choice.
- * action (Ci.nsIPermissionManager action, optional)
- * The nsIPermissionManager action that will be associated with
- * this choice. For example, Ci.nsIPermissionManager.DENY_ACTION.
- *
- * If omitted, the nsIPermissionManager will not be written to
- * when this choice is chosen.
- * expireType (Ci.nsIPermissionManager expiration policy, optional)
- * The nsIPermissionManager expiration policy that will be associated
- * with this choice. For example, Ci.nsIPermissionManager.EXPIRE_SESSION.
- *
- * If action is not set, expireType will be ignored.
- * callback (function, optional)
- * A callback function that will fire if the user makes this choice.
- */
- get promptActions() {
- return [];
- },
-
- /**
- * If the prompt will be shown to the user, this callback will
- * be called just before. Subclasses may want to override this
- * in order to, for example, bump a counter Telemetry probe for
- * how often a particular permission request is seen.
- */
- onBeforeShow() {},
-
- /**
- * Will determine if a prompt should be shown to the user, and if so,
- * will show it.
- *
- * If a permissionKey is defined prompt() might automatically
- * allow or cancel itself based on the user's current
- * permission settings without displaying the prompt.
- *
- * If the <xul:browser> that the request is associated with
- * does not belong to a browser window with the PopupNotifications
- * global set, the prompt request is ignored.
- */
- prompt() {
- let chromeWin = this.browser.ownerGlobal;
- if (!chromeWin.PopupNotifications) {
- return;
- }
-
- // We ignore requests from non-nsIStandardURLs
- let requestingURI = this.principal.URI;
- if (!(requestingURI instanceof Ci.nsIStandardURL)) {
- return;
- }
-
- if (this.permissionKey) {
- // If we're reading and setting permissions, then we need
- // to check to see if we already have a permission setting
- // for this particular principal.
- let result =
- Services.perms.testExactPermissionFromPrincipal(this.principal,
- this.permissionKey);
-
- if (result == Ci.nsIPermissionManager.DENY_ACTION) {
- this.cancel();
- return;
- }
-
- if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
- this.allow();
- return;
- }
- }
-
- // Transform the PermissionPrompt actions into PopupNotification actions.
- let popupNotificationActions = [];
- for (let promptAction of this.promptActions) {
- // Don't offer action in PB mode if the action remembers permission
- // for more than a session.
- if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
- promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
- promptAction.action) {
- continue;
- }
-
- let action = {
- label: promptAction.label,
- accessKey: promptAction.accessKey,
- callback: () => {
- if (promptAction.callback) {
- promptAction.callback();
- }
-
- if (this.permissionKey) {
- // Remember permissions.
- if (promptAction.action) {
- Services.perms.addFromPrincipal(this.principal,
- this.permissionKey,
- promptAction.action,
- promptAction.expireType);
- }
-
- // Grant permission if action is null or ALLOW_ACTION.
- if (!promptAction.action ||
- promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
- this.allow();
- } else {
- this.cancel();
- }
- }
- },
- };
- if (promptAction.dismiss) {
- action.dismiss = promptAction.dismiss
- }
-
- popupNotificationActions.push(action);
- }
-
- let mainAction = popupNotificationActions.length ?
- popupNotificationActions[0] : null;
- let secondaryActions = popupNotificationActions.splice(1);
-
- let options = this.popupOptions;
-
- if (!options.hasOwnProperty('displayURI') || options.displayURI) {
- options.displayURI = this.principal.URI;
- }
-
- this.onBeforeShow();
- chromeWin.PopupNotifications.show(this.browser,
- this.notificationID,
- this.message,
- this.anchorID,
- mainAction,
- secondaryActions,
- options);
- },
-};
-
-PermissionUI.PermissionPromptPrototype = PermissionPromptPrototype;
-
-/**
- * A subclass of PermissionPromptPrototype that assumes
- * that this.request is an nsIContentPermissionRequest
- * and fills in some of the required properties on the
- * PermissionPrompt. For callers that are wrapping an
- * nsIContentPermissionRequest, this should be subclassed
- * rather than PermissionPromptPrototype.
- */
-this.PermissionPromptForRequestPrototype = {
- __proto__: PermissionPromptPrototype,
-
- get browser() {
- // In the e10s-case, the <xul:browser> will be at request.element.
- // In the single-process case, we have to use some XPCOM incantations
- // to resolve to the <xul:browser>.
- if (this.request.element) {
- return this.request.element;
- }
- return this.request
- .window
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
- },
-
- get principal() {
- return this.request.principal;
- },
-
- cancel() {
- this.request.cancel();
- },
-
- allow() {
- this.request.allow();
- },
-};
-
-PermissionUI.PermissionPromptForRequestPrototype =
- PermissionPromptForRequestPrototype;
-
-/**
- * Creates a PermissionPrompt for a nsIContentPermissionRequest for
- * the GeoLocation API.
- *
- * @param request (nsIContentPermissionRequest)
- * The request for a permission from content.
- */
-function GeolocationPermissionPrompt(request) {
- this.request = request;
-}
-
-GeolocationPermissionPrompt.prototype = {
- __proto__: PermissionPromptForRequestPrototype,
-
- get permissionKey() {
- return "geo";
- },
-
- get popupOptions() {
- let pref = "browser.geolocation.warning.infoURL";
- return {
- learnMoreURL: Services.urlFormatter.formatURLPref(pref),
- };
- },
-
- get notificationID() {
- return "geolocation";
- },
-
- get anchorID() {
- return "geo-notification-icon";
- },
-
- get message() {
- let message;
- if (this.principal.URI.schemeIs("file")) {
- message = gBrowserBundle.GetStringFromName("geolocation.shareWithFile2");
- } else {
- message = gBrowserBundle.GetStringFromName("geolocation.shareWithSite2");
- }
- return message;
- },
-
- get promptActions() {
- // We collect Telemetry data on Geolocation prompts and how users
- // respond to them. The probe keys are a bit verbose, so let's alias them.
- const SHARE_LOCATION =
- Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION;
- const ALWAYS_SHARE =
- Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE;
- const NEVER_SHARE =
- Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE;
-
- let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
-
- let actions = [{
- label: gBrowserBundle.GetStringFromName("geolocation.shareLocation"),
- accessKey:
- gBrowserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
- action: null,
- expireType: null,
- callback: function() {
- secHistogram.add(SHARE_LOCATION);
- },
- }];
-
- if (!this.principal.URI.schemeIs("file")) {
- // Always share location action.
- actions.push({
- label: gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation"),
- accessKey:
- gBrowserBundle.GetStringFromName("geolocation.alwaysShareLocation.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: null,
- callback: function() {
- secHistogram.add(ALWAYS_SHARE);
- },
- });
-
- // Never share location action.
- actions.push({
- label: gBrowserBundle.GetStringFromName("geolocation.neverShareLocation"),
- accessKey:
- gBrowserBundle.GetStringFromName("geolocation.neverShareLocation.accesskey"),
- action: Ci.nsIPermissionManager.DENY_ACTION,
- expireType: null,
- callback: function() {
- secHistogram.add(NEVER_SHARE);
- },
- });
- }
-
- return actions;
- },
-
- onBeforeShow() {
- let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
- const SHOW_REQUEST = Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST;
- secHistogram.add(SHOW_REQUEST);
- },
-};
-
-PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
-
-/**
- * Creates a PermissionPrompt for a nsIContentPermissionRequest for
- * the Desktop Notification API.
- *
- * @param request (nsIContentPermissionRequest)
- * The request for a permission from content.
- * @return {PermissionPrompt} (see documentation in header)
- */
-function DesktopNotificationPermissionPrompt(request) {
- this.request = request;
-}
-
-DesktopNotificationPermissionPrompt.prototype = {
- __proto__: PermissionPromptForRequestPrototype,
-
- get permissionKey() {
- return "desktop-notification";
- },
-
- get popupOptions() {
- let learnMoreURL =
- Services.urlFormatter.formatURLPref("app.support.baseURL") + "push";
-
- // The eventCallback is bound to the Notification that's being
- // shown. We'll stash a reference to this in the closure so that
- // the request can be cancelled.
- let prompt = this;
-
- let eventCallback = function(type) {
- if (type == "dismissed") {
- // Bug 1259148: Hide the doorhanger icon. Unlike other permission
- // doorhangers, the user can't restore the doorhanger using the icon
- // in the location bar. Instead, the site will be notified that the
- // doorhanger was dismissed.
- this.remove();
- prompt.request.cancel();
- }
- };
-
- return {
- learnMoreURL,
- eventCallback,
- };
- },
-
- get notificationID() {
- return "web-notifications";
- },
-
- get anchorID() {
- return "web-notifications-notification-icon";
- },
-
- get message() {
- return gBrowserBundle.GetStringFromName("webNotifications.receiveFromSite");
- },
-
- get promptActions() {
- let promptActions;
- // Only show "allow for session" in PB mode, we don't
- // support "allow for session" in non-PB mode.
- if (PrivateBrowsingUtils.isBrowserPrivate(this.browser)) {
- promptActions = [
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.receiveForSession"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.receiveForSession.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
- }
- ];
- } else {
- promptActions = [
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.alwaysReceive.accesskey"),
- action: Ci.nsIPermissionManager.ALLOW_ACTION,
- expireType: null,
- },
- {
- label: gBrowserBundle.GetStringFromName("webNotifications.neverShow"),
- accessKey:
- gBrowserBundle.GetStringFromName("webNotifications.neverShow.accesskey"),
- action: Ci.nsIPermissionManager.DENY_ACTION,
- expireType: null,
- },
- ];
- }
-
- return promptActions;
- },
-};
-
-PermissionUI.DesktopNotificationPermissionPrompt =
- DesktopNotificationPermissionPrompt;
diff --git a/browser/modules/PluginContent.jsm b/browser/modules/PluginContent.jsm
deleted file mode 100644
index 622d608bc..000000000
--- a/browser/modules/PluginContent.jsm
+++ /dev/null
@@ -1,1132 +0,0 @@
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "PluginContent" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource://gre/modules/BrowserUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "gNavigatorBundle", function() {
- const url = "chrome://browser/locale/browser.properties";
- return Services.strings.createBundle(url);
-});
-
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-
-this.PluginContent = function (global) {
- this.init(global);
-}
-
-const FLASH_MIME_TYPE = "application/x-shockwave-flash";
-const REPLACEMENT_STYLE_SHEET = Services.io.newURI("chrome://pluginproblem/content/pluginReplaceBinding.css", null, null);
-
-PluginContent.prototype = {
- init: function (global) {
- this.global = global;
- // Need to hold onto the content window or else it'll get destroyed
- this.content = this.global.content;
- // Cache of plugin actions for the current page.
- this.pluginData = new Map();
- // Cache of plugin crash information sent from the parent
- this.pluginCrashData = new Map();
-
- // Note that the XBL binding is untrusted
- global.addEventListener("PluginBindingAttached", this, true, true);
- global.addEventListener("PluginPlaceholderReplaced", this, true, true);
- global.addEventListener("PluginCrashed", this, true);
- global.addEventListener("PluginOutdated", this, true);
- global.addEventListener("PluginInstantiated", this, true);
- global.addEventListener("PluginRemoved", this, true);
- global.addEventListener("pagehide", this, true);
- global.addEventListener("pageshow", this, true);
- global.addEventListener("unload", this);
- global.addEventListener("HiddenPlugin", this, true);
-
- global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
- global.addMessageListener("BrowserPlugins:NotificationShown", this);
- global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
- global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
- global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
- global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
-
- Services.obs.addObserver(this, "decoder-doctor-notification", false);
- },
-
- uninit: function() {
- let global = this.global;
-
- global.removeEventListener("PluginBindingAttached", this, true);
- global.removeEventListener("PluginPlaceholderReplaced", this, true, true);
- global.removeEventListener("PluginCrashed", this, true);
- global.removeEventListener("PluginOutdated", this, true);
- global.removeEventListener("PluginInstantiated", this, true);
- global.removeEventListener("PluginRemoved", this, true);
- global.removeEventListener("pagehide", this, true);
- global.removeEventListener("pageshow", this, true);
- global.removeEventListener("unload", this);
- global.removeEventListener("HiddenPlugin", this, true);
-
- global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
- global.removeMessageListener("BrowserPlugins:NotificationShown", this);
- global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
- global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
- global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
- global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
-
- Services.obs.removeObserver(this, "decoder-doctor-notification");
-
- delete this.global;
- delete this.content;
- },
-
- receiveMessage: function (msg) {
- switch (msg.name) {
- case "BrowserPlugins:ActivatePlugins":
- this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
- break;
- case "BrowserPlugins:NotificationShown":
- setTimeout(() => this.updateNotificationUI(), 0);
- break;
- case "BrowserPlugins:ContextMenuCommand":
- switch (msg.data.command) {
- case "play":
- this._showClickToPlayNotification(msg.objects.plugin, true);
- break;
- case "hide":
- this.hideClickToPlayOverlay(msg.objects.plugin);
- break;
- }
- break;
- case "BrowserPlugins:NPAPIPluginProcessCrashed":
- this.NPAPIPluginProcessCrashed({
- pluginName: msg.data.pluginName,
- runID: msg.data.runID,
- state: msg.data.state,
- });
- break;
- case "BrowserPlugins:CrashReportSubmitted":
- this.NPAPIPluginCrashReportSubmitted({
- runID: msg.data.runID,
- state: msg.data.state,
- })
- break;
- case "BrowserPlugins:Test:ClearCrashData":
- // This message should ONLY ever be sent by automated tests.
- if (Services.prefs.getBoolPref("plugins.testmode")) {
- this.pluginCrashData.clear();
- }
- }
- },
-
- observe: function observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "decoder-doctor-notification":
- let data = JSON.parse(aData);
- if (this.haveShownNotification &&
- aSubject.top.document == this.content.document &&
- data.formats.toLowerCase().includes("application/x-mpegurl", 0)) {
- let principal = this.content.document.nodePrincipal;
- let location = this.content.document.location.href;
- this.global.content.pluginRequiresReload = true;
- this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification",
- { plugins: [... this.pluginData.values()],
- showNow: true,
- location: location,
- }, null, principal);
- }
- }
- },
-
- onPageShow: function (event) {
- // Ignore events that aren't from the main document.
- if (!this.content || event.target != this.content.document) {
- return;
- }
-
- // The PluginClickToPlay events are not fired when navigating using the
- // BF cache. |persisted| is true when the page is loaded from the
- // BF cache, so this code reshows the notification if necessary.
- if (event.persisted) {
- this.reshowClickToPlayNotification();
- }
- },
-
- onPageHide: function (event) {
- // Ignore events that aren't from the main document.
- if (!this.content || event.target != this.content.document) {
- return;
- }
-
- this._finishRecordingFlashPluginTelemetry();
- this.clearPluginCaches();
- this.haveShownNotification = false;
- },
-
- getPluginUI: function (plugin, anonid) {
- return plugin.ownerDocument.
- getAnonymousElementByAttribute(plugin, "anonid", anonid);
- },
-
- _getPluginInfo: function (pluginElement) {
- if (pluginElement instanceof Ci.nsIDOMHTMLAnchorElement) {
- // Anchor elements are our place holders, and we only have them for Flash
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
- return {
- pluginName: "Shockwave Flash",
- mimetype: FLASH_MIME_TYPE,
- permissionString: pluginHost.getPermissionStringForType(FLASH_MIME_TYPE)
- };
- }
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
- pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
-
- let tagMimetype;
- let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
- let pluginTag = null;
- let permissionString = null;
- let fallbackType = null;
- let blocklistState = null;
-
- tagMimetype = pluginElement.actualType;
- if (tagMimetype == "") {
- tagMimetype = pluginElement.type;
- }
-
- if (this.isKnownPlugin(pluginElement)) {
- pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
- pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
-
- // Convert this from nsIPluginTag so it can be serialized.
- let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
- let pluginTagCopy = {};
- for (let prop of properties) {
- pluginTagCopy[prop] = pluginTag[prop];
- }
- pluginTag = pluginTagCopy;
-
- permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
- fallbackType = pluginElement.defaultFallbackType;
- blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
- // Make state-softblocked == state-notblocked for our purposes,
- // they have the same UI. STATE_OUTDATED should not exist for plugin
- // items, but let's alias it anyway, just in case.
- if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
- blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
- blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
- }
- }
-
- return { mimetype: tagMimetype,
- pluginName: pluginName,
- pluginTag: pluginTag,
- permissionString: permissionString,
- fallbackType: fallbackType,
- blocklistState: blocklistState,
- };
- },
-
- _getPluginInfoForTag: function (pluginTag, tagMimetype) {
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-
- let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
- let permissionString = null;
- let blocklistState = null;
-
- if (pluginTag) {
- pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
-
- permissionString = pluginHost.getPermissionStringForTag(pluginTag);
- blocklistState = pluginTag.blocklistState;
-
- // Convert this from nsIPluginTag so it can be serialized.
- let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
- let pluginTagCopy = {};
- for (let prop of properties) {
- pluginTagCopy[prop] = pluginTag[prop];
- }
- pluginTag = pluginTagCopy;
-
- // Make state-softblocked == state-notblocked for our purposes,
- // they have the same UI. STATE_OUTDATED should not exist for plugin
- // items, but let's alias it anyway, just in case.
- if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
- blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
- blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
- }
- }
-
- return { mimetype: tagMimetype,
- pluginName: pluginName,
- pluginTag: pluginTag,
- permissionString: permissionString,
- fallbackType: null,
- blocklistState: blocklistState,
- };
- },
-
- /**
- * Update the visibility of the plugin overlay.
- */
- setVisibility : function (plugin, overlay, shouldShow) {
- overlay.classList.toggle("visible", shouldShow);
- if (shouldShow) {
- overlay.removeAttribute("dismissed");
- }
- },
-
- /**
- * Check whether the plugin should be visible on the page. A plugin should
- * not be visible if the overlay is too big, or if any other page content
- * overlays it.
- *
- * This function will handle showing or hiding the overlay.
- * @returns true if the plugin is invisible.
- */
- shouldShowOverlay : function (plugin, overlay) {
- // If the overlay size is 0, we haven't done layout yet. Presume that
- // plugins are visible until we know otherwise.
- if (overlay.scrollWidth == 0) {
- return true;
- }
-
- // Is the <object>'s size too small to hold what we want to show?
- let pluginRect = plugin.getBoundingClientRect();
- // XXX bug 446693. The text-shadow on the submitted-report text at
- // the bottom causes scrollHeight to be larger than it should be.
- let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
- (overlay.scrollHeight - 5 > Math.ceil(pluginRect.height));
- if (overflows) {
- return false;
- }
-
- // Is the plugin covered up by other content so that it is not clickable?
- // Floating point can confuse .elementFromPoint, so inset just a bit
- let left = pluginRect.left + 2;
- let right = pluginRect.right - 2;
- let top = pluginRect.top + 2;
- let bottom = pluginRect.bottom - 2;
- let centerX = left + (right - left) / 2;
- let centerY = top + (bottom - top) / 2;
- let points = [[left, top],
- [left, bottom],
- [right, top],
- [right, bottom],
- [centerX, centerY]];
-
- if (right <= 0 || top <= 0) {
- return false;
- }
-
- let contentWindow = plugin.ownerGlobal;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
-
- for (let [x, y] of points) {
- let el = cwu.elementFromPoint(x, y, true, true);
- if (el !== plugin) {
- return false;
- }
- }
-
- return true;
- },
-
- addLinkClickCallback: function (linkNode, callbackName /* callbackArgs...*/) {
- // XXX just doing (callback)(arg) was giving a same-origin error. bug?
- let self = this;
- let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
- linkNode.addEventListener("click",
- function(evt) {
- if (!evt.isTrusted)
- return;
- evt.preventDefault();
- if (callbackArgs.length == 0)
- callbackArgs = [ evt ];
- (self[callbackName]).apply(self, callbackArgs);
- },
- true);
-
- linkNode.addEventListener("keydown",
- function(evt) {
- if (!evt.isTrusted)
- return;
- if (evt.keyCode == evt.DOM_VK_RETURN) {
- evt.preventDefault();
- if (callbackArgs.length == 0)
- callbackArgs = [ evt ];
- evt.preventDefault();
- (self[callbackName]).apply(self, callbackArgs);
- }
- },
- true);
- },
-
- // Helper to get the binding handler type from a plugin object
- _getBindingType : function(plugin) {
- if (!(plugin instanceof Ci.nsIObjectLoadingContent))
- return null;
-
- switch (plugin.pluginFallbackType) {
- case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
- return "PluginNotFound";
- case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
- return "PluginDisabled";
- case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
- return "PluginBlocklisted";
- case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
- return "PluginOutdated";
- case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
- return "PluginClickToPlay";
- case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
- return "PluginVulnerableUpdatable";
- case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
- return "PluginVulnerableNoUpdate";
- default:
- // Not all states map to a handler
- return null;
- }
- },
-
- handleEvent: function (event) {
- let eventType = event.type;
-
- if (eventType == "unload") {
- this.uninit();
- return;
- }
-
- if (eventType == "pagehide") {
- this.onPageHide(event);
- return;
- }
-
- if (eventType == "pageshow") {
- this.onPageShow(event);
- return;
- }
-
- if (eventType == "PluginRemoved") {
- this.updateNotificationUI(event.target);
- return;
- }
-
- if (eventType == "click") {
- this.onOverlayClick(event);
- return;
- }
-
- if (eventType == "PluginCrashed" &&
- !(event.target instanceof Ci.nsIObjectLoadingContent)) {
- // If the event target is not a plugin object (i.e., an <object> or
- // <embed> element), this call is for a window-global plugin.
- this.onPluginCrashed(event.target, event);
- return;
- }
-
- if (eventType == "HiddenPlugin") {
- let win = event.target.defaultView;
- if (!win.mozHiddenPluginTouched) {
- let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
- if (win.top.document != this.content.document) {
- return;
- }
- this._showClickToPlayNotification(pluginTag, false);
- let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
- try {
- winUtils.loadSheet(REPLACEMENT_STYLE_SHEET, win.AGENT_SHEET);
- win.mozHiddenPluginTouched = true;
- } catch (e) {
- Cu.reportError("Error adding plugin replacement style sheet: " + e);
- }
- }
- }
-
- let plugin = event.target;
-
- if (eventType == "PluginPlaceholderReplaced") {
- plugin.removeAttribute("href");
- let overlay = this.getPluginUI(plugin, "main");
- this.setVisibility(plugin, overlay, true);
- let inIDOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
- .getService(Ci.inIDOMUtils);
- // Add psuedo class so our styling will take effect
- inIDOMUtils.addPseudoClassLock(plugin, "-moz-handler-clicktoplay");
- overlay.addEventListener("click", this, true);
- return;
- }
-
- if (!(plugin instanceof Ci.nsIObjectLoadingContent))
- return;
-
- if (eventType == "PluginBindingAttached") {
- // The plugin binding fires this event when it is created.
- // As an untrusted event, ensure that this object actually has a binding
- // and make sure we don't handle it twice
- let overlay = this.getPluginUI(plugin, "main");
- if (!overlay || overlay._bindingHandled) {
- return;
- }
- overlay._bindingHandled = true;
-
- // Lookup the handler for this binding
- eventType = this._getBindingType(plugin);
- if (!eventType) {
- // Not all bindings have handlers
- return;
- }
- }
-
- let shouldShowNotification = false;
- switch (eventType) {
- case "PluginCrashed":
- this.onPluginCrashed(plugin, event);
- break;
-
- case "PluginNotFound": {
- /* NOP */
- break;
- }
-
- case "PluginBlocklisted":
- case "PluginOutdated":
- shouldShowNotification = true;
- break;
-
- case "PluginVulnerableUpdatable":
- let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
- let { pluginTag } = this._getPluginInfo(plugin);
- this.addLinkClickCallback(updateLink, "forwardCallback",
- "openPluginUpdatePage", pluginTag);
- /* FALLTHRU */
-
- case "PluginVulnerableNoUpdate":
- case "PluginClickToPlay":
- this._handleClickToPlayEvent(plugin);
- let pluginName = this._getPluginInfo(plugin).pluginName;
- let messageString = gNavigatorBundle.formatStringFromName("PluginClickToActivate", [pluginName], 1);
- let overlayText = this.getPluginUI(plugin, "clickToPlay");
- overlayText.textContent = messageString;
- if (eventType == "PluginVulnerableUpdatable" ||
- eventType == "PluginVulnerableNoUpdate") {
- let vulnerabilityString = gNavigatorBundle.GetStringFromName(eventType);
- let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
- vulnerabilityText.textContent = vulnerabilityString;
- }
- shouldShowNotification = true;
- break;
-
- case "PluginDisabled":
- let manageLink = this.getPluginUI(plugin, "managePluginsLink");
- this.addLinkClickCallback(manageLink, "forwardCallback", "managePlugins");
- shouldShowNotification = true;
- break;
-
- case "PluginInstantiated":
- let key = this._getPluginInfo(plugin).pluginTag.niceName;
- Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(key);
- shouldShowNotification = true;
- let pluginRect = plugin.getBoundingClientRect();
- if (pluginRect.width <= 5 && pluginRect.height <= 5) {
- Services.telemetry.getHistogramById('PLUGIN_TINY_CONTENT').add(1);
- }
- break;
- }
-
- if (this._getPluginInfo(plugin).mimetype === FLASH_MIME_TYPE) {
- this._recordFlashPluginTelemetry(eventType, plugin);
- }
-
- // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
- let overlay = this.getPluginUI(plugin, "main");
- if (eventType != "PluginCrashed") {
- if (overlay != null) {
- this.setVisibility(plugin, overlay,
- this.shouldShowOverlay(plugin, overlay));
- let resizeListener = (event) => {
- this.setVisibility(plugin, overlay,
- this.shouldShowOverlay(plugin, overlay));
- this.updateNotificationUI();
- };
- plugin.addEventListener("overflow", resizeListener);
- plugin.addEventListener("underflow", resizeListener);
- }
- }
-
- let closeIcon = this.getPluginUI(plugin, "closeIcon");
- if (closeIcon) {
- closeIcon.addEventListener("click", event => {
- if (event.button == 0 && event.isTrusted) {
- this.hideClickToPlayOverlay(plugin);
- overlay.setAttribute("dismissed", "true");
- }
- }, true);
- }
-
- if (shouldShowNotification) {
- this._showClickToPlayNotification(plugin, false);
- }
- },
-
- _recordFlashPluginTelemetry: function (eventType, plugin) {
- if (!Services.telemetry.canRecordExtended) {
- return;
- }
-
- if (!this.flashPluginStats) {
- this.flashPluginStats = {
- instancesCount: 0,
- plugins: new WeakSet()
- };
- }
-
- if (!this.flashPluginStats.plugins.has(plugin)) {
- // Reporting plugin instance and its dimensions only once.
- this.flashPluginStats.plugins.add(plugin);
-
- this.flashPluginStats.instancesCount++;
-
- let pluginRect = plugin.getBoundingClientRect();
- Services.telemetry.getHistogramById('FLASH_PLUGIN_WIDTH')
- .add(pluginRect.width);
- Services.telemetry.getHistogramById('FLASH_PLUGIN_HEIGHT')
- .add(pluginRect.height);
- Services.telemetry.getHistogramById('FLASH_PLUGIN_AREA')
- .add(pluginRect.width * pluginRect.height);
-
- let state = this._getPluginInfo(plugin).fallbackType;
- if (state === null) {
- state = Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED;
- }
- Services.telemetry.getHistogramById('FLASH_PLUGIN_STATES')
- .add(state);
- }
- },
-
- _finishRecordingFlashPluginTelemetry: function () {
- if (this.flashPluginStats) {
- Services.telemetry.getHistogramById('FLASH_PLUGIN_INSTANCES_ON_PAGE')
- .add(this.flashPluginStats.instancesCount);
- delete this.flashPluginStats;
- }
- },
-
- isKnownPlugin: function (objLoadingContent) {
- return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
- Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
- },
-
- canActivatePlugin: function (objLoadingContent) {
- // if this isn't a known plugin, we can't activate it
- // (this also guards pluginHost.getPermissionStringForType against
- // unexpected input)
- if (!this.isKnownPlugin(objLoadingContent))
- return false;
-
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
- let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
- let principal = objLoadingContent.ownerGlobal.top.document.nodePrincipal;
- let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
-
- let isFallbackTypeValid =
- objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
- objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
-
- return !objLoadingContent.activated &&
- pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
- isFallbackTypeValid;
- },
-
- hideClickToPlayOverlay: function (plugin) {
- let overlay = this.getPluginUI(plugin, "main");
- if (overlay) {
- overlay.classList.remove("visible");
- }
- },
-
- // Forward a link click callback to the chrome process.
- forwardCallback: function (name, pluginTag) {
- this.global.sendAsyncMessage("PluginContent:LinkClickCallback",
- { name, pluginTag });
- },
-
- submitReport: function submitReport(plugin) {
- /*** STUB ***/
- return;
- },
-
- reloadPage: function () {
- this.global.content.location.reload();
- },
-
- // Event listener for click-to-play plugins.
- _handleClickToPlayEvent: function (plugin) {
- let doc = plugin.ownerDocument;
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
- let permissionString;
- if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) {
- // We only have replacement content for Flash installs
- permissionString = pluginHost.getPermissionStringForType(FLASH_MIME_TYPE);
- } else {
- let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- // guard against giving pluginHost.getPermissionStringForType a type
- // not associated with any known plugin
- if (!this.isKnownPlugin(objLoadingContent))
- return;
- permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
- }
-
- let principal = doc.defaultView.top.document.nodePrincipal;
- let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
-
- let overlay = this.getPluginUI(plugin, "main");
-
- if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
- if (overlay) {
- overlay.classList.remove("visible");
- }
- return;
- }
-
- if (overlay) {
- overlay.addEventListener("click", this, true);
- }
- },
-
- onOverlayClick: function (event) {
- let document = event.target.ownerDocument;
- let plugin = document.getBindingParent(event.target);
- let contentWindow = plugin.ownerGlobal.top;
- let overlay = this.getPluginUI(plugin, "main");
- // Have to check that the target is not the link to update the plugin
- if (!(event.originalTarget instanceof contentWindow.HTMLAnchorElement) &&
- (event.originalTarget.getAttribute('anonid') != 'closeIcon') &&
- !overlay.hasAttribute('dismissed') &&
- event.button == 0 &&
- event.isTrusted) {
- this._showClickToPlayNotification(plugin, true);
- event.stopPropagation();
- event.preventDefault();
- }
- },
-
- reshowClickToPlayNotification: function () {
- let contentWindow = this.global.content;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let plugins = cwu.plugins;
- for (let plugin of plugins) {
- let overlay = this.getPluginUI(plugin, "main");
- if (overlay)
- overlay.removeEventListener("click", this, true);
- let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- if (this.canActivatePlugin(objLoadingContent))
- this._handleClickToPlayEvent(plugin);
- }
- this._showClickToPlayNotification(null, false);
- },
-
- /**
- * Activate the plugins that the user has specified.
- */
- activatePlugins: function (pluginInfo, newState) {
- let contentWindow = this.global.content;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let plugins = cwu.plugins;
- let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-
- let pluginFound = false;
- let placeHolderFound = false;
- for (let plugin of plugins) {
- plugin.QueryInterface(Ci.nsIObjectLoadingContent);
- if (!this.isKnownPlugin(plugin)) {
- continue;
- }
- if (pluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
- let overlay = this.getPluginUI(plugin, "main");
- if (plugin instanceof Ci.nsIDOMHTMLAnchorElement) {
- placeHolderFound = true;
- } else {
- pluginFound = true;
- }
- if (newState == "block") {
- if (overlay) {
- overlay.addEventListener("click", this, true);
- }
- plugin.reload(true);
- } else if (this.canActivatePlugin(plugin)) {
- if (overlay) {
- overlay.removeEventListener("click", this, true);
- }
- plugin.playPlugin();
- }
- }
- }
-
- // If there are no instances of the plugin on the page any more, what the
- // user probably needs is for us to allow and then refresh. Additionally, if
- // this is content that requires HLS or we replaced the placeholder the page
- // needs to be refreshed for it to insert its plugins
- if (newState != "block" &&
- (!pluginFound || placeHolderFound || contentWindow.pluginRequiresReload)) {
- this.reloadPage();
- }
- this.updateNotificationUI();
- },
-
- _showClickToPlayNotification: function (plugin, showNow) {
- let plugins = [];
-
- // If plugin is null, that means the user has navigated back to a page with
- // plugins, and we need to collect all the plugins.
- if (plugin === null) {
- let contentWindow = this.content;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- // cwu.plugins may contain non-plugin <object>s, filter them out
- plugins = cwu.plugins.filter((plugin) =>
- plugin.getContentTypeForMIMEType(plugin.actualType) == Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
-
- if (plugins.length == 0) {
- this.removeNotification("click-to-play-plugins");
- return;
- }
- } else {
- plugins = [plugin];
- }
-
- let pluginData = this.pluginData;
-
- let principal = this.content.document.nodePrincipal;
- let location = this.content.document.location.href;
-
- for (let p of plugins) {
- let pluginInfo;
- if (p instanceof Ci.nsIPluginTag) {
- let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
- pluginInfo = this._getPluginInfoForTag(p, mimeType);
- } else {
- pluginInfo = this._getPluginInfo(p);
- }
- if (pluginInfo.permissionString === null) {
- Cu.reportError("No permission string for active plugin.");
- continue;
- }
- if (pluginData.has(pluginInfo.permissionString)) {
- continue;
- }
-
- let permissionObj = Services.perms.
- getPermissionObject(principal, pluginInfo.permissionString, false);
- if (permissionObj) {
- pluginInfo.pluginPermissionPrePath = permissionObj.principal.originNoSuffix;
- pluginInfo.pluginPermissionType = permissionObj.expireType;
- }
- else {
- pluginInfo.pluginPermissionPrePath = principal.originNoSuffix;
- pluginInfo.pluginPermissionType = undefined;
- }
-
- this.pluginData.set(pluginInfo.permissionString, pluginInfo);
- }
-
- this.haveShownNotification = true;
-
- this.global.sendAsyncMessage("PluginContent:ShowClickToPlayNotification", {
- plugins: [... this.pluginData.values()],
- showNow: showNow,
- location: location,
- }, null, principal);
- },
-
- /**
- * Updates the "hidden plugin" notification bar UI.
- *
- * @param document (optional)
- * Specify the document that is causing the update.
- * This is useful when the document is possibly no longer
- * the current loaded document (for example, if we're
- * responding to a PluginRemoved event for an unloading
- * document). If this parameter is omitted, it defaults
- * to the current top-level document.
- */
- updateNotificationUI: function (document) {
- document = document || this.content.document;
-
- // We're only interested in the top-level document, since that's
- // the one that provides the Principal that we send back to the
- // parent.
- let principal = document.defaultView.top.document.nodePrincipal;
- let location = document.location.href;
-
- // Make a copy of the actions from the last popup notification.
- let haveInsecure = false;
- let actions = new Map();
- for (let action of this.pluginData.values()) {
- switch (action.fallbackType) {
- // haveInsecure will trigger the red flashing icon and the infobar
- // styling below
- case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
- case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
- haveInsecure = true;
- // fall through
-
- case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
- actions.set(action.permissionString, action);
- continue;
- }
- }
-
- // Remove plugins that are already active, or large enough to show an overlay.
- let cwu = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- for (let plugin of cwu.plugins) {
- let info = this._getPluginInfo(plugin);
- if (!actions.has(info.permissionString)) {
- continue;
- }
- let fallbackType = info.fallbackType;
- if (fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
- actions.delete(info.permissionString);
- if (actions.size == 0) {
- break;
- }
- continue;
- }
- if (fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
- fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE &&
- fallbackType != Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE) {
- continue;
- }
- let overlay = this.getPluginUI(plugin, "main");
- if (!overlay) {
- continue;
- }
- let shouldShow = this.shouldShowOverlay(plugin, overlay);
- this.setVisibility(plugin, overlay, shouldShow);
- if (shouldShow) {
- actions.delete(info.permissionString);
- if (actions.size == 0) {
- break;
- }
- }
- }
-
- // If there are any items remaining in `actions` now, they are hidden
- // plugins that need a notification bar.
- this.global.sendAsyncMessage("PluginContent:UpdateHiddenPluginUI", {
- haveInsecure: haveInsecure,
- actions: [... actions.values()],
- location: location,
- }, null, principal);
- },
-
- removeNotification: function (name) {
- this.global.sendAsyncMessage("PluginContent:RemoveNotification", { name: name });
- },
-
- clearPluginCaches: function () {
- this.pluginData.clear();
- this.pluginCrashData.clear();
- },
-
- hideNotificationBar: function (name) {
- this.global.sendAsyncMessage("PluginContent:HideNotificationBar", { name: name });
- },
-
- /**
- * The PluginCrashed event handler. Note that the PluginCrashed event is
- * fired for both NPAPI and Gecko Media plugins. In the latter case, the
- * target of the event is the document that the GMP is being used in.
- */
- onPluginCrashed: function (target, aEvent) {
- if (!(aEvent instanceof this.content.PluginCrashedEvent))
- return;
-
- if (aEvent.gmpPlugin) {
- this.GMPCrashed(aEvent);
- return;
- }
-
- if (!(target instanceof Ci.nsIObjectLoadingContent))
- return;
-
- let crashData = this.pluginCrashData.get(target.runID);
- if (!crashData) {
- // We haven't received information from the parent yet about
- // this crash, so we should hold off showing the crash report
- // UI.
- return;
- }
-
- crashData.instances.delete(target);
- if (crashData.instances.length == 0) {
- this.pluginCrashData.delete(target.runID);
- }
-
- this.setCrashedNPAPIPluginState({
- plugin: target,
- state: crashData.state,
- message: crashData.message,
- });
- },
-
- NPAPIPluginProcessCrashed: function ({pluginName, runID, state}) {
- let message =
- gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
- [pluginName], 1);
-
- let contentWindow = this.global.content;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let plugins = cwu.plugins;
-
- for (let plugin of plugins) {
- if (plugin instanceof Ci.nsIObjectLoadingContent &&
- plugin.runID == runID) {
- // The parent has told us that the plugin process has died.
- // It's possible that this content process hasn't yet noticed,
- // in which case we need to stash this data around until the
- // PluginCrashed events get sent up.
- if (plugin.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CRASHED) {
- // This plugin has already been put into the crashed state by the
- // content process, so we can tweak its crash UI without delay.
- this.setCrashedNPAPIPluginState({plugin, state, message});
- } else {
- // The content process hasn't yet determined that the plugin has crashed.
- // Stash the data in our map, and throw the plugin into a WeakSet. When
- // the PluginCrashed event fires on the <object>/<embed>, we'll retrieve
- // the information we need from the Map and remove the instance from the
- // WeakSet. Once the WeakSet is empty, we can clear the map.
- if (!this.pluginCrashData.has(runID)) {
- this.pluginCrashData.set(runID, {
- state: state,
- message: message,
- instances: new WeakSet(),
- });
- }
- let crashData = this.pluginCrashData.get(runID);
- crashData.instances.add(plugin);
- }
- }
- }
- },
-
- setCrashedNPAPIPluginState: function ({plugin, state, message}) {
- // Force a layout flush so the binding is attached.
- plugin.clientTop;
- let overlay = this.getPluginUI(plugin, "main");
- let statusDiv = this.getPluginUI(plugin, "submitStatus");
- let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
-
- this.getPluginUI(plugin, "submitButton")
- .addEventListener("click", (event) => {
- if (event.button != 0 || !event.isTrusted)
- return;
- this.submitReport(plugin);
- });
-
- let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
- optInCB.checked = pref.getBoolPref("");
-
- statusDiv.setAttribute("status", state);
-
- let helpIcon = this.getPluginUI(plugin, "helpIcon");
- this.addLinkClickCallback(helpIcon, "openHelpPage");
-
- let crashText = this.getPluginUI(plugin, "crashedText");
- crashText.textContent = message;
-
- let link = this.getPluginUI(plugin, "reloadLink");
- this.addLinkClickCallback(link, "reloadPage");
-
- let isShowing = this.shouldShowOverlay(plugin, overlay);
-
- // Is the <object>'s size too small to hold what we want to show?
- if (!isShowing) {
- // First try hiding the crash report submission UI.
- statusDiv.removeAttribute("status");
-
- isShowing = this.shouldShowOverlay(plugin, overlay);
- }
- this.setVisibility(plugin, overlay, isShowing);
-
- let doc = plugin.ownerDocument;
- let runID = plugin.runID;
-
- if (isShowing) {
- // If a previous plugin on the page was too small and resulted in adding a
- // notification bar, then remove it because this plugin instance it big
- // enough to serve as in-content notification.
- this.hideNotificationBar("plugin-crashed");
- doc.mozNoPluginCrashedNotification = true;
-
- // Notify others that the crash reporter UI is now ready.
- // Currently, this event is only used by tests.
- let winUtils = this.content.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let event = new this.content.CustomEvent("PluginCrashReporterDisplayed", {bubbles: true});
- winUtils.dispatchEventToChromeOnly(plugin, event);
- } else if (!doc.mozNoPluginCrashedNotification) {
- // If another plugin on the page was large enough to show our UI, we don't
- // want to show a notification bar.
- this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
- { messageString: message, pluginID: runID });
- // Remove the notification when the page is reloaded.
- doc.defaultView.top.addEventListener("unload", event => {
- this.hideNotificationBar("plugin-crashed");
- }, false);
- }
- },
-
- NPAPIPluginCrashReportSubmitted: function({ runID, state }) {
- this.pluginCrashData.delete(runID);
- let contentWindow = this.global.content;
- let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- let plugins = cwu.plugins;
-
- for (let plugin of plugins) {
- if (plugin instanceof Ci.nsIObjectLoadingContent &&
- plugin.runID == runID) {
- let statusDiv = this.getPluginUI(plugin, "submitStatus");
- statusDiv.setAttribute("status", state);
- }
- }
- },
-
- GMPCrashed: function(aEvent) {
- let target = aEvent.target;
- let pluginName = aEvent.pluginName;
- let gmpPlugin = aEvent.gmpPlugin;
- let pluginID = aEvent.pluginID;
- let doc = target.document;
-
- if (!gmpPlugin || !doc) {
- // TODO: Throw exception? How did we get here?
- return;
- }
-
- let messageString =
- gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
- [pluginName], 1);
-
- this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
- { messageString, pluginID });
-
- // Remove the notification when the page is reloaded.
- doc.defaultView.top.addEventListener("unload", event => {
- this.hideNotificationBar("plugin-crashed");
- }, false);
- },
-};
diff --git a/browser/modules/ProcessHangMonitor.jsm b/browser/modules/ProcessHangMonitor.jsm
deleted file mode 100644
index e048f5b40..000000000
--- a/browser/modules/ProcessHangMonitor.jsm
+++ /dev/null
@@ -1,397 +0,0 @@
-/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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;
-
-this.EXPORTED_SYMBOLS = ["ProcessHangMonitor"];
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-/**
- * This JSM is responsible for observing content process hang reports
- * and asking the user what to do about them. See nsIHangReport for
- * the platform interface.
- */
-
-var ProcessHangMonitor = {
- /**
- * This timeout is the wait period applied after a user selects "Wait" in
- * an existing notification.
- */
- get WAIT_EXPIRATION_TIME() {
- try {
- return Services.prefs.getIntPref("browser.hangNotification.waitPeriod");
- } catch (ex) {
- return 10000;
- }
- },
-
- /**
- * Collection of hang reports that haven't expired or been dismissed
- * by the user. These are nsIHangReports.
- */
- _activeReports: new Set(),
-
- /**
- * Collection of hang reports that have been suppressed for a short
- * period of time. Value is an nsITimer for when the wait time
- * expires.
- */
- _pausedReports: new Map(),
-
- /**
- * Initialize hang reporting. Called once in the parent process.
- */
- init: function() {
- Services.obs.addObserver(this, "process-hang-report", false);
- Services.obs.addObserver(this, "clear-hang-report", false);
- Services.obs.addObserver(this, "xpcom-shutdown", false);
- Services.ww.registerNotification(this);
- },
-
- /**
- * Terminate JavaScript associated with the hang being reported for
- * the selected browser in |win|.
- */
- terminateScript: function(win) {
- this.handleUserInput(win, report => report.terminateScript());
- },
-
- /**
- * Start devtools debugger for JavaScript associated with the hang
- * being reported for the selected browser in |win|.
- */
- debugScript: function(win) {
- this.handleUserInput(win, report => {
- function callback() {
- report.endStartingDebugger();
- }
-
- report.beginStartingDebugger();
-
- let svc = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(Ci.nsISlowScriptDebug);
- let handler = svc.remoteActivationHandler;
- handler.handleSlowScriptDebug(report.scriptBrowser, callback);
- });
- },
-
- /**
- * Terminate the plugin process associated with a hang being reported
- * for the selected browser in |win|. Will attempt to generate a combined
- * crash report for all processes.
- */
- terminatePlugin: function(win) {
- this.handleUserInput(win, report => report.terminatePlugin());
- },
-
- /**
- * Dismiss the browser notification and invoke an appropriate action based on
- * the hang type.
- */
- stopIt: function (win) {
- let report = this.findActiveReport(win.gBrowser.selectedBrowser);
- if (!report) {
- return;
- }
-
- switch (report.hangType) {
- case report.SLOW_SCRIPT:
- this.terminateScript(win);
- break;
- case report.PLUGIN_HANG:
- this.terminatePlugin(win);
- break;
- }
- },
-
- /**
- * Dismiss the notification, clear the report from the active list and set up
- * a new timer to track a wait period during which we won't notify.
- */
- waitLonger: function(win) {
- let report = this.findActiveReport(win.gBrowser.selectedBrowser);
- if (!report) {
- return;
- }
- // Remove the report from the active list.
- this.removeActiveReport(report);
-
- // NOTE, we didn't call userCanceled on nsIHangReport here. This insures
- // we don't repeatedly generate and cache crash report data for this hang
- // in the process hang reporter. It already has one report for the browser
- // process we want it hold onto.
-
- // Create a new wait timer with notify callback
- let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- timer.initWithCallback(() => {
- for (let [stashedReport, otherTimer] of this._pausedReports) {
- if (otherTimer === timer) {
- this.removePausedReport(stashedReport);
-
- // We're still hung, so move the report back to the active
- // list and update the UI.
- this._activeReports.add(report);
- this.updateWindows();
- break;
- }
- }
- }, this.WAIT_EXPIRATION_TIME, timer.TYPE_ONE_SHOT);
-
- this._pausedReports.set(report, timer);
-
- // remove the browser notification associated with this hang
- this.updateWindows();
- },
-
- /**
- * If there is a hang report associated with the selected browser in
- * |win|, invoke |func| on that report and stop notifying the user
- * about it.
- */
- handleUserInput: function(win, func) {
- let report = this.findActiveReport(win.gBrowser.selectedBrowser);
- if (!report) {
- return null;
- }
- this.removeActiveReport(report);
-
- return func(report);
- },
-
- observe: function(subject, topic, data) {
- switch (topic) {
- case "xpcom-shutdown":
- Services.obs.removeObserver(this, "xpcom-shutdown");
- Services.obs.removeObserver(this, "process-hang-report");
- Services.obs.removeObserver(this, "clear-hang-report");
- Services.ww.unregisterNotification(this);
- break;
-
- case "process-hang-report":
- this.reportHang(subject.QueryInterface(Ci.nsIHangReport));
- break;
-
- case "clear-hang-report":
- this.clearHang(subject.QueryInterface(Ci.nsIHangReport));
- break;
-
- case "domwindowopened":
- // Install event listeners on the new window in case one of
- // its tabs is already hung.
- let win = subject.QueryInterface(Ci.nsIDOMWindow);
- let listener = (ev) => {
- win.removeEventListener("load", listener, true);
- this.updateWindows();
- };
- win.addEventListener("load", listener, true);
- break;
- }
- },
-
- /**
- * Find a active hang report for the given <browser> element.
- */
- findActiveReport: function(browser) {
- let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
- for (let report of this._activeReports) {
- if (report.isReportForBrowser(frameLoader)) {
- return report;
- }
- }
- return null;
- },
-
- /**
- * Find a paused hang report for the given <browser> element.
- */
- findPausedReport: function(browser) {
- let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
- for (let [report, ] of this._pausedReports) {
- if (report.isReportForBrowser(frameLoader)) {
- return report;
- }
- }
- return null;
- },
-
- /**
- * Remove an active hang report from the active list and cancel the timer
- * associated with it.
- */
- removeActiveReport: function(report) {
- this._activeReports.delete(report);
- this.updateWindows();
- },
-
- /**
- * Remove a paused hang report from the paused list and cancel the timer
- * associated with it.
- */
- removePausedReport: function(report) {
- let timer = this._pausedReports.get(report);
- if (timer) {
- timer.cancel();
- }
- this._pausedReports.delete(report);
- },
-
- /**
- * Iterate over all XUL windows and ensure that the proper hang
- * reports are shown for each one. Also install event handlers in
- * each window to watch for events that would cause a different hang
- * report to be displayed.
- */
- updateWindows: function() {
- let e = Services.wm.getEnumerator("navigator:browser");
- while (e.hasMoreElements()) {
- let win = e.getNext();
-
- this.updateWindow(win);
-
- // Only listen for these events if there are active hang reports.
- if (this._activeReports.size) {
- this.trackWindow(win);
- } else {
- this.untrackWindow(win);
- }
- }
- },
-
- /**
- * If there is a hang report for the current tab in |win|, display it.
- */
- updateWindow: function(win) {
- let report = this.findActiveReport(win.gBrowser.selectedBrowser);
-
- if (report) {
- this.showNotification(win, report);
- } else {
- this.hideNotification(win);
- }
- },
-
- /**
- * Show the notification for a hang.
- */
- showNotification: function(win, report) {
- let nb = win.document.getElementById("high-priority-global-notificationbox");
- let notification = nb.getNotificationWithValue("process-hang");
- if (notification) {
- return;
- }
-
- let bundle = win.gNavigatorBundle;
-
- let buttons = [{
- label: bundle.getString("processHang.button_stop.label"),
- accessKey: bundle.getString("processHang.button_stop.accessKey"),
- callback: function() {
- ProcessHangMonitor.stopIt(win);
- }
- },
- {
- label: bundle.getString("processHang.button_wait.label"),
- accessKey: bundle.getString("processHang.button_wait.accessKey"),
- callback: function() {
- ProcessHangMonitor.waitLonger(win);
- }
- }];
-
- if (AppConstants.MOZ_DEV_EDITION && report.hangType == report.SLOW_SCRIPT) {
- buttons.push({
- label: bundle.getString("processHang.button_debug.label"),
- accessKey: bundle.getString("processHang.button_debug.accessKey"),
- callback: function() {
- ProcessHangMonitor.debugScript(win);
- }
- });
- }
-
- nb.appendNotification(bundle.getString("processHang.label"),
- "process-hang",
- "chrome://browser/content/aboutRobots-icon.png",
- nb.PRIORITY_WARNING_HIGH, buttons);
- },
-
- /**
- * Ensure that no hang notifications are visible in |win|.
- */
- hideNotification: function(win) {
- let nb = win.document.getElementById("high-priority-global-notificationbox");
- let notification = nb.getNotificationWithValue("process-hang");
- if (notification) {
- nb.removeNotification(notification);
- }
- },
-
- /**
- * Install event handlers on |win| to watch for events that would
- * cause a different hang report to be displayed.
- */
- trackWindow: function(win) {
- win.gBrowser.tabContainer.addEventListener("TabSelect", this, true);
- win.gBrowser.tabContainer.addEventListener("TabRemotenessChange", this, true);
- },
-
- untrackWindow: function(win) {
- win.gBrowser.tabContainer.removeEventListener("TabSelect", this, true);
- win.gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this, true);
- },
-
- handleEvent: function(event) {
- let win = event.target.ownerGlobal;
-
- // If a new tab is selected or if a tab changes remoteness, then
- // we may need to show or hide a hang notification.
-
- if (event.type == "TabSelect" || event.type == "TabRemotenessChange") {
- this.updateWindow(win);
- }
- },
-
- /**
- * Handle a potentially new hang report. If it hasn't been seen
- * before, show a notification for it in all open XUL windows.
- */
- reportHang: function(report) {
- // If this hang was already reported reset the timer for it.
- if (this._activeReports.has(report)) {
- // if this report is in active but doesn't have a notification associated
- // with it, display a notification.
- this.updateWindows();
- return;
- }
-
- // If this hang was already reported and paused by the user ignore it.
- if (this._pausedReports.has(report)) {
- return;
- }
-
- // On e10s this counts slow-script/hanged-plugin notice only once.
- // This code is not reached on non-e10s.
- if (report.hangType == report.SLOW_SCRIPT) {
- // On non-e10s, SLOW_SCRIPT_NOTICE_COUNT is probed at nsGlobalWindow.cpp
- Services.telemetry.getHistogramById("SLOW_SCRIPT_NOTICE_COUNT").add();
- } else if (report.hangType == report.PLUGIN_HANG) {
- // On non-e10s we have sufficient plugin telemetry probes,
- // so PLUGIN_HANG_NOTICE_COUNT is only probed on e10s.
- Services.telemetry.getHistogramById("PLUGIN_HANG_NOTICE_COUNT").add();
- }
-
- this._activeReports.add(report);
- this.updateWindows();
- },
-
- clearHang: function(report) {
- this.removeActiveReport(report);
- this.removePausedReport(report);
- report.userCanceled();
- },
-};
diff --git a/browser/modules/ReaderParent.jsm b/browser/modules/ReaderParent.jsm
deleted file mode 100644
index c81aa9905..000000000
--- a/browser/modules/ReaderParent.jsm
+++ /dev/null
@@ -1,102 +0,0 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-/* 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";
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-this.EXPORTED_SYMBOLS = [ "ReaderParent" ];
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
-
-const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
-
-var ReaderParent = {
- MESSAGES: [
- "Reader:UpdateReaderButton",
- ],
-
- init() {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- for (let msg of this.MESSAGES) {
- mm.addMessageListener(msg, this);
- }
- },
-
- receiveMessage(message) {
- switch (message.name) {
- case "Reader:UpdateReaderButton": {
- let browser = message.target;
- if (message.data && message.data.isArticle !== undefined) {
- browser.isArticle = message.data.isArticle;
- }
- this.updateReaderButton(browser);
- break;
- }
- }
- },
-
- updateReaderButton(browser) {
- let win = browser.ownerGlobal;
- if (browser != win.gBrowser.selectedBrowser) {
- return;
- }
-
- let button = win.document.getElementById("reader-mode-button");
- let command = win.document.getElementById("View:ReaderView");
- let key = win.document.getElementById("key_toggleReaderMode");
- // aria-reader is not a real ARIA attribute. However, this will cause
- // Gecko accessibility to expose the "reader" object attribute. We do this
- // so that the reader state is easy for accessibility clients to access
- // programmatically.
- if (browser.currentURI.spec.startsWith("about:reader")) {
- button.setAttribute("readeractive", true);
- button.hidden = false;
- let closeText = gStringBundle.GetStringFromName("readerView.close");
- button.setAttribute("tooltiptext", closeText);
- command.setAttribute("label", closeText);
- command.setAttribute("hidden", false);
- command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.close.accesskey"));
- key.setAttribute("disabled", false);
- browser.setAttribute("aria-reader", "active");
- } else {
- button.removeAttribute("readeractive");
- button.hidden = !browser.isArticle;
- let enterText = gStringBundle.GetStringFromName("readerView.enter");
- button.setAttribute("tooltiptext", enterText);
- command.setAttribute("label", enterText);
- command.setAttribute("hidden", !browser.isArticle);
- command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.enter.accesskey"));
- key.setAttribute("disabled", !browser.isArticle);
- if (browser.isArticle) {
- browser.setAttribute("aria-reader", "available");
- } else {
- browser.removeAttribute("aria-reader");
- }
- }
- },
-
- forceShowReaderIcon(browser) {
- browser.isArticle = true;
- this.updateReaderButton(browser);
- },
-
- buttonClick(event) {
- if (event.button != 0) {
- return;
- }
- this.toggleReaderMode(event);
- },
-
- toggleReaderMode(event) {
- let win = event.target.ownerGlobal;
- let browser = win.gBrowser.selectedBrowser;
- browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
- }
-};
diff --git a/browser/modules/RecentWindow.jsm b/browser/modules/RecentWindow.jsm
deleted file mode 100644
index fac9dcea4..000000000
--- a/browser/modules/RecentWindow.jsm
+++ /dev/null
@@ -1,67 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["RecentWindow"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-this.RecentWindow = {
- /*
- * Get the most recent browser window.
- *
- * @param aOptions an object accepting the arguments for the search.
- * * private: true to restrict the search to private windows
- * only, false to restrict the search to non-private only.
- * Omit the property to search in both groups.
- * * allowPopups: true if popup windows are permissable.
- */
- getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
- let checkPrivacy = typeof aOptions == "object" &&
- "private" in aOptions;
-
- let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
-
- function isSuitableBrowserWindow(win) {
- return (!win.closed &&
- (allowPopups || win.toolbar.visible) &&
- (!checkPrivacy ||
- PrivateBrowsingUtils.permanentPrivateBrowsing ||
- PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
- }
-
- let broken_wm_z_order =
- AppConstants.platform != "macosx" && AppConstants.platform != "win";
-
- if (broken_wm_z_order) {
- let win = Services.wm.getMostRecentWindow("navigator:browser");
-
- // if we're lucky, this isn't a popup, and we can just return this
- if (win && !isSuitableBrowserWindow(win)) {
- win = null;
- let windowList = Services.wm.getEnumerator("navigator:browser");
- // this is oldest to newest, so this gets a bit ugly
- while (windowList.hasMoreElements()) {
- let nextWin = windowList.getNext();
- if (isSuitableBrowserWindow(nextWin))
- win = nextWin;
- }
- }
- return win;
- }
- let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
- while (windowList.hasMoreElements()) {
- let win = windowList.getNext();
- if (isSuitableBrowserWindow(win))
- return win;
- }
- return null;
- }
-};
-
diff --git a/browser/modules/RemotePrompt.jsm b/browser/modules/RemotePrompt.jsm
deleted file mode 100644
index da4945c2e..000000000
--- a/browser/modules/RemotePrompt.jsm
+++ /dev/null
@@ -1,110 +0,0 @@
-/* vim: set ts=2 sw=2 et tw=80: */
-/* 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;
-
-this.EXPORTED_SYMBOLS = [ "RemotePrompt" ];
-
-Cu.import("resource:///modules/PlacesUIUtils.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
-
-var RemotePrompt = {
- init: function() {
- let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("Prompt:Open", this);
- },
-
- receiveMessage: function(message) {
- switch (message.name) {
- case "Prompt:Open":
- if (message.data.uri) {
- this.openModalWindow(message.data, message.target);
- } else {
- this.openTabPrompt(message.data, message.target)
- }
- break;
- }
- },
-
- openTabPrompt: function(args, browser) {
- let window = browser.ownerGlobal;
- let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
- let newPrompt;
- let needRemove = false;
- let promptId = args._remoteId;
-
- function onPromptClose(forceCleanup) {
- // It's possible that we removed the prompt during the
- // appendPrompt call below. In that case, newPrompt will be
- // undefined. We set the needRemove flag to remember to remove
- // it right after we've finished adding it.
- if (newPrompt)
- tabPrompt.removePrompt(newPrompt);
- else
- needRemove = true;
-
- PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
- browser.messageManager.sendAsyncMessage("Prompt:Close", args);
- }
-
- browser.messageManager.addMessageListener("Prompt:ForceClose", function listener(message) {
- // If this was for another prompt in the same tab, ignore it.
- if (message.data._remoteId !== promptId) {
- return;
- }
-
- browser.messageManager.removeMessageListener("Prompt:ForceClose", listener);
-
- if (newPrompt) {
- newPrompt.abortPrompt();
- }
- });
-
- try {
- let eventDetail = {
- tabPrompt: true,
- promptPrincipal: args.promptPrincipal,
- inPermitUnload: args.inPermitUnload,
- };
- PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser, eventDetail);
-
- args.promptActive = true;
-
- newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
-
- if (needRemove) {
- tabPrompt.removePrompt(newPrompt);
- }
-
- // TODO since we don't actually open a window, need to check if
- // there's other stuff in nsWindowWatcher::OpenWindowInternal
- // that we might need to do here as well.
- } catch (ex) {
- onPromptClose(true);
- }
- },
-
- openModalWindow: function(args, browser) {
- let window = browser.ownerGlobal;
- try {
- PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
- let bag = PromptUtils.objectToPropBag(args);
-
- Services.ww.openWindow(window, args.uri, "_blank",
- "centerscreen,chrome,modal,titlebar", bag);
-
- PromptUtils.propBagToObject(bag, args);
- } finally {
- PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
- browser.messageManager.sendAsyncMessage("Prompt:Close", args);
- }
- }
-};
diff --git a/browser/modules/Sanitizer.jsm b/browser/modules/Sanitizer.jsm
deleted file mode 100644
index 31c2823c7..000000000
--- a/browser/modules/Sanitizer.jsm
+++ /dev/null
@@ -1,22 +0,0 @@
-/* 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";
-
-//
-// A shared module for sanitize.js
-//
-// Until bug 1167238 lands, this serves only as a way to ensure that
-// sanitize is loaded from its own compartment, rather than from that
-// of the sanitize dialog.
-//
-
-this.EXPORTED_SYMBOLS = ["Sanitizer"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-var scope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://browser/content/sanitize.js", scope);
-
-this.Sanitizer = scope.Sanitizer;
diff --git a/browser/modules/SitePermissions.jsm b/browser/modules/SitePermissions.jsm
deleted file mode 100644
index d15ddb21b..000000000
--- a/browser/modules/SitePermissions.jsm
+++ /dev/null
@@ -1,269 +0,0 @@
-/* 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/. */
-
-this.EXPORTED_SYMBOLS = [ "SitePermissions" ];
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-var gStringBundle =
- Services.strings.createBundle("chrome://browser/locale/sitePermissions.properties");
-
-this.SitePermissions = {
-
- UNKNOWN: Services.perms.UNKNOWN_ACTION,
- ALLOW: Services.perms.ALLOW_ACTION,
- BLOCK: Services.perms.DENY_ACTION,
- SESSION: Components.interfaces.nsICookiePermission.ACCESS_SESSION,
-
- /* Returns all custom permissions for a given URI, the return
- * type is a list of objects with the keys:
- * - id: the permissionId of the permission
- * - state: a constant representing the current permission state
- * (e.g. SitePermissions.ALLOW)
- *
- * To receive a more detailed, albeit less performant listing see
- * SitePermissions.getPermissionDetailsByURI().
- *
- * install addon permission is excluded, check bug 1303108
- */
- getAllByURI: function (aURI) {
- let result = [];
- if (!this.isSupportedURI(aURI)) {
- return result;
- }
-
- let permissions = Services.perms.getAllForURI(aURI);
- while (permissions.hasMoreElements()) {
- let permission = permissions.getNext();
-
- // filter out unknown permissions
- if (gPermissionObject[permission.type]) {
- // XXX Bug 1303108 - Control Center should only show non-default permissions
- if (permission.type == "install") {
- continue;
- }
- result.push({
- id: permission.type,
- state: permission.capability,
- });
- }
- }
-
- return result;
- },
-
- /* Returns an object representing the aId permission. It contains the
- * following keys:
- * - id: the permissionID of the permission
- * - label: the localized label
- * - state: a constant representing the aState permission state
- * (e.g. SitePermissions.ALLOW), or the default if aState is omitted
- * - availableStates: an array of all available states for that permission,
- * represented as objects with the keys:
- * - id: the state constant
- * - label: the translated label of that state
- */
- getPermissionItem: function (aId, aState) {
- let availableStates = this.getAvailableStates(aId).map(state => {
- return { id: state, label: this.getStateLabel(aId, state) };
- });
- if (aState == undefined)
- aState = this.getDefault(aId);
- return {id: aId, label: this.getPermissionLabel(aId),
- state: aState, availableStates};
- },
-
- /* Returns a list of objects representing all permissions that are currently
- * set for the given URI. See getPermissionItem for the content of each object.
- */
- getPermissionDetailsByURI: function (aURI) {
- let permissions = [];
- for (let {state, id} of this.getAllByURI(aURI)) {
- permissions.push(this.getPermissionItem(id, state));
- }
-
- return permissions;
- },
-
- /* Checks whether a UI for managing permissions should be exposed for a given
- * URI. This excludes file URIs, for instance, as they don't have a host,
- * even though nsIPermissionManager can still handle them.
- */
- isSupportedURI: function (aURI) {
- return aURI.schemeIs("http") || aURI.schemeIs("https");
- },
-
- /* Returns an array of all permission IDs.
- */
- listPermissions: function () {
- return Object.keys(gPermissionObject);
- },
-
- /* Returns an array of permission states to be exposed to the user for a
- * permission with the given ID.
- */
- getAvailableStates: function (aPermissionID) {
- if (aPermissionID in gPermissionObject &&
- gPermissionObject[aPermissionID].states)
- return gPermissionObject[aPermissionID].states;
-
- if (this.getDefault(aPermissionID) == this.UNKNOWN)
- return [ SitePermissions.UNKNOWN, SitePermissions.ALLOW, SitePermissions.BLOCK ];
-
- return [ SitePermissions.ALLOW, SitePermissions.BLOCK ];
- },
-
- /* Returns the default state of a particular permission.
- */
- getDefault: function (aPermissionID) {
- if (aPermissionID in gPermissionObject &&
- gPermissionObject[aPermissionID].getDefault)
- return gPermissionObject[aPermissionID].getDefault();
-
- return this.UNKNOWN;
- },
-
- /* Returns the state of a particular permission for a given URI.
- */
- get: function (aURI, aPermissionID) {
- if (!this.isSupportedURI(aURI))
- return this.UNKNOWN;
-
- let state;
- if (aPermissionID in gPermissionObject &&
- gPermissionObject[aPermissionID].exactHostMatch)
- state = Services.perms.testExactPermission(aURI, aPermissionID);
- else
- state = Services.perms.testPermission(aURI, aPermissionID);
- return state;
- },
-
- /* Sets the state of a particular permission for a given URI.
- */
- set: function (aURI, aPermissionID, aState) {
- if (!this.isSupportedURI(aURI))
- return;
-
- if (aState == this.UNKNOWN) {
- this.remove(aURI, aPermissionID);
- return;
- }
-
- Services.perms.add(aURI, aPermissionID, aState);
- },
-
- /* Removes the saved state of a particular permission for a given URI.
- */
- remove: function (aURI, aPermissionID) {
- if (!this.isSupportedURI(aURI))
- return;
-
- Services.perms.remove(aURI, aPermissionID);
- },
-
- /* Returns the localized label for the permission with the given ID, to be
- * used in a UI for managing permissions.
- */
- getPermissionLabel: function (aPermissionID) {
- let labelID = gPermissionObject[aPermissionID].labelID || aPermissionID;
- return gStringBundle.GetStringFromName("permission." + labelID + ".label");
- },
-
- /* Returns the localized label for the given permission state, to be used in
- * a UI for managing permissions.
- */
- getStateLabel: function (aPermissionID, aState, aInUse = false) {
- switch (aState) {
- case this.UNKNOWN:
- if (aInUse)
- return gStringBundle.GetStringFromName("allowTemporarily");
- return gStringBundle.GetStringFromName("alwaysAsk");
- case this.ALLOW:
- return gStringBundle.GetStringFromName("allow");
- case this.SESSION:
- return gStringBundle.GetStringFromName("allowForSession");
- case this.BLOCK:
- return gStringBundle.GetStringFromName("block");
- default:
- return null;
- }
- }
-};
-
-var gPermissionObject = {
- /* Holds permission ID => options pairs.
- *
- * Supported options:
- *
- * - exactHostMatch
- * Allows sub domains to have their own permissions.
- * Defaults to false.
- *
- * - getDefault
- * Called to get the permission's default state.
- * Defaults to UNKNOWN, indicating that the user will be asked each time
- * a page asks for that permissions.
- *
- * - labelID
- * Use the given ID instead of the permission name for looking up strings.
- * e.g. "desktop-notification2" to use permission.desktop-notification2.label
- *
- * - states
- * Array of permission states to be exposed to the user.
- * Defaults to ALLOW, BLOCK and the default state (see getDefault).
- */
-
- "image": {
- getDefault: function () {
- return Services.prefs.getIntPref("permissions.default.image") == 2 ?
- SitePermissions.BLOCK : SitePermissions.ALLOW;
- }
- },
-
- "cookie": {
- states: [ SitePermissions.ALLOW, SitePermissions.SESSION, SitePermissions.BLOCK ],
- getDefault: function () {
- if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == 2)
- return SitePermissions.BLOCK;
-
- if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == 2)
- return SitePermissions.SESSION;
-
- return SitePermissions.ALLOW;
- }
- },
-
- "desktop-notification": {
- exactHostMatch: true,
- labelID: "desktop-notification2",
- },
-
- "camera": {},
- "microphone": {},
- "screen": {
- states: [ SitePermissions.UNKNOWN, SitePermissions.BLOCK ],
- },
-
- "popup": {
- getDefault: function () {
- return Services.prefs.getBoolPref("dom.disable_open_during_load") ?
- SitePermissions.BLOCK : SitePermissions.ALLOW;
- }
- },
-
- "install": {
- getDefault: function () {
- return Services.prefs.getBoolPref("xpinstall.whitelist.required") ?
- SitePermissions.BLOCK : SitePermissions.ALLOW;
- }
- },
-
- "geo": {
- exactHostMatch: true
- },
-
- "indexedDB": {}
-};
-
-const kPermissionIDs = Object.keys(gPermissionObject);
diff --git a/browser/modules/TransientPrefs.jsm b/browser/modules/TransientPrefs.jsm
deleted file mode 100644
index 30ba6a152..000000000
--- a/browser/modules/TransientPrefs.jsm
+++ /dev/null
@@ -1,24 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["TransientPrefs"];
-
-Components.utils.import("resource://gre/modules/Preferences.jsm");
-
-var prefVisibility = new Map;
-
-/* Use for preferences that should only be visible when they've been modified.
- When reset to their default state, they remain visible until restarting the
- application. */
-
-this.TransientPrefs = {
- prefShouldBeVisible: function (prefName) {
- if (Preferences.isSet(prefName))
- prefVisibility.set(prefName, true);
-
- return !!prefVisibility.get(prefName);
- }
-};
diff --git a/browser/modules/URLBarZoom.jsm b/browser/modules/URLBarZoom.jsm
deleted file mode 100644
index 3e1c0f707..000000000
--- a/browser/modules/URLBarZoom.jsm
+++ /dev/null
@@ -1,51 +0,0 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
-/* 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";
-
-this.EXPORTED_SYMBOLS = [ "URLBarZoom" ];
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-var URLBarZoom = {
-
- init: function(aWindow) {
- // Register ourselves with the service so we know when the zoom prefs change.
- Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomChange", false);
- Services.obs.addObserver(updateZoomButton, "browser-fullZoom:zoomReset", false);
- Services.obs.addObserver(updateZoomButton, "browser-fullZoom:location-change", false);
- },
-}
-
-function updateZoomButton(aSubject, aTopic) {
- let win = aSubject.ownerDocument.defaultView;
- let customizableZoomControls = win.document.getElementById("zoom-controls");
- let zoomResetButton = win.document.getElementById("urlbar-zoom-button");
- let zoomFactor = Math.round(win.ZoomManager.zoom * 100);
-
- // Ensure that zoom controls haven't already been added to browser in Customize Mode
- if (customizableZoomControls &&
- customizableZoomControls.getAttribute("cui-areatype") == "toolbar") {
- zoomResetButton.hidden = true;
- return;
- }
- if (zoomFactor != 100) {
- // Check if zoom button is visible and update label if it is
- if (zoomResetButton.hidden) {
- zoomResetButton.hidden = false;
- }
- // Only allow pulse animation for zoom changes, not tab switching
- if (aTopic != "browser-fullZoom:location-change") {
- zoomResetButton.setAttribute("animate", "true");
- } else {
- zoomResetButton.removeAttribute("animate");
- }
- zoomResetButton.setAttribute("label",
- win.gNavigatorBundle.getFormattedString("urlbar-zoom-button.label", [zoomFactor]));
- // Hide button if zoom is at 100%
- } else {
- zoomResetButton.hidden = true;
- }
-}
diff --git a/browser/modules/Windows8WindowFrameColor.jsm b/browser/modules/Windows8WindowFrameColor.jsm
deleted file mode 100644
index 911333747..000000000
--- a/browser/modules/Windows8WindowFrameColor.jsm
+++ /dev/null
@@ -1,53 +0,0 @@
-/* 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";
-const {interfaces: Ci, utils: Cu} = Components;
-
-this.EXPORTED_SYMBOLS = ["Windows8WindowFrameColor"];
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-var Registry = Cu.import("resource://gre/modules/WindowsRegistry.jsm").WindowsRegistry;
-
-var Windows8WindowFrameColor = {
- _windowFrameColor: null,
-
- get: function() {
- if (this._windowFrameColor)
- return this._windowFrameColor;
-
- const HKCU = Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER;
- const dwmKey = "Software\\Microsoft\\Windows\\DWM";
- let customizationColor = Registry.readRegKey(HKCU, dwmKey,
- "ColorizationColor");
- if (customizationColor == undefined) {
- // Seems to be the default color (hardcoded because of bug 1065998)
- return [158, 158, 158];
- }
-
- // The color returned from the Registry is in decimal form.
- let customizationColorHex = customizationColor.toString(16);
-
- // Zero-pad the number just to make sure that it is 8 digits.
- customizationColorHex = ("00000000" + customizationColorHex).substr(-8);
- let customizationColorArray = customizationColorHex.match(/../g);
- let [, fgR, fgG, fgB] = customizationColorArray.map(val => parseInt(val, 16));
- let colorizationColorBalance = Registry.readRegKey(HKCU, dwmKey,
- "ColorizationColorBalance");
- if (colorizationColorBalance == undefined) {
- colorizationColorBalance = 78;
- }
-
- // Window frame base color when Color Intensity is at 0, see bug 1004576.
- let frameBaseColor = 217;
- let alpha = colorizationColorBalance / 100;
-
- // Alpha-blend the foreground color with the frame base color.
- let r = Math.round(fgR * alpha + frameBaseColor * (1 - alpha));
- let g = Math.round(fgG * alpha + frameBaseColor * (1 - alpha));
- let b = Math.round(fgB * alpha + frameBaseColor * (1 - alpha));
- return this._windowFrameColor = [r, g, b];
- },
-};
diff --git a/browser/modules/WindowsJumpLists.jsm b/browser/modules/WindowsJumpLists.jsm
deleted file mode 100644
index 4df87b47a..000000000
--- a/browser/modules/WindowsJumpLists.jsm
+++ /dev/null
@@ -1,579 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-
-/**
- * Constants
- */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-// Stop updating jumplists after some idle time.
-const IDLE_TIMEOUT_SECONDS = 5 * 60;
-
-// Prefs
-const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
-const PREF_TASKBAR_ENABLED = "enabled";
-const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
-const PREF_TASKBAR_FREQUENT = "frequent.enabled";
-const PREF_TASKBAR_RECENT = "recent.enabled";
-const PREF_TASKBAR_TASKS = "tasks.enabled";
-const PREF_TASKBAR_REFRESH = "refreshInSeconds";
-
-// Hash keys for pendingStatements.
-const LIST_TYPE = {
- FREQUENT: 0
-, RECENT: 1
-}
-
-/**
- * Exports
- */
-
-this.EXPORTED_SYMBOLS = [
- "WinTaskbarJumpList",
-];
-
-/**
- * Smart getters
- */
-
-XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
- return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
-});
-
-XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
- return Services.strings
- .createBundle("chrome://browser/locale/taskbar.properties");
-});
-
-XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
- Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
- return PlacesUtils;
-});
-
-XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
- Components.utils.import("resource://gre/modules/NetUtil.jsm");
- return NetUtil;
-});
-
-XPCOMUtils.defineLazyServiceGetter(this, "_idle",
- "@mozilla.org/widget/idleservice;1",
- "nsIIdleService");
-
-XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
- "@mozilla.org/windows-taskbar;1",
- "nsIWinTaskbar");
-
-XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
- "@mozilla.org/browser/shell-service;1",
- "nsIWindowsShellService");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
- "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-/**
- * Global functions
- */
-
-function _getString(name) {
- return _stringBundle.GetStringFromName(name);
-}
-
-// Task list configuration data object.
-
-var tasksCfg = [
- /**
- * Task configuration options: title, description, args, iconIndex, open, close.
- *
- * title - Task title displayed in the list. (strings in the table are temp fillers.)
- * description - Tooltip description on the list item.
- * args - Command line args to invoke the task.
- * iconIndex - Optional win icon index into the main application for the
- * list item.
- * open - Boolean indicates if the command should be visible after the browser opens.
- * close - Boolean indicates if the command should be visible after the browser closes.
- */
- // Open new tab
- {
- get title() { return _getString("taskbar.tasks.newTab.label"); },
- get description() { return _getString("taskbar.tasks.newTab.description"); },
- args: "-new-tab about:blank",
- iconIndex: 3, // New window icon
- open: true,
- close: true, // The jump list already has an app launch icon, but
- // we don't always update the list on shutdown.
- // Thus true for consistency.
- },
-
- // Open new window
- {
- get title() { return _getString("taskbar.tasks.newWindow.label"); },
- get description() { return _getString("taskbar.tasks.newWindow.description"); },
- args: "-browser",
- iconIndex: 2, // New tab icon
- open: true,
- close: true, // No point, but we don't always update the list on
- // shutdown. Thus true for consistency.
- },
-
- // Open new private window
- {
- get title() { return _getString("taskbar.tasks.newPrivateWindow.label"); },
- get description() { return _getString("taskbar.tasks.newPrivateWindow.description"); },
- args: "-private-window",
- iconIndex: 4, // Private browsing mode icon
- open: true,
- close: true, // No point, but we don't always update the list on
- // shutdown. Thus true for consistency.
- },
-];
-
-// Implementation
-
-this.WinTaskbarJumpList =
-{
- _builder: null,
- _tasks: null,
- _shuttingDown: false,
-
- /**
- * Startup, shutdown, and update
- */
-
- startup: function WTBJL_startup() {
- // exit if this isn't win7 or higher.
- if (!this._initTaskbar())
- return;
-
- // Win shell shortcut maintenance. If we've gone through an update,
- // this will update any pinned taskbar shortcuts. Not specific to
- // jump lists, but this was a convienent place to call it.
- try {
- // dev builds may not have helper.exe, ignore failures.
- this._shortcutMaintenance();
- } catch (ex) {
- }
-
- // Store our task list config data
- this._tasks = tasksCfg;
-
- // retrieve taskbar related prefs.
- this._refreshPrefs();
-
- // observer for private browsing and our prefs branch
- this._initObs();
-
- // jump list refresh timer
- this._updateTimer();
- },
-
- update: function WTBJL_update() {
- // are we disabled via prefs? don't do anything!
- if (!this._enabled)
- return;
-
- // do what we came here to do, update the taskbar jumplist
- this._buildList();
- },
-
- _shutdown: function WTBJL__shutdown() {
- this._shuttingDown = true;
-
- // Correctly handle a clear history on shutdown. If there are no
- // entries be sure to empty all history lists. Luckily Places caches
- // this value, so it's a pretty fast call.
- if (!PlacesUtils.history.hasHistoryEntries) {
- this.update();
- }
-
- this._free();
- },
-
- _shortcutMaintenance: function WTBJL__maintenace() {
- _winShellService.shortcutMaintenance();
- },
-
- /**
- * List building
- *
- * @note Async builders must add their mozIStoragePendingStatement to
- * _pendingStatements object, using a different LIST_TYPE entry for
- * each statement. Once finished they must remove it and call
- * commitBuild(). When there will be no more _pendingStatements,
- * commitBuild() will commit for real.
- */
-
- _pendingStatements: {},
- _hasPendingStatements: function WTBJL__hasPendingStatements() {
- return Object.keys(this._pendingStatements).length > 0;
- },
-
- _buildList: function WTBJL__buildList() {
- if (this._hasPendingStatements()) {
- // We were requested to update the list while another update was in
- // progress, this could happen at shutdown, idle or privatebrowsing.
- // Abort the current list building.
- for (let listType in this._pendingStatements) {
- this._pendingStatements[listType].cancel();
- delete this._pendingStatements[listType];
- }
- this._builder.abortListBuild();
- }
-
- // anything to build?
- if (!this._showFrequent && !this._showRecent && !this._showTasks) {
- // don't leave the last list hanging on the taskbar.
- this._deleteActiveJumpList();
- return;
- }
-
- if (!this._startBuild())
- return;
-
- if (this._showTasks)
- this._buildTasks();
-
- // Space for frequent items takes priority over recent.
- if (this._showFrequent)
- this._buildFrequent();
-
- if (this._showRecent)
- this._buildRecent();
-
- this._commitBuild();
- },
-
- /**
- * Taskbar api wrappers
- */
-
- _startBuild: function WTBJL__startBuild() {
- var removedItems = Cc["@mozilla.org/array;1"].
- createInstance(Ci.nsIMutableArray);
- this._builder.abortListBuild();
- if (this._builder.initListBuild(removedItems)) {
- // Prior to building, delete removed items from history.
- this._clearHistory(removedItems);
- return true;
- }
- return false;
- },
-
- _commitBuild: function WTBJL__commitBuild() {
- if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
- this._builder.abortListBuild();
- }
- },
-
- _buildTasks: function WTBJL__buildTasks() {
- var items = Cc["@mozilla.org/array;1"].
- createInstance(Ci.nsIMutableArray);
- this._tasks.forEach(function (task) {
- if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
- return;
- var item = this._getHandlerAppItem(task.title, task.description,
- task.args, task.iconIndex, null);
- items.appendElement(item, false);
- }, this);
-
- if (items.length > 0)
- this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
- },
-
- _buildCustom: function WTBJL__buildCustom(title, items) {
- if (items.length > 0)
- this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
- },
-
- _buildFrequent: function WTBJL__buildFrequent() {
- // If history is empty, just bail out.
- if (!PlacesUtils.history.hasHistoryEntries) {
- return;
- }
-
- // Windows supports default frequent and recent lists,
- // but those depend on internal windows visit tracking
- // which we don't populate. So we build our own custom
- // frequent and recent lists using our nav history data.
-
- var items = Cc["@mozilla.org/array;1"].
- createInstance(Ci.nsIMutableArray);
- // track frequent items so that we don't add them to
- // the recent list.
- this._frequentHashList = [];
-
- this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
- Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
- this._maxItemCount,
- function (aResult) {
- if (!aResult) {
- delete this._pendingStatements[LIST_TYPE.FREQUENT];
- // The are no more results, build the list.
- this._buildCustom(_getString("taskbar.frequent.label"), items);
- this._commitBuild();
- return;
- }
-
- let title = aResult.title || aResult.uri;
- let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
- let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
- faviconPageUri);
- items.appendElement(shortcut, false);
- this._frequentHashList.push(aResult.uri);
- },
- this
- );
- },
-
- _buildRecent: function WTBJL__buildRecent() {
- // If history is empty, just bail out.
- if (!PlacesUtils.history.hasHistoryEntries) {
- return;
- }
-
- var items = Cc["@mozilla.org/array;1"].
- createInstance(Ci.nsIMutableArray);
- // Frequent items will be skipped, so we select a double amount of
- // entries and stop fetching results at _maxItemCount.
- var count = 0;
-
- this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
- Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
- this._maxItemCount * 2,
- function (aResult) {
- if (!aResult) {
- // The are no more results, build the list.
- this._buildCustom(_getString("taskbar.recent.label"), items);
- delete this._pendingStatements[LIST_TYPE.RECENT];
- this._commitBuild();
- return;
- }
-
- if (count >= this._maxItemCount) {
- return;
- }
-
- // Do not add items to recent that have already been added to frequent.
- if (this._frequentHashList &&
- this._frequentHashList.indexOf(aResult.uri) != -1) {
- return;
- }
-
- let title = aResult.title || aResult.uri;
- let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
- let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
- faviconPageUri);
- items.appendElement(shortcut, false);
- count++;
- },
- this
- );
- },
-
- _deleteActiveJumpList: function WTBJL__deleteAJL() {
- this._builder.deleteActiveList();
- },
-
- /**
- * Jump list item creation helpers
- */
-
- _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
- args, iconIndex,
- faviconPageUri) {
- var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
-
- var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
- createInstance(Ci.nsILocalHandlerApp);
- handlerApp.executable = file;
- // handlers default to the leaf name if a name is not specified
- if (name && name.length != 0)
- handlerApp.name = name;
- handlerApp.detailedDescription = description;
- handlerApp.appendParameter(args);
-
- var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
- createInstance(Ci.nsIJumpListShortcut);
- item.app = handlerApp;
- item.iconIndex = iconIndex;
- item.faviconPageUri = faviconPageUri;
- return item;
- },
-
- _getSeparatorItem: function WTBJL__getSeparatorItem() {
- var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
- createInstance(Ci.nsIJumpListSeparator);
- return item;
- },
-
- /**
- * Nav history helpers
- */
-
- _getHistoryResults:
- function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
- var options = PlacesUtils.history.getNewQueryOptions();
- options.maxResults = aLimit;
- options.sortingMode = aSortingMode;
- var query = PlacesUtils.history.getNewQuery();
-
- // Return the pending statement to the caller, to allow cancelation.
- return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
- .asyncExecuteLegacyQueries([query], 1, options, {
- handleResult: function (aResultSet) {
- for (let row; (row = aResultSet.getNextRow());) {
- try {
- aCallback.call(aScope,
- { uri: row.getResultByIndex(1)
- , title: row.getResultByIndex(2)
- });
- } catch (e) {}
- }
- },
- handleError: function (aError) {
- Components.utils.reportError(
- "Async execution error (" + aError.result + "): " + aError.message);
- },
- handleCompletion: function (aReason) {
- aCallback.call(WinTaskbarJumpList, null);
- },
- });
- },
-
- _clearHistory: function WTBJL__clearHistory(items) {
- if (!items)
- return;
- var URIsToRemove = [];
- var e = items.enumerate();
- while (e.hasMoreElements()) {
- let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
- if (oldItem) {
- try { // in case we get a bad uri
- let uriSpec = oldItem.app.getParameter(0);
- URIsToRemove.push(NetUtil.newURI(uriSpec));
- } catch (err) { }
- }
- }
- if (URIsToRemove.length > 0) {
- PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
- }
- },
-
- /**
- * Prefs utilities
- */
-
- _refreshPrefs: function WTBJL__refreshPrefs() {
- this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
- this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
- this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
- this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
- this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
- },
-
- /**
- * Init and shutdown utilities
- */
-
- _initTaskbar: function WTBJL__initTaskbar() {
- this._builder = _taskbarService.createJumpListBuilder();
- if (!this._builder || !this._builder.available)
- return false;
-
- return true;
- },
-
- _initObs: function WTBJL__initObs() {
- // If the browser is closed while in private browsing mode, the "exit"
- // notification is fired on quit-application-granted.
- // History cleanup can happen at profile-change-teardown.
- Services.obs.addObserver(this, "profile-before-change", false);
- Services.obs.addObserver(this, "browser:purge-session-history", false);
- _prefs.addObserver("", this, false);
- },
-
- _freeObs: function WTBJL__freeObs() {
- Services.obs.removeObserver(this, "profile-before-change");
- Services.obs.removeObserver(this, "browser:purge-session-history");
- _prefs.removeObserver("", this);
- },
-
- _updateTimer: function WTBJL__updateTimer() {
- if (this._enabled && !this._shuttingDown && !this._timer) {
- this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- this._timer.initWithCallback(this,
- _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
- this._timer.TYPE_REPEATING_SLACK);
- }
- else if ((!this._enabled || this._shuttingDown) && this._timer) {
- this._timer.cancel();
- delete this._timer;
- }
- },
-
- _hasIdleObserver: false,
- _updateIdleObserver: function WTBJL__updateIdleObserver() {
- if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
- _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
- this._hasIdleObserver = true;
- }
- else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
- _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
- this._hasIdleObserver = false;
- }
- },
-
- _free: function WTBJL__free() {
- this._freeObs();
- this._updateTimer();
- this._updateIdleObserver();
- delete this._builder;
- },
-
- /**
- * Notification handlers
- */
-
- notify: function WTBJL_notify(aTimer) {
- // Add idle observer on the first notification so it doesn't hit startup.
- this._updateIdleObserver();
- this.update();
- },
-
- observe: function WTBJL_observe(aSubject, aTopic, aData) {
- switch (aTopic) {
- case "nsPref:changed":
- if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
- this._deleteActiveJumpList();
- this._refreshPrefs();
- this._updateTimer();
- this._updateIdleObserver();
- this.update();
- break;
-
- case "profile-before-change":
- this._shutdown();
- break;
-
- case "browser:purge-session-history":
- this.update();
- break;
- case "idle":
- if (this._timer) {
- this._timer.cancel();
- delete this._timer;
- }
- break;
-
- case "active":
- this._updateTimer();
- break;
- }
- },
-};
diff --git a/browser/modules/WindowsPreviewPerTab.jsm b/browser/modules/WindowsPreviewPerTab.jsm
deleted file mode 100644
index 6586b5d3b..000000000
--- a/browser/modules/WindowsPreviewPerTab.jsm
+++ /dev/null
@@ -1,862 +0,0 @@
-/* vim: se cin sw=2 ts=2 et filetype=javascript :
- * 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/. */
-/*
- * This module implements the front end behavior for AeroPeek. Starting in
- * Windows Vista, the taskbar began showing live thumbnail previews of windows
- * when the user hovered over the window icon in the taskbar. Starting with
- * Windows 7, the taskbar allows an application to expose its tabbed interface
- * in the taskbar by showing thumbnail previews rather than the default window
- * preview. Additionally, when a user hovers over a thumbnail (tab or window),
- * they are shown a live preview of the window (or tab + its containing window).
- *
- * In Windows 7, a title, icon, close button and optional toolbar are shown for
- * each preview. This feature does not make use of the toolbar. For window
- * previews, the title is the window title and the icon the window icon. For
- * tab previews, the title is the page title and the page's favicon. In both
- * cases, the close button "does the right thing."
- *
- * The primary objects behind this feature are nsITaskbarTabPreview and
- * nsITaskbarPreviewController. Each preview has a controller. The controller
- * responds to the user's interactions on the taskbar and provides the required
- * data to the preview for determining the size of the tab and thumbnail. The
- * PreviewController class implements this interface. The preview will request
- * the controller to provide a thumbnail or preview when the user interacts with
- * the taskbar. To reduce the overhead of drawing the tab area, the controller
- * implementation caches the tab's contents in a <canvas> element. If no
- * previews or thumbnails have been requested for some time, the controller will
- * discard its cached tab contents.
- *
- * Screen real estate is limited so when there are too many thumbnails to fit
- * on the screen, the taskbar stops displaying thumbnails and instead displays
- * just the title, icon and close button in a similar fashion to previous
- * versions of the taskbar. If there are still too many previews to fit on the
- * screen, the taskbar resorts to a scroll up and scroll down button pair to let
- * the user scroll through the list of tabs. Since this is undoubtedly
- * inconvenient for users with many tabs, the AeroPeek objects turns off all of
- * the tab previews. This tells the taskbar to revert to one preview per window.
- * If the number of tabs falls below this magic threshold, the preview-per-tab
- * behavior returns. There is no reliable way to determine when the scroll
- * buttons appear on the taskbar, so a magic pref-controlled number determines
- * when this threshold has been crossed.
- */
-this.EXPORTED_SYMBOLS = ["AeroPeek"];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
-Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-// Pref to enable/disable preview-per-tab
-const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
-// Pref to determine the magic auto-disable threshold
-const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max";
-// Pref to control the time in seconds that tab contents live in the cache
-const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
-
-const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
-
-// Various utility properties
-XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
- "@mozilla.org/image/tools;1",
- "imgITools");
-XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
- "resource://gre/modules/PageThumbs.jsm");
-
-// nsIURI -> imgIContainer
-function _imageFromURI(uri, privateMode, callback) {
- let channel = NetUtil.newChannel({
- uri: uri,
- loadUsingSystemPrincipal: true,
- contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
- });
-
- try {
- channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
- channel.setPrivate(privateMode);
- } catch (e) {
- // Ignore channels which do not support nsIPrivateBrowsingChannel
- }
- NetUtil.asyncFetch(channel, function(inputStream, resultCode) {
- if (!Components.isSuccessCode(resultCode))
- return;
- try {
- let out_img = { value: null };
- imgTools.decodeImageData(inputStream, channel.contentType, out_img);
- callback(out_img.value);
- } catch (e) {
- // We failed, so use the default favicon (only if this wasn't the default
- // favicon).
- let defaultURI = PlacesUtils.favicons.defaultFavicon;
- if (!defaultURI.equals(uri))
- _imageFromURI(defaultURI, privateMode, callback);
- }
- });
-}
-
-// string? -> imgIContainer
-function getFaviconAsImage(iconurl, privateMode, callback) {
- if (iconurl) {
- _imageFromURI(NetUtil.newURI(iconurl), privateMode, callback);
- } else {
- _imageFromURI(PlacesUtils.favicons.defaultFavicon, privateMode, callback);
- }
-}
-
-// Snaps the given rectangle to be pixel-aligned at the given scale
-function snapRectAtScale(r, scale) {
- let x = Math.floor(r.x * scale);
- let y = Math.floor(r.y * scale);
- let width = Math.ceil((r.x + r.width) * scale) - x;
- let height = Math.ceil((r.y + r.height) * scale) - y;
-
- r.x = x / scale;
- r.y = y / scale;
- r.width = width / scale;
- r.height = height / scale;
-}
-
-// PreviewController
-
-/*
- * This class manages the behavior of thumbnails and previews. It has the following
- * responsibilities:
- * 1) responding to requests from Windows taskbar for a thumbnail or window
- * preview.
- * 2) listens for dom events that result in a thumbnail or window preview needing
- * to be refresh, and communicates this to the taskbar.
- * 3) Handles querying and returning to the taskbar new thumbnail or window
- * preview images through PageThumbs.
- *
- * @param win
- * The TabWindow (see below) that owns the preview that this controls
- * @param tab
- * The <tab> that this preview is associated with
- */
-function PreviewController(win, tab) {
- this.win = win;
- this.tab = tab;
- this.linkedBrowser = tab.linkedBrowser;
- this.preview = this.win.createTabPreview(this);
-
- this.tab.addEventListener("TabAttrModified", this, false);
-
- XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
- let canvas = PageThumbs.createCanvas();
- canvas.mozOpaque = true;
- return canvas;
- });
-}
-
-PreviewController.prototype = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
- Ci.nsIDOMEventListener]),
-
- destroy: function () {
- this.tab.removeEventListener("TabAttrModified", this, false);
-
- // Break cycles, otherwise we end up leaking the window with everything
- // attached to it.
- delete this.win;
- delete this.preview;
- },
-
- get wrappedJSObject() {
- return this;
- },
-
- // Resizes the canvasPreview to 0x0, essentially freeing its memory.
- resetCanvasPreview: function () {
- this.canvasPreview.width = 0;
- this.canvasPreview.height = 0;
- },
-
- /**
- * Set the canvas dimensions.
- */
- resizeCanvasPreview: function (aRequestedWidth, aRequestedHeight) {
- this.canvasPreview.width = aRequestedWidth;
- this.canvasPreview.height = aRequestedHeight;
- },
-
-
- get zoom() {
- // Note that winutils.fullZoom accounts for "quantization" of the zoom factor
- // from nsIContentViewer due to conversion through appUnits.
- // We do -not- want screenPixelsPerCSSPixel here, because that would -also-
- // incorporate any scaling that is applied due to hi-dpi resolution options.
- return this.tab.linkedBrowser.fullZoom;
- },
-
- get screenPixelsPerCSSPixel() {
- let chromeWin = this.tab.ownerGlobal;
- let windowUtils = chromeWin.getInterface(Ci.nsIDOMWindowUtils);
- return windowUtils.screenPixelsPerCSSPixel;
- },
-
- get browserDims() {
- return this.tab.linkedBrowser.getBoundingClientRect();
- },
-
- cacheBrowserDims: function () {
- let dims = this.browserDims;
- this._cachedWidth = dims.width;
- this._cachedHeight = dims.height;
- },
-
- testCacheBrowserDims: function () {
- let dims = this.browserDims;
- return this._cachedWidth == dims.width &&
- this._cachedHeight == dims.height;
- },
-
- /**
- * Capture a new thumbnail image for this preview. Called by the controller
- * in response to a request for a new thumbnail image.
- */
- updateCanvasPreview: function (aFullScale, aCallback) {
- // Update our cached browser dims so that delayed resize
- // events don't trigger another invalidation if this tab becomes active.
- this.cacheBrowserDims();
- PageThumbs.captureToCanvas(this.linkedBrowser, this.canvasPreview,
- aCallback, { fullScale: aFullScale });
- // If we're updating the canvas, then we're in the middle of a peek so
- // don't discard the cache of previews.
- AeroPeek.resetCacheTimer();
- },
-
- updateTitleAndTooltip: function () {
- let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
- this.preview.title = title;
- this.preview.tooltip = title;
- },
-
- // nsITaskbarPreviewController
-
- // window width and height, not browser
- get width() {
- return this.win.width;
- },
-
- // window width and height, not browser
- get height() {
- return this.win.height;
- },
-
- get thumbnailAspectRatio() {
- let browserDims = this.browserDims;
- // Avoid returning 0
- let tabWidth = browserDims.width || 1;
- // Avoid divide by 0
- let tabHeight = browserDims.height || 1;
- return tabWidth / tabHeight;
- },
-
- /**
- * Responds to taskbar requests for window previews. Returns the results asynchronously
- * through updateCanvasPreview.
- *
- * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
- */
- requestPreview: function (aTaskbarCallback) {
- // Grab a high res content preview
- this.resetCanvasPreview();
- this.updateCanvasPreview(true, (aPreviewCanvas) => {
- let winWidth = this.win.width;
- let winHeight = this.win.height;
-
- let composite = PageThumbs.createCanvas();
-
- // Use transparency, Aero glass is drawn black without it.
- composite.mozOpaque = false;
-
- let ctx = composite.getContext('2d');
- let scale = this.screenPixelsPerCSSPixel / this.zoom;
-
- composite.width = winWidth * scale;
- composite.height = winHeight * scale;
-
- ctx.save();
- ctx.scale(scale, scale);
-
- // Draw chrome. Note we currently do not get scrollbars for remote frames
- // in the image above.
- ctx.drawWindow(this.win.win, 0, 0, winWidth, winHeight, "rgba(0,0,0,0)");
-
- // Draw the content are into the composite canvas at the right location.
- ctx.drawImage(aPreviewCanvas, this.browserDims.x, this.browserDims.y,
- aPreviewCanvas.width, aPreviewCanvas.height);
- ctx.restore();
-
- // Deliver the resulting composite canvas to Windows
- this.win.tabbrowser.previewTab(this.tab, function () {
- aTaskbarCallback.done(composite, false);
- });
- });
- },
-
- /**
- * Responds to taskbar requests for tab thumbnails. Returns the results asynchronously
- * through updateCanvasPreview.
- *
- * Note Windows requests a specific width and height here, if the resulting thumbnail
- * does not match these dimensions thumbnail display will fail.
- *
- * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
- * @param aRequestedWidth width of the requested thumbnail
- * @param aRequestedHeight height of the requested thumbnail
- */
- requestThumbnail: function (aTaskbarCallback, aRequestedWidth, aRequestedHeight) {
- this.resizeCanvasPreview(aRequestedWidth, aRequestedHeight);
- this.updateCanvasPreview(false, (aThumbnailCanvas) => {
- aTaskbarCallback.done(aThumbnailCanvas, false);
- });
- },
-
- // Event handling
-
- onClose: function () {
- this.win.tabbrowser.removeTab(this.tab);
- },
-
- onActivate: function () {
- this.win.tabbrowser.selectedTab = this.tab;
-
- // Accept activation - this will restore the browser window
- // if it's minimized
- return true;
- },
-
- // nsIDOMEventListener
- handleEvent: function (evt) {
- switch (evt.type) {
- case "TabAttrModified":
- this.updateTitleAndTooltip();
- break;
- }
- }
-};
-
-XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
- function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
- return canvasInterface.DRAWWINDOW_DRAW_VIEW
- | canvasInterface.DRAWWINDOW_DRAW_CARET
- | canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES
- | canvasInterface.DRAWWINDOW_DO_NOT_FLUSH;
-});
-
-// TabWindow
-
-/*
- * This class monitors a browser window for changes to its tabs
- *
- * @param win
- * The nsIDOMWindow browser window
- */
-function TabWindow(win) {
- this.win = win;
- this.tabbrowser = win.gBrowser;
-
- this.previews = new Map();
-
- for (let i = 0; i < this.tabEvents.length; i++)
- this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
-
- for (let i = 0; i < this.winEvents.length; i++)
- this.win.addEventListener(this.winEvents[i], this, false);
-
- this.tabbrowser.addTabsProgressListener(this);
-
- AeroPeek.windows.push(this);
- let tabs = this.tabbrowser.tabs;
- for (let i = 0; i < tabs.length; i++)
- this.newTab(tabs[i]);
-
- this.updateTabOrdering();
- AeroPeek.checkPreviewCount();
-}
-
-TabWindow.prototype = {
- _enabled: false,
- _cachedWidth: 0,
- _cachedHeight: 0,
- tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
- winEvents: ["resize"],
-
- destroy: function () {
- this._destroying = true;
-
- let tabs = this.tabbrowser.tabs;
-
- this.tabbrowser.removeTabsProgressListener(this);
-
- for (let i = 0; i < this.winEvents.length; i++)
- this.win.removeEventListener(this.winEvents[i], this, false);
-
- for (let i = 0; i < this.tabEvents.length; i++)
- this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
-
- for (let i = 0; i < tabs.length; i++)
- this.removeTab(tabs[i]);
-
- let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
- AeroPeek.windows.splice(idx, 1);
- AeroPeek.checkPreviewCount();
- },
-
- get width () {
- return this.win.innerWidth;
- },
- get height () {
- return this.win.innerHeight;
- },
-
- cacheDims: function () {
- this._cachedWidth = this.width;
- this._cachedHeight = this.height;
- },
-
- testCacheDims: function () {
- return this._cachedWidth == this.width && this._cachedHeight == this.height;
- },
-
- // Invoked when the given tab is added to this window
- newTab: function (tab) {
- let controller = new PreviewController(this, tab);
- // It's OK to add the preview now while the favicon still loads.
- this.previews.set(tab, controller.preview);
- AeroPeek.addPreview(controller.preview);
- // updateTitleAndTooltip relies on having controller.preview which is lazily resolved.
- // Now that we've updated this.previews, it will resolve successfully.
- controller.updateTitleAndTooltip();
- },
-
- createTabPreview: function (controller) {
- let docShell = this.win
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller);
- preview.visible = AeroPeek.enabled;
- preview.active = this.tabbrowser.selectedTab == controller.tab;
- this.onLinkIconAvailable(controller.tab.linkedBrowser,
- controller.tab.getAttribute("image"));
- return preview;
- },
-
- // Invoked when the given tab is closed
- removeTab: function (tab) {
- let preview = this.previewFromTab(tab);
- preview.active = false;
- preview.visible = false;
- preview.move(null);
- preview.controller.wrappedJSObject.destroy();
-
- this.previews.delete(tab);
- AeroPeek.removePreview(preview);
- },
-
- get enabled () {
- return this._enabled;
- },
-
- set enabled (enable) {
- this._enabled = enable;
- // Because making a tab visible requires that the tab it is next to be
- // visible, it is far simpler to unset the 'next' tab and recreate them all
- // at once.
- for (let [, preview] of this.previews) {
- preview.move(null);
- preview.visible = enable;
- }
- this.updateTabOrdering();
- },
-
- previewFromTab: function (tab) {
- return this.previews.get(tab);
- },
-
- updateTabOrdering: function () {
- let previews = this.previews;
- let tabs = this.tabbrowser.tabs;
-
- // Previews are internally stored using a map, so we need to iterate the
- // tabbrowser's array of tabs to retrieve previews in the same order.
- let inorder = [];
- for (let t of tabs) {
- if (previews.has(t)) {
- inorder.push(previews.get(t));
- }
- }
-
- // Since the internal taskbar array has not yet been updated we must force
- // on it the sorting order of our local array. To do so we must walk
- // the local array backwards, otherwise we would send move requests in the
- // wrong order. See bug 522610 for details.
- for (let i = inorder.length - 1; i >= 0; i--) {
- inorder[i].move(inorder[i + 1] || null);
- }
- },
-
- // nsIDOMEventListener
- handleEvent: function (evt) {
- let tab = evt.originalTarget;
- switch (evt.type) {
- case "TabOpen":
- this.newTab(tab);
- this.updateTabOrdering();
- break;
- case "TabClose":
- this.removeTab(tab);
- this.updateTabOrdering();
- break;
- case "TabSelect":
- this.previewFromTab(tab).active = true;
- break;
- case "TabMove":
- this.updateTabOrdering();
- break;
- case "resize":
- if (!AeroPeek._prefenabled)
- return;
- this.onResize();
- break;
- }
- },
-
- // Set or reset a timer that will invalidate visible thumbnails soon.
- setInvalidationTimer: function () {
- if (!this.invalidateTimer) {
- this.invalidateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
- }
- this.invalidateTimer.cancel();
-
- // delay 1 second before invalidating
- this.invalidateTimer.initWithCallback(() => {
- // invalidate every preview. note the internal implementation of
- // invalidate ignores thumbnails that aren't visible.
- this.previews.forEach(function (aPreview) {
- let controller = aPreview.controller.wrappedJSObject;
- if (!controller.testCacheBrowserDims()) {
- controller.cacheBrowserDims();
- aPreview.invalidate();
- }
- });
- }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
- },
-
- onResize: function () {
- // Specific to a window.
-
- // Call invalidate on each tab thumbnail so that Windows will request an
- // updated image. However don't do this repeatedly across multiple resize
- // events triggered during window border drags.
-
- if (this.testCacheDims()) {
- return;
- }
-
- // update the window dims on our TabWindow object.
- this.cacheDims();
-
- // invalidate soon
- this.setInvalidationTimer();
- },
-
- invalidateTabPreview: function(aBrowser) {
- for (let [tab, preview] of this.previews) {
- if (aBrowser == tab.linkedBrowser) {
- preview.invalidate();
- break;
- }
- }
- },
-
- // Browser progress listener
-
- onLocationChange: function (aBrowser) {
- // I'm not sure we need this, onStateChange does a really good job
- // of picking up page changes.
- // this.invalidateTabPreview(aBrowser);
- },
-
- onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
- if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
- aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
- this.invalidateTabPreview(aBrowser);
- }
- },
-
- directRequestProtocols: new Set([
- "file", "chrome", "resource", "about"
- ]),
- onLinkIconAvailable: function (aBrowser, aIconURL) {
- let requestURL = null;
- if (aIconURL) {
- let shouldRequestFaviconURL = true;
- try {
- let urlObject = NetUtil.newURI(aIconURL);
- shouldRequestFaviconURL =
- !this.directRequestProtocols.has(urlObject.scheme);
- } catch (ex) {}
-
- requestURL = shouldRequestFaviconURL ?
- "moz-anno:favicon:" + aIconURL :
- aIconURL;
- }
- let isDefaultFavicon = !requestURL;
- getFaviconAsImage(
- requestURL,
- PrivateBrowsingUtils.isWindowPrivate(this.win),
- img => {
- let index = this.tabbrowser.browsers.indexOf(aBrowser);
- // Only add it if we've found the index and the URI is still the same.
- // The tab could have closed, and there's no guarantee the icons
- // will have finished fetching 'in order'.
- if (index != -1) {
- let tab = this.tabbrowser.tabs[index];
- let preview = this.previews.get(tab);
- if (tab.getAttribute("image") == aIconURL ||
- (!preview.icon && isDefaultFavicon)) {
- preview.icon = img;
- }
- }
- }
- );
- }
-}
-
-// AeroPeek
-
-/*
- * This object acts as global storage and external interface for this feature.
- * It maintains the values of the prefs.
- */
-this.AeroPeek = {
- available: false,
- // Does the pref say we're enabled?
- __prefenabled: false,
-
- _enabled: true,
-
- initialized: false,
-
- // nsITaskbarTabPreview array
- previews: [],
-
- // TabWindow array
- windows: [],
-
- // nsIWinTaskbar service
- taskbar: null,
-
- // Maximum number of previews
- maxpreviews: 20,
-
- // Length of time in seconds that previews are cached
- cacheLifespan: 20,
-
- initialize: function () {
- if (!(WINTASKBAR_CONTRACTID in Cc))
- return;
- this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar);
- this.available = this.taskbar.available;
- if (!this.available)
- return;
-
- this.prefs.addObserver(TOGGLE_PREF_NAME, this, true);
- this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
- this.initialized = true;
- },
-
- destroy: function destroy() {
- this._enabled = false;
-
- if (this.cacheTimer)
- this.cacheTimer.cancel();
- },
-
- get enabled() {
- return this._enabled;
- },
-
- set enabled(enable) {
- if (this._enabled == enable)
- return;
-
- this._enabled = enable;
-
- this.windows.forEach(function (win) {
- win.enabled = enable;
- });
- },
-
- get _prefenabled() {
- return this.__prefenabled;
- },
-
- set _prefenabled(enable) {
- if (enable == this.__prefenabled) {
- return;
- }
- this.__prefenabled = enable;
-
- if (enable) {
- this.enable();
- } else {
- this.disable();
- }
- },
-
- _observersAdded: false,
-
- enable() {
- if (!this._observersAdded) {
- this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, true);
- this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, true);
- PlacesUtils.history.addObserver(this, true);
- this._observersAdded = true;
- }
-
- this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
-
- this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
-
- // If the user toggled us on/off while the browser was already up
- // (rather than this code running on startup because the pref was
- // already set to true), we must initialize previews for open windows:
- if (this.initialized) {
- let browserWindows = Services.wm.getEnumerator("navigator:browser");
- while (browserWindows.hasMoreElements()) {
- let win = browserWindows.getNext();
- if (!win.closed) {
- this.onOpenWindow(win);
- }
- }
- }
- },
-
- disable() {
- while (this.windows.length) {
- // We can't call onCloseWindow here because it'll bail if we're not
- // enabled.
- let tabWinObject = this.windows[0];
- tabWinObject.destroy(); // This will remove us from the array.
- delete tabWinObject.win.gTaskbarTabGroup; // Tidy up the window.
- }
- },
-
- addPreview: function (preview) {
- this.previews.push(preview);
- this.checkPreviewCount();
- },
-
- removePreview: function (preview) {
- let idx = this.previews.indexOf(preview);
- this.previews.splice(idx, 1);
- this.checkPreviewCount();
- },
-
- checkPreviewCount: function () {
- if (!this._prefenabled) {
- return;
- }
- this.enabled = this.previews.length <= this.maxpreviews;
- },
-
- onOpenWindow: function (win) {
- // This occurs when the taskbar service is not available (xp, vista)
- if (!this.available || !this._prefenabled)
- return;
-
- win.gTaskbarTabGroup = new TabWindow(win);
- },
-
- onCloseWindow: function (win) {
- // This occurs when the taskbar service is not available (xp, vista)
- if (!this.available || !this._prefenabled)
- return;
-
- win.gTaskbarTabGroup.destroy();
- delete win.gTaskbarTabGroup;
-
- if (this.windows.length == 0)
- this.destroy();
- },
-
- resetCacheTimer: function () {
- this.cacheTimer.cancel();
- this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
- },
-
- // nsIObserver
- observe: function (aSubject, aTopic, aData) {
- if (aTopic == "nsPref:changed" && aData == TOGGLE_PREF_NAME) {
- this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
- }
- if (!this._prefenabled) {
- return;
- }
- switch (aTopic) {
- case "nsPref:changed":
- if (aData == CACHE_EXPIRATION_TIME_PREF_NAME)
- break;
-
- if (aData == DISABLE_THRESHOLD_PREF_NAME)
- this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
- // Might need to enable/disable ourselves
- this.checkPreviewCount();
- break;
- case "timer-callback":
- this.previews.forEach(function (preview) {
- let controller = preview.controller.wrappedJSObject;
- controller.resetCanvasPreview();
- });
- break;
- }
- },
-
- /* nsINavHistoryObserver implementation */
- onBeginUpdateBatch() {},
- onEndUpdateBatch() {},
- onVisit() {},
- onTitleChanged() {},
- onFrecencyChanged() {},
- onManyFrecenciesChanged() {},
- onDeleteURI() {},
- onClearHistory() {},
- onDeleteVisits() {},
- onPageChanged(uri, changedConst, newValue) {
- if (this.enabled && changedConst == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
- for (let win of this.windows) {
- for (let [tab, ] of win.previews) {
- if (tab.getAttribute("image") == newValue) {
- win.onLinkIconAvailable(tab.linkedBrowser, newValue);
- }
- }
- }
- }
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsISupportsWeakReference,
- Ci.nsINavHistoryObserver,
- Ci.nsIObserver
- ]),
-};
-
-XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", () =>
- Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
-);
-
-XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs",
- "@mozilla.org/preferences-service;1",
- "nsIPrefBranch");
-
-AeroPeek.initialize();
diff --git a/browser/modules/moz.build b/browser/modules/moz.build
deleted file mode 100644
index 684f662d7..000000000
--- a/browser/modules/moz.build
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-EXTRA_JS_MODULES += [
- 'AboutHome.jsm',
- 'AboutNewTab.jsm',
- 'AttributionCode.jsm',
- 'BrowserUITelemetry.jsm',
- 'BrowserUsageTelemetry.jsm',
- 'CastingApps.jsm',
- 'ContentClick.jsm',
- 'ContentCrashHandlers.jsm',
- 'ContentLinkHandler.jsm',
- 'ContentObservers.jsm',
- 'ContentSearch.jsm',
- 'ContentWebRTC.jsm',
- 'DirectoryLinksProvider.jsm',
- 'E10SUtils.jsm',
- 'Feeds.jsm',
- 'FormSubmitObserver.jsm',
- 'FormValidationHandler.jsm',
- 'HiddenFrame.jsm',
- 'LaterRun.jsm',
- 'NetworkPrioritizer.jsm',
- 'offlineAppCache.jsm',
- 'PermissionUI.jsm',
- 'PluginContent.jsm',
- 'ProcessHangMonitor.jsm',
- 'ReaderParent.jsm',
- 'RecentWindow.jsm',
- 'RemotePrompt.jsm',
- 'Sanitizer.jsm',
- 'SitePermissions.jsm',
- 'TransientPrefs.jsm',
- 'URLBarZoom.jsm',
- 'webrtcUI.jsm',
-]
-
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
- EXTRA_JS_MODULES += [
- 'Windows8WindowFrameColor.jsm',
- 'WindowsJumpLists.jsm',
- 'WindowsPreviewPerTab.jsm',
- ]
diff --git a/browser/modules/offlineAppCache.jsm b/browser/modules/offlineAppCache.jsm
deleted file mode 100644
index 5d0e3481a..000000000
--- a/browser/modules/offlineAppCache.jsm
+++ /dev/null
@@ -1,20 +0,0 @@
-/* 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/. */
-
-this.EXPORTED_SYMBOLS = ["OfflineAppCacheHelper"];
-
-Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-this.OfflineAppCacheHelper = {
- clear: function() {
- var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
- var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
- try {
- appCacheStorage.asyncEvictStorage(null);
- } catch (er) {}
- }
-};
diff --git a/browser/modules/webrtcUI.jsm b/browser/modules/webrtcUI.jsm
deleted file mode 100644
index 08de46bb3..000000000
--- a/browser/modules/webrtcUI.jsm
+++ /dev/null
@@ -1,969 +0,0 @@
-/* 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";
-
-this.EXPORTED_SYMBOLS = ["webrtcUI"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
- "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
- "resource://gre/modules/PluralForm.jsm");
-
-this.webrtcUI = {
- init: function () {
- Services.obs.addObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished", false);
-
- let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
- .getService(Ci.nsIMessageBroadcaster);
- ppmm.addMessageListener("webrtc:UpdatingIndicators", this);
- ppmm.addMessageListener("webrtc:UpdateGlobalIndicators", this);
- ppmm.addMessageListener("child-process-shutdown", this);
-
- let mm = Cc["@mozilla.org/globalmessagemanager;1"]
- .getService(Ci.nsIMessageListenerManager);
- mm.addMessageListener("rtcpeer:Request", this);
- mm.addMessageListener("rtcpeer:CancelRequest", this);
- mm.addMessageListener("webrtc:Request", this);
- mm.addMessageListener("webrtc:CancelRequest", this);
- mm.addMessageListener("webrtc:UpdateBrowserIndicators", this);
- },
-
- uninit: function () {
- Services.obs.removeObserver(maybeAddMenuIndicator, "browser-delayed-startup-finished");
-
- let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
- .getService(Ci.nsIMessageBroadcaster);
- ppmm.removeMessageListener("webrtc:UpdatingIndicators", this);
- ppmm.removeMessageListener("webrtc:UpdateGlobalIndicators", this);
-
- let mm = Cc["@mozilla.org/globalmessagemanager;1"]
- .getService(Ci.nsIMessageListenerManager);
- mm.removeMessageListener("rtcpeer:Request", this);
- mm.removeMessageListener("rtcpeer:CancelRequest", this);
- mm.removeMessageListener("webrtc:Request", this);
- mm.removeMessageListener("webrtc:CancelRequest", this);
- mm.removeMessageListener("webrtc:UpdateBrowserIndicators", this);
-
- if (gIndicatorWindow) {
- gIndicatorWindow.close();
- gIndicatorWindow = null;
- }
- },
-
- processIndicators: new Map(),
-
- get showGlobalIndicator() {
- for (let [, indicators] of this.processIndicators) {
- if (indicators.showGlobalIndicator)
- return true;
- }
- return false;
- },
-
- get showCameraIndicator() {
- for (let [, indicators] of this.processIndicators) {
- if (indicators.showCameraIndicator)
- return true;
- }
- return false;
- },
-
- get showMicrophoneIndicator() {
- for (let [, indicators] of this.processIndicators) {
- if (indicators.showMicrophoneIndicator)
- return true;
- }
- return false;
- },
-
- get showScreenSharingIndicator() {
- let list = [""];
- for (let [, indicators] of this.processIndicators) {
- if (indicators.showScreenSharingIndicator)
- list.push(indicators.showScreenSharingIndicator);
- }
-
- let precedence =
- ["Screen", "Window", "Application", "Browser", ""];
-
- list.sort((a, b) => { return precedence.indexOf(a) -
- precedence.indexOf(b); });
-
- return list[0];
- },
-
- _streams: [],
- // The boolean parameters indicate which streams should be included in the result.
- getActiveStreams: function(aCamera, aMicrophone, aScreen) {
- return webrtcUI._streams.filter(aStream => {
- let state = aStream.state;
- return aCamera && state.camera ||
- aMicrophone && state.microphone ||
- aScreen && state.screen;
- }).map(aStream => {
- let state = aStream.state;
- let types = {camera: state.camera, microphone: state.microphone,
- screen: state.screen};
- let browser = aStream.browser;
- let browserWindow = browser.ownerGlobal;
- let tab = browserWindow.gBrowser &&
- browserWindow.gBrowser.getTabForBrowser(browser);
- return {uri: state.documentURI, tab: tab, browser: browser, types: types};
- });
- },
-
- swapBrowserForNotification: function(aOldBrowser, aNewBrowser) {
- for (let stream of this._streams) {
- if (stream.browser == aOldBrowser)
- stream.browser = aNewBrowser;
- }
- },
-
- forgetStreamsFromBrowser: function(aBrowser) {
- this._streams = this._streams.filter(stream => stream.browser != aBrowser);
- },
-
- showSharingDoorhanger: function(aActiveStream, aType) {
- let browserWindow = aActiveStream.browser.ownerGlobal;
- if (aActiveStream.tab) {
- browserWindow.gBrowser.selectedTab = aActiveStream.tab;
- } else {
- aActiveStream.browser.focus();
- }
- browserWindow.focus();
- let identityBox = browserWindow.document.getElementById("identity-box");
- if (AppConstants.platform == "macosx" && !Services.focus.activeWindow) {
- browserWindow.addEventListener("activate", function onActivate() {
- browserWindow.removeEventListener("activate", onActivate);
- Services.tm.mainThread.dispatch(function() {
- identityBox.click();
- }, Ci.nsIThread.DISPATCH_NORMAL);
- });
- Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport)
- .activateApplication(true);
- return;
- }
- identityBox.click();
- },
-
- updateMainActionLabel: function(aMenuList) {
- let type = aMenuList.selectedItem.getAttribute("devicetype");
- let document = aMenuList.ownerDocument;
- document.getElementById("webRTC-all-windows-shared").hidden = type != "Screen";
-
- // If we are also requesting audio in addition to screen sharing,
- // always use a generic label.
- if (!document.getElementById("webRTC-selectMicrophone").hidden)
- type = "";
-
- let bundle = document.defaultView.gNavigatorBundle;
- let stringId = "getUserMedia.share" + (type || "SelectedItems") + ".label";
- let popupnotification = aMenuList.parentNode.parentNode;
- popupnotification.setAttribute("buttonlabel", bundle.getString(stringId));
- },
-
- receiveMessage: function(aMessage) {
- switch (aMessage.name) {
-
- // Add-ons can override stock permission behavior by doing:
- //
- // var stockReceiveMessage = webrtcUI.receiveMessage;
- //
- // webrtcUI.receiveMessage = function(aMessage) {
- // switch (aMessage.name) {
- // case "rtcpeer:Request": {
- // // new code.
- // break;
- // ...
- // default:
- // return stockReceiveMessage.call(this, aMessage);
- //
- // Intercepting gUM and peerConnection requests should let an add-on
- // limit PeerConnection activity with automatic rules and/or prompts
- // in a sensible manner that avoids double-prompting in typical
- // gUM+PeerConnection scenarios. For example:
- //
- // State Sample Action
- // --------------------------------------------------------------
- // No IP leaked yet + No gUM granted Warn user
- // No IP leaked yet + gUM granted Avoid extra dialog
- // No IP leaked yet + gUM request pending. Delay until gUM grant
- // IP already leaked Too late to warn
-
- case "rtcpeer:Request": {
- // Always allow. This code-point exists for add-ons to override.
- let { callID, windowID } = aMessage.data;
- // Also available: isSecure, innerWindowID. For contentWindow:
- //
- // let contentWindow = Services.wm.getOuterWindowWithId(windowID);
-
- let mm = aMessage.target.messageManager;
- mm.sendAsyncMessage("rtcpeer:Allow",
- { callID: callID, windowID: windowID });
- break;
- }
- case "rtcpeer:CancelRequest":
- // No data to release. This code-point exists for add-ons to override.
- break;
- case "webrtc:Request":
- prompt(aMessage.target, aMessage.data);
- break;
- case "webrtc:CancelRequest":
- removePrompt(aMessage.target, aMessage.data);
- break;
- case "webrtc:UpdatingIndicators":
- webrtcUI._streams = [];
- break;
- case "webrtc:UpdateGlobalIndicators":
- updateIndicators(aMessage.data, aMessage.target);
- break;
- case "webrtc:UpdateBrowserIndicators":
- let id = aMessage.data.windowId;
- let index;
- for (index = 0; index < webrtcUI._streams.length; ++index) {
- if (webrtcUI._streams[index].state.windowId == id)
- break;
- }
- // If there's no documentURI, the update is actually a removal of the
- // stream, triggered by the recording-window-ended notification.
- if (!aMessage.data.documentURI && index < webrtcUI._streams.length)
- webrtcUI._streams.splice(index, 1);
- else
- webrtcUI._streams[index] = {browser: aMessage.target, state: aMessage.data};
- let tabbrowser = aMessage.target.ownerGlobal.gBrowser;
- if (tabbrowser)
- tabbrowser.setBrowserSharing(aMessage.target, aMessage.data);
- break;
- case "child-process-shutdown":
- webrtcUI.processIndicators.delete(aMessage.target);
- updateIndicators(null, null);
- break;
- }
- }
-};
-
-function getBrowserForWindow(aContentWindow) {
- return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
-}
-
-function denyRequest(aBrowser, aRequest) {
- aBrowser.messageManager.sendAsyncMessage("webrtc:Deny",
- {callID: aRequest.callID,
- windowID: aRequest.windowID});
-}
-
-function getHost(uri, href) {
- let host;
- try {
- if (!uri) {
- uri = Services.io.newURI(href, null, null);
- }
- host = uri.host;
- } catch (ex) {}
- if (!host) {
- if (uri && uri.scheme.toLowerCase() == "about") {
- // For about URIs, just use the full spec, without any #hash parts.
- host = uri.specIgnoringRef;
- } else {
- // This is unfortunate, but we should display *something*...
- const kBundleURI = "chrome://browser/locale/browser.properties";
- let bundle = Services.strings.createBundle(kBundleURI);
- host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
- }
- }
- return host;
-}
-
-function prompt(aBrowser, aRequest) {
- let {audioDevices: audioDevices, videoDevices: videoDevices,
- sharingScreen: sharingScreen, sharingAudio: sharingAudio,
- requestTypes: requestTypes} = aRequest;
- let uri;
- try {
- // This fails for principals that serialize to "null", e.g. file URIs.
- uri = Services.io.newURI(aRequest.origin, null, null);
- } catch (e) {
- uri = Services.io.newURI(aRequest.documentURI, null, null);
- }
- let host = getHost(uri);
- let chromeDoc = aBrowser.ownerDocument;
- let chromeWin = chromeDoc.defaultView;
- let stringBundle = chromeWin.gNavigatorBundle;
- let stringId = "getUserMedia.share" + requestTypes.join("And") + ".message";
- let message = stringBundle.getFormattedString(stringId, [host]);
-
- let mainLabel;
- if (sharingScreen || sharingAudio) {
- mainLabel = stringBundle.getString("getUserMedia.shareSelectedItems.label");
- } else {
- let string = stringBundle.getString("getUserMedia.shareSelectedDevices.label");
- mainLabel = PluralForm.get(requestTypes.length, string);
- }
-
- let notification; // Used by action callbacks.
- let mainAction = {
- label: mainLabel,
- accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
- // The real callback will be set during the "showing" event. The
- // empty function here is so that PopupNotifications.show doesn't
- // reject the action.
- callback: function() {}
- };
-
- let secondaryActions = [
- {
- label: stringBundle.getString("getUserMedia.denyRequest.label"),
- accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
- callback: function () {
- denyRequest(notification.browser, aRequest);
- }
- }
- ];
- // Bug 1037438: implement 'never' for screen sharing.
- if (!sharingScreen && !sharingAudio) {
- secondaryActions.push({
- label: stringBundle.getString("getUserMedia.never.label"),
- accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
- callback: function () {
- denyRequest(notification.browser, aRequest);
- // Let someone save "Never" for http sites so that they can be stopped from
- // bothering you with doorhangers.
- let perms = Services.perms;
- if (audioDevices.length)
- perms.add(uri, "microphone", perms.DENY_ACTION);
- if (videoDevices.length)
- perms.add(uri, "camera", perms.DENY_ACTION);
- }
- });
- }
-
- if (aRequest.secure && !sharingScreen && !sharingAudio) {
- // Don't show the 'Always' action if the connection isn't secure, or for
- // screen/audio sharing (because we can't guess which window the user wants
- // to share without prompting).
- secondaryActions.unshift({
- label: stringBundle.getString("getUserMedia.always.label"),
- accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
- callback: function (aState) {
- mainAction.callback(aState, true);
- }
- });
- }
-
- let options = {
- eventCallback: function(aTopic, aNewBrowser) {
- if (aTopic == "swapping")
- return true;
-
- let chromeDoc = this.browser.ownerDocument;
-
- // Clean-up video streams of screensharing previews.
- if ((aTopic == "dismissed" || aTopic == "removed") &&
- requestTypes.includes("Screen")) {
- let video = chromeDoc.getElementById("webRTC-previewVideo");
- video.deviceId = undefined;
- if (video.stream) {
- video.stream.getTracks().forEach(t => t.stop());
- video.stream = null;
- video.src = null;
- chromeDoc.getElementById("webRTC-preview").hidden = true;
- }
- let menupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
- if (menupopup._commandEventListener) {
- menupopup.removeEventListener("command", menupopup._commandEventListener);
- menupopup._commandEventListener = null;
- }
- }
-
- if (aTopic != "showing")
- return false;
-
- // DENY_ACTION is handled immediately by MediaManager, but handling
- // of ALLOW_ACTION is delayed until the popupshowing event
- // to avoid granting permissions automatically to background tabs.
- if (aRequest.secure) {
- let perms = Services.perms;
-
- let micPerm = perms.testExactPermission(uri, "microphone");
- if (micPerm == perms.PROMPT_ACTION)
- micPerm = perms.UNKNOWN_ACTION;
-
- let camPerm = perms.testExactPermission(uri, "camera");
-
- let mediaManagerPerm =
- perms.testExactPermission(uri, "MediaManagerVideo");
- if (mediaManagerPerm) {
- perms.remove(uri, "MediaManagerVideo");
- }
-
- if (camPerm == perms.PROMPT_ACTION)
- camPerm = perms.UNKNOWN_ACTION;
-
- // Screen sharing shouldn't follow the camera permissions.
- if (videoDevices.length && sharingScreen)
- camPerm = perms.UNKNOWN_ACTION;
-
- // We don't check that permissions are set to ALLOW_ACTION in this
- // test; only that they are set. This is because if audio is allowed
- // and video is denied persistently, we don't want to show the prompt,
- // and will grant audio access immediately.
- if ((!audioDevices.length || micPerm) && (!videoDevices.length || camPerm)) {
- // All permissions we were about to request are already persistently set.
- let allowedDevices = [];
- if (videoDevices.length && camPerm == perms.ALLOW_ACTION) {
- allowedDevices.push(videoDevices[0].deviceIndex);
- let perms = Services.perms;
- perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
- perms.EXPIRE_SESSION);
- }
- if (audioDevices.length && micPerm == perms.ALLOW_ACTION)
- allowedDevices.push(audioDevices[0].deviceIndex);
-
- // Remember on which URIs we found persistent permissions so that we
- // can remove them if the user clicks 'Stop Sharing'. There's no
- // other way for the stop sharing code to know the hostnames of frames
- // using devices until bug 1066082 is fixed.
- let browser = this.browser;
- browser._devicePermissionURIs = browser._devicePermissionURIs || [];
- browser._devicePermissionURIs.push(uri);
-
- let mm = browser.messageManager;
- mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
- windowID: aRequest.windowID,
- devices: allowedDevices});
- this.remove();
- return true;
- }
- }
-
- function listDevices(menupopup, devices) {
- while (menupopup.lastChild)
- menupopup.removeChild(menupopup.lastChild);
-
- for (let device of devices)
- addDeviceToList(menupopup, device.name, device.deviceIndex);
- }
-
- function listScreenShareDevices(menupopup, devices) {
- while (menupopup.lastChild)
- menupopup.removeChild(menupopup.lastChild);
-
- let type = devices[0].mediaSource;
- let typeName = type.charAt(0).toUpperCase() + type.substr(1);
-
- let label = chromeDoc.getElementById("webRTC-selectWindow-label");
- let stringId = "getUserMedia.select" + typeName;
- label.setAttribute("value",
- stringBundle.getString(stringId + ".label"));
- label.setAttribute("accesskey",
- stringBundle.getString(stringId + ".accesskey"));
-
- // "No <type>" is the default because we can't pick a
- // 'default' window to share.
- addDeviceToList(menupopup,
- stringBundle.getString("getUserMedia.no" + typeName + ".label"),
- "-1");
- menupopup.appendChild(chromeDoc.createElement("menuseparator"));
-
- // Build the list of 'devices'.
- let monitorIndex = 1;
- for (let i = 0; i < devices.length; ++i) {
- let device = devices[i];
-
- let name;
- // Building screen list from available screens.
- if (type == "screen") {
- if (device.name == "Primary Monitor") {
- name = stringBundle.getString("getUserMedia.shareEntireScreen.label");
- } else {
- name = stringBundle.getFormattedString("getUserMedia.shareMonitor.label",
- [monitorIndex]);
- ++monitorIndex;
- }
- }
- else {
- name = device.name;
- if (type == "application") {
- // The application names returned by the platform are of the form:
- // <window count>\x1e<application name>
- let sepIndex = name.indexOf("\x1e");
- let count = name.slice(0, sepIndex);
- let stringId = "getUserMedia.shareApplicationWindowCount.label";
- name = PluralForm.get(parseInt(count), stringBundle.getString(stringId))
- .replace("#1", name.slice(sepIndex + 1))
- .replace("#2", count);
- }
- }
- let item = addDeviceToList(menupopup, name, i, typeName);
- item.deviceId = device.id;
- if (device.scary)
- item.scary = true;
- }
-
- // Always re-select the "No <type>" item.
- chromeDoc.getElementById("webRTC-selectWindow-menulist").removeAttribute("value");
- chromeDoc.getElementById("webRTC-all-windows-shared").hidden = true;
- menupopup._commandEventListener = event => {
- let video = chromeDoc.getElementById("webRTC-previewVideo");
- if (video.stream) {
- video.stream.getTracks().forEach(t => t.stop());
- video.stream = null;
- }
-
- let deviceId = event.target.deviceId;
- if (deviceId == undefined) {
- chromeDoc.getElementById("webRTC-preview").hidden = true;
- video.src = null;
- return;
- }
-
- let scary = event.target.scary;
- let warning = chromeDoc.getElementById("webRTC-previewWarning");
- warning.hidden = !scary;
- let chromeWin = chromeDoc.defaultView;
- if (scary) {
- warning.hidden = false;
- let string;
- let bundle = chromeWin.gNavigatorBundle;
-
- let learnMoreText =
- bundle.getString("getUserMedia.shareScreen.learnMoreLabel");
- let baseURL =
- Services.urlFormatter.formatURLPref("app.support.baseURL");
- let learnMore =
- "<label class='text-link' href='" + baseURL + "screenshare-safety'>" +
- learnMoreText + "</label>";
-
- if (type == "screen") {
- string = bundle.getFormattedString("getUserMedia.shareScreenWarning.message",
- [learnMore]);
- }
- else {
- let brand =
- chromeDoc.getElementById("bundle_brand").getString("brandShortName");
- string = bundle.getFormattedString("getUserMedia.shareFirefoxWarning.message",
- [brand, learnMore]);
- }
- warning.innerHTML = string;
- }
-
- let perms = Services.perms;
- let chromeUri = Services.io.newURI(chromeDoc.documentURI, null, null);
- perms.add(chromeUri, "MediaManagerVideo", perms.ALLOW_ACTION,
- perms.EXPIRE_SESSION);
-
- video.deviceId = deviceId;
- let constraints = { video: { mediaSource: type, deviceId: {exact: deviceId } } };
- chromeWin.navigator.mediaDevices.getUserMedia(constraints).then(stream => {
- if (video.deviceId != deviceId) {
- // The user has selected a different device or closed the panel
- // before getUserMedia finished.
- stream.getTracks().forEach(t => t.stop());
- return;
- }
- video.src = chromeWin.URL.createObjectURL(stream);
- video.stream = stream;
- chromeDoc.getElementById("webRTC-preview").hidden = false;
- video.onloadedmetadata = function(e) {
- video.play();
- };
- });
- };
- menupopup.addEventListener("command", menupopup._commandEventListener);
- }
-
- function addDeviceToList(menupopup, deviceName, deviceIndex, type) {
- let menuitem = chromeDoc.createElement("menuitem");
- menuitem.setAttribute("value", deviceIndex);
- menuitem.setAttribute("label", deviceName);
- menuitem.setAttribute("tooltiptext", deviceName);
- if (type)
- menuitem.setAttribute("devicetype", type);
- menupopup.appendChild(menuitem);
- return menuitem;
- }
-
- chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length || sharingScreen;
- chromeDoc.getElementById("webRTC-selectWindowOrScreen").hidden = !sharingScreen || !videoDevices.length;
- chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length || sharingAudio;
-
- let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
- let windowMenupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
- let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
- if (sharingScreen)
- listScreenShareDevices(windowMenupopup, videoDevices);
- else
- listDevices(camMenupopup, videoDevices);
-
- if (!sharingAudio)
- listDevices(micMenupopup, audioDevices);
-
- this.mainAction.callback = function(aState, aRemember) {
- let allowedDevices = [];
- let perms = Services.perms;
- if (videoDevices.length) {
- let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
- let videoDeviceIndex = chromeDoc.getElementById(listId).value;
- let allowCamera = videoDeviceIndex != "-1";
- if (allowCamera) {
- allowedDevices.push(videoDeviceIndex);
- // Session permission will be removed after use
- // (it's really one-shot, not for the entire session)
- perms.add(uri, "MediaManagerVideo", perms.ALLOW_ACTION,
- perms.EXPIRE_SESSION);
- }
- if (aRemember) {
- perms.add(uri, "camera",
- allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
- }
- }
- if (audioDevices.length) {
- if (!sharingAudio) {
- let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
- let allowMic = audioDeviceIndex != "-1";
- if (allowMic)
- allowedDevices.push(audioDeviceIndex);
- if (aRemember) {
- perms.add(uri, "microphone",
- allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
- }
- } else {
- // Only one device possible for audio capture.
- allowedDevices.push(0);
- }
- }
-
- if (!allowedDevices.length) {
- denyRequest(notification.browser, aRequest);
- return;
- }
-
- if (aRemember) {
- // Remember on which URIs we set persistent permissions so that we
- // can remove them if the user clicks 'Stop Sharing'.
- aBrowser._devicePermissionURIs = aBrowser._devicePermissionURIs || [];
- aBrowser._devicePermissionURIs.push(uri);
- }
-
- let mm = notification.browser.messageManager;
- mm.sendAsyncMessage("webrtc:Allow", {callID: aRequest.callID,
- windowID: aRequest.windowID,
- devices: allowedDevices});
- };
- return false;
- }
- };
-
- let iconType = "Devices";
- if (requestTypes.length == 1 && (requestTypes[0] == "Microphone" ||
- requestTypes[0] == "AudioCapture"))
- iconType = "Microphone";
- if (requestTypes.includes("Screen"))
- iconType = "Screen";
- let anchorId = "webRTC-share" + iconType + "-notification-icon";
-
- let iconClass = iconType.toLowerCase();
- if (iconClass == "devices")
- iconClass = "camera";
- options.popupIconClass = iconClass + "-icon";
-
- notification =
- chromeWin.PopupNotifications.show(aBrowser, "webRTC-shareDevices", message,
- anchorId, mainAction, secondaryActions,
- options);
- notification.callID = aRequest.callID;
-}
-
-function removePrompt(aBrowser, aCallId) {
- let chromeWin = aBrowser.ownerGlobal;
- let notification =
- chromeWin.PopupNotifications.getNotification("webRTC-shareDevices", aBrowser);
- if (notification && notification.callID == aCallId)
- notification.remove();
-}
-
-function getGlobalIndicator() {
- if (AppConstants.platform != "macosx") {
- const INDICATOR_CHROME_URI = "chrome://browser/content/webrtcIndicator.xul";
- const features = "chrome,dialog=yes,titlebar=no,popup=yes";
-
- return Services.ww.openWindow(null, INDICATOR_CHROME_URI, "_blank", features, []);
- }
-
- let indicator = {
- _camera: null,
- _microphone: null,
- _screen: null,
-
- _hiddenDoc: Cc["@mozilla.org/appshell/appShellService;1"]
- .getService(Ci.nsIAppShellService)
- .hiddenDOMWindow.document,
- _statusBar: Cc["@mozilla.org/widget/macsystemstatusbar;1"]
- .getService(Ci.nsISystemStatusBar),
-
- _command: function(aEvent) {
- let type = this.getAttribute("type");
- if (type == "Camera" || type == "Microphone")
- type = "Devices";
- else if (type == "Window" || type == "Application" || type == "Browser")
- type = "Screen";
- webrtcUI.showSharingDoorhanger(aEvent.target.stream, type);
- },
-
- _popupShowing: function(aEvent) {
- let type = this.getAttribute("type");
- let activeStreams;
- if (type == "Camera") {
- activeStreams = webrtcUI.getActiveStreams(true, false, false);
- }
- else if (type == "Microphone") {
- activeStreams = webrtcUI.getActiveStreams(false, true, false);
- }
- else if (type == "Screen") {
- activeStreams = webrtcUI.getActiveStreams(false, false, true);
- type = webrtcUI.showScreenSharingIndicator;
- }
-
- let bundle =
- Services.strings.createBundle("chrome://browser/locale/webrtcIndicator.properties");
-
- if (activeStreams.length == 1) {
- let stream = activeStreams[0];
-
- let menuitem = this.ownerDocument.createElement("menuitem");
- let labelId = "webrtcIndicator.sharing" + type + "With.menuitem";
- let label = stream.browser.contentTitle || stream.uri;
- menuitem.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
- menuitem.setAttribute("disabled", "true");
- this.appendChild(menuitem);
-
- menuitem = this.ownerDocument.createElement("menuitem");
- menuitem.setAttribute("label",
- bundle.GetStringFromName("webrtcIndicator.controlSharing.menuitem"));
- menuitem.setAttribute("type", type);
- menuitem.stream = stream;
- menuitem.addEventListener("command", indicator._command);
-
- this.appendChild(menuitem);
- return true;
- }
-
- // We show a different menu when there are several active streams.
- let menuitem = this.ownerDocument.createElement("menuitem");
- let labelId = "webrtcIndicator.sharing" + type + "WithNTabs.menuitem";
- let count = activeStreams.length;
- let label = PluralForm.get(count, bundle.GetStringFromName(labelId)).replace("#1", count);
- menuitem.setAttribute("label", label);
- menuitem.setAttribute("disabled", "true");
- this.appendChild(menuitem);
-
- for (let stream of activeStreams) {
- let item = this.ownerDocument.createElement("menuitem");
- let labelId = "webrtcIndicator.controlSharingOn.menuitem";
- let label = stream.browser.contentTitle || stream.uri;
- item.setAttribute("label", bundle.formatStringFromName(labelId, [label], 1));
- item.setAttribute("type", type);
- item.stream = stream;
- item.addEventListener("command", indicator._command);
- this.appendChild(item);
- }
-
- return true;
- },
-
- _popupHiding: function(aEvent) {
- while (this.firstChild)
- this.firstChild.remove();
- },
-
- _setIndicatorState: function(aName, aState) {
- let field = "_" + aName.toLowerCase();
- if (aState && !this[field]) {
- let menu = this._hiddenDoc.createElement("menu");
- menu.setAttribute("id", "webRTC-sharing" + aName + "-menu");
-
- // The CSS will only be applied if the menu is actually inserted in the DOM.
- this._hiddenDoc.documentElement.appendChild(menu);
-
- this._statusBar.addItem(menu);
-
- let menupopup = this._hiddenDoc.createElement("menupopup");
- menupopup.setAttribute("type", aName);
- menupopup.addEventListener("popupshowing", this._popupShowing);
- menupopup.addEventListener("popuphiding", this._popupHiding);
- menupopup.addEventListener("command", this._command);
- menu.appendChild(menupopup);
-
- this[field] = menu;
- }
- else if (this[field] && !aState) {
- this._statusBar.removeItem(this[field]);
- this[field].remove();
- this[field] = null
- }
- },
- updateIndicatorState: function() {
- this._setIndicatorState("Camera", webrtcUI.showCameraIndicator);
- this._setIndicatorState("Microphone", webrtcUI.showMicrophoneIndicator);
- this._setIndicatorState("Screen", webrtcUI.showScreenSharingIndicator);
- },
- close: function() {
- this._setIndicatorState("Camera", false);
- this._setIndicatorState("Microphone", false);
- this._setIndicatorState("Screen", false);
- }
- };
-
- indicator.updateIndicatorState();
- return indicator;
-}
-
-function onTabSharingMenuPopupShowing(e) {
- let streams = webrtcUI.getActiveStreams(true, true, true);
- for (let streamInfo of streams) {
- let stringName = "getUserMedia.sharingMenu";
- let types = streamInfo.types;
- if (types.camera)
- stringName += "Camera";
- if (types.microphone)
- stringName += "Microphone";
- if (types.screen)
- stringName += types.screen;
-
- let doc = e.target.ownerDocument;
- let bundle = doc.defaultView.gNavigatorBundle;
-
- let origin = getHost(null, streamInfo.uri);
- let menuitem = doc.createElement("menuitem");
- menuitem.setAttribute("label", bundle.getFormattedString(stringName, [origin]));
- menuitem.stream = streamInfo;
-
- // We can only open 1 doorhanger at a time. Guessing that users would be
- // most eager to control screen/window/app sharing, and only then
- // camera/microphone sharing, in that (decreasing) order of priority.
- let doorhangerType;
- if ((/Screen|Window|Application/).test(stringName)) {
- doorhangerType = "Screen";
- } else {
- doorhangerType = "Devices";
- }
- menuitem.setAttribute("doorhangertype", doorhangerType);
- menuitem.addEventListener("command", onTabSharingMenuPopupCommand);
- e.target.appendChild(menuitem);
- }
-}
-
-function onTabSharingMenuPopupHiding(e) {
- while (this.lastChild)
- this.lastChild.remove();
-}
-
-function onTabSharingMenuPopupCommand(e) {
- let type = e.target.getAttribute("doorhangertype");
- webrtcUI.showSharingDoorhanger(e.target.stream, type);
-}
-
-function showOrCreateMenuForWindow(aWindow) {
- let document = aWindow.document;
- let menu = document.getElementById("tabSharingMenu");
- if (!menu) {
- let stringBundle = aWindow.gNavigatorBundle;
- menu = document.createElement("menu");
- menu.id = "tabSharingMenu";
- let labelStringId = "getUserMedia.sharingMenu.label";
- menu.setAttribute("label", stringBundle.getString(labelStringId));
-
- let container, insertionPoint;
- if (AppConstants.platform == "macosx") {
- container = document.getElementById("windowPopup");
- insertionPoint = document.getElementById("sep-window-list");
- let separator = document.createElement("menuseparator");
- separator.id = "tabSharingSeparator";
- container.insertBefore(separator, insertionPoint);
- } else {
- let accesskeyStringId = "getUserMedia.sharingMenu.accesskey";
- menu.setAttribute("accesskey", stringBundle.getString(accesskeyStringId));
- container = document.getElementById("main-menubar");
- insertionPoint = document.getElementById("helpMenu");
- }
- let popup = document.createElement("menupopup");
- popup.id = "tabSharingMenuPopup";
- popup.addEventListener("popupshowing", onTabSharingMenuPopupShowing);
- popup.addEventListener("popuphiding", onTabSharingMenuPopupHiding);
- menu.appendChild(popup);
- container.insertBefore(menu, insertionPoint);
- } else {
- menu.hidden = false;
- if (AppConstants.platform == "macosx") {
- document.getElementById("tabSharingSeparator").hidden = false;
- }
- }
-}
-
-function maybeAddMenuIndicator(window) {
- if (webrtcUI.showGlobalIndicator) {
- showOrCreateMenuForWindow(window);
- }
-}
-
-var gIndicatorWindow = null;
-
-function updateIndicators(data, target) {
- if (data) {
- // the global indicators specific to this process
- let indicators;
- if (webrtcUI.processIndicators.has(target)) {
- indicators = webrtcUI.processIndicators.get(target);
- } else {
- indicators = {};
- webrtcUI.processIndicators.set(target, indicators);
- }
-
- indicators.showGlobalIndicator = data.showGlobalIndicator;
- indicators.showCameraIndicator = data.showCameraIndicator;
- indicators.showMicrophoneIndicator = data.showMicrophoneIndicator;
- indicators.showScreenSharingIndicator = data.showScreenSharingIndicator;
- }
-
- let browserWindowEnum = Services.wm.getEnumerator("navigator:browser");
- while (browserWindowEnum.hasMoreElements()) {
- let chromeWin = browserWindowEnum.getNext();
- if (webrtcUI.showGlobalIndicator) {
- showOrCreateMenuForWindow(chromeWin);
- } else {
- let doc = chromeWin.document;
- let existingMenu = doc.getElementById("tabSharingMenu");
- if (existingMenu) {
- existingMenu.hidden = true;
- }
- if (AppConstants.platform == "macosx") {
- let separator = doc.getElementById("tabSharingSeparator");
- if (separator) {
- separator.hidden = true;
- }
- }
- }
- }
-
- if (webrtcUI.showGlobalIndicator) {
- if (!gIndicatorWindow)
- gIndicatorWindow = getGlobalIndicator();
- else
- gIndicatorWindow.updateIndicatorState();
- } else if (gIndicatorWindow) {
- gIndicatorWindow.close();
- gIndicatorWindow = null;
- }
-}