diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-06-04 13:17:38 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-06-04 13:17:38 +0200 |
commit | a1be17c1cea81ebb1e8b131a662c698d78f3f7f2 (patch) | |
tree | a92f7de513be600cc07bac458183e9af40e00c06 /browser/modules | |
parent | bf11fdd304898ac675e39b01b280d39550e419d0 (diff) | |
download | UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.gz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.lz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.tar.xz UXP-a1be17c1cea81ebb1e8b131a662c698d78f3f7f2.zip |
Issue #303 Part 1: Move basilisk files from /browser to /application/basilisk
Diffstat (limited to 'browser/modules')
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 = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - - 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; - } -} |