summaryrefslogtreecommitdiffstats
path: root/browser/modules/BrowserUITelemetry.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/BrowserUITelemetry.jsm')
-rw-r--r--browser/modules/BrowserUITelemetry.jsm895
1 files changed, 0 insertions, 895 deletions
diff --git a/browser/modules/BrowserUITelemetry.jsm b/browser/modules/BrowserUITelemetry.jsm
deleted file mode 100644
index 2ad319f1a..000000000
--- a/browser/modules/BrowserUITelemetry.jsm
+++ /dev/null
@@ -1,895 +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.defineLazyModuleGetter(this, "UITour",
- "resource:///modules/UITour.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));
- // Ensure that UITour.jsm remains lazy-loaded, yet always registers its
- // simple measure function with UITelemetry.
- UITelemetry.addSimpleMeasureFunction("UITour",
- () => UITour.getTelemetry());
-
- 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;
-}