forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1261 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1261 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			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/. */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| var EXPORTED_SYMBOLS = [
 | |
|   "PageActions",
 | |
|   // PageActions.Action
 | |
|   // PageActions.ACTION_ID_BOOKMARK
 | |
|   // PageActions.ACTION_ID_BOOKMARK_SEPARATOR
 | |
|   // PageActions.ACTION_ID_BUILT_IN_SEPARATOR
 | |
|   // PageActions.ACTION_ID_TRANSIENT_SEPARATOR
 | |
| ];
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "AppConstants",
 | |
|   "resource://gre/modules/AppConstants.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "AsyncShutdown",
 | |
|   "resource://gre/modules/AsyncShutdown.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "BinarySearch",
 | |
|   "resource://gre/modules/BinarySearch.jsm");
 | |
| 
 | |
| 
 | |
| const ACTION_ID_BOOKMARK = "bookmark";
 | |
| const ACTION_ID_BOOKMARK_SEPARATOR = "bookmarkSeparator";
 | |
| const ACTION_ID_BUILT_IN_SEPARATOR = "builtInSeparator";
 | |
| const ACTION_ID_TRANSIENT_SEPARATOR = "transientSeparator";
 | |
| 
 | |
| const PREF_PERSISTED_ACTIONS = "browser.pageActions.persistedActions";
 | |
| const PERSISTED_ACTIONS_CURRENT_VERSION = 1;
 | |
| 
 | |
| // Escapes the given raw URL string, and returns an equivalent CSS url()
 | |
| // value for it.
 | |
| function escapeCSSURL(url) {
 | |
|   return `url("${url.replace(/[\\\s"]/g, encodeURIComponent)}")`;
 | |
| }
 | |
| 
 | |
| var PageActions = {
 | |
|   /**
 | |
|    * Inits.  Call to init.
 | |
|    */
 | |
|   init() {
 | |
|     let callbacks = this._deferredAddActionCalls;
 | |
|     delete this._deferredAddActionCalls;
 | |
| 
 | |
|     this._loadPersistedActions();
 | |
| 
 | |
|     // Register the built-in actions, which are defined below in this file.
 | |
|     for (let options of gBuiltInActions) {
 | |
|       if (!this.actionForID(options.id)) {
 | |
|         this._registerAction(new Action(options));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Now place them all in each window.  Instead of splitting the register and
 | |
|     // place steps, we could simply call addAction, which does both, but doing
 | |
|     // it this way means that all windows initially place their actions the same
 | |
|     // way -- placeAllActions -- regardless of whether they're open when this
 | |
|     // method is called or opened later.
 | |
|     for (let bpa of allBrowserPageActions()) {
 | |
|       bpa.placeAllActions();
 | |
|     }
 | |
| 
 | |
|     // These callbacks are deferred until init happens and all built-in actions
 | |
|     // are added.
 | |
|     while (callbacks && callbacks.length) {
 | |
|       callbacks.shift()();
 | |
|     }
 | |
| 
 | |
|     // Purge removed actions from persisted state on shutdown.  The point is not
 | |
|     // to do it on Action.remove().  That way actions that are removed and
 | |
|     // re-added while the app is running will have their urlbar placement and
 | |
|     // other state remembered and restored.  This happens for upgraded and
 | |
|     // downgraded extensions, for example.
 | |
|     AsyncShutdown.profileBeforeChange.addBlocker(
 | |
|       "PageActions: purging unregistered actions from cache",
 | |
|       () => this._purgeUnregisteredPersistedActions(),
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _deferredAddActionCalls: [],
 | |
| 
 | |
|   /**
 | |
|    * A list of all Action objects, not in any particular order.  Not live.
 | |
|    * (array of Action objects)
 | |
|    */
 | |
|   get actions() {
 | |
|     let lists = [
 | |
|       this._builtInActions,
 | |
|       this._nonBuiltInActions,
 | |
|       this._transientActions,
 | |
|     ];
 | |
|     return lists.reduce((memo, list) => memo.concat(list), []);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The list of Action objects that should appear in the panel for a given
 | |
|    * window, sorted in the order in which they appear.  If there are both
 | |
|    * built-in and non-built-in actions, then the list will include the separator
 | |
|    * between the two.  The list is not live.  (array of Action objects)
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         This window's actions will be returned.
 | |
|    * @return (array of PageAction.Action objects) The actions currently in the
 | |
|    *         given window's panel.
 | |
|    */
 | |
|   actionsInPanel(browserWindow) {
 | |
|     function filter(action) {
 | |
|       return action.shouldShowInPanel(browserWindow);
 | |
|     }
 | |
|     let actions = this._builtInActions.filter(filter);
 | |
|     let nonBuiltInActions = this._nonBuiltInActions.filter(filter);
 | |
|     if (nonBuiltInActions.length) {
 | |
|       if (actions.length) {
 | |
|         actions.push(new Action({
 | |
|           id: ACTION_ID_BUILT_IN_SEPARATOR,
 | |
|           _isSeparator: true,
 | |
|         }));
 | |
|       }
 | |
|       actions.push(...nonBuiltInActions);
 | |
|     }
 | |
|     let transientActions = this._transientActions.filter(filter);
 | |
|     if (transientActions.length) {
 | |
|       if (actions.length) {
 | |
|         actions.push(new Action({
 | |
|           id: ACTION_ID_TRANSIENT_SEPARATOR,
 | |
|           _isSeparator: true,
 | |
|         }));
 | |
|       }
 | |
|       actions.push(...transientActions);
 | |
|     }
 | |
|     return actions;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The list of actions currently in the urlbar, sorted in the order in which
 | |
|    * they appear.  Not live.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         This window's actions will be returned.
 | |
|    * @return (array of PageAction.Action objects) The actions currently in the
 | |
|    *         given window's urlbar.
 | |
|    */
 | |
|   actionsInUrlbar(browserWindow) {
 | |
|     // Remember that IDs in idsInUrlbar may belong to actions that aren't
 | |
|     // currently registered.
 | |
|     return this._persistedActions.idsInUrlbar.reduce((actions, id) => {
 | |
|       let action = this.actionForID(id);
 | |
|       if (action && action.shouldShowInUrlbar(browserWindow)) {
 | |
|         actions.push(action);
 | |
|       }
 | |
|       return actions;
 | |
|     }, []);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Gets an action.
 | |
|    *
 | |
|    * @param  id (string, required)
 | |
|    *         The ID of the action to get.
 | |
|    * @return The Action object, or null if none.
 | |
|    */
 | |
|   actionForID(id) {
 | |
|     return this._actionsByID.get(id);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Registers an action.
 | |
|    *
 | |
|    * Actions are registered by their IDs.  An error is thrown if an action with
 | |
|    * the given ID has already been added.  Use actionForID() before calling this
 | |
|    * method if necessary.
 | |
|    *
 | |
|    * Be sure to call remove() on the action if the lifetime of the code that
 | |
|    * owns it is shorter than the browser's -- if it lives in an extension, for
 | |
|    * example.
 | |
|    *
 | |
|    * @param  action (Action, required)
 | |
|    *         The Action object to register.
 | |
|    * @return The given Action.
 | |
|    */
 | |
|   addAction(action) {
 | |
|     if (this._deferredAddActionCalls) {
 | |
|       // init() hasn't been called yet.  Defer all additions until it's called,
 | |
|       // at which time _deferredAddActionCalls will be deleted.
 | |
|       this._deferredAddActionCalls.push(() => this.addAction(action));
 | |
|       return action;
 | |
|     }
 | |
|     this._registerAction(action);
 | |
|     for (let bpa of allBrowserPageActions()) {
 | |
|       bpa.placeAction(action);
 | |
|     }
 | |
|     return action;
 | |
|   },
 | |
| 
 | |
|   _registerAction(action) {
 | |
|     if (this.actionForID(action.id)) {
 | |
|       throw new Error(`Action with ID '${action.id}' already added`);
 | |
|     }
 | |
|     this._actionsByID.set(action.id, action);
 | |
| 
 | |
|     // Insert the action into the appropriate list, either _builtInActions or
 | |
|     // _nonBuiltInActions.
 | |
| 
 | |
|     // Keep in mind that _insertBeforeActionID may be present but null, which
 | |
|     // means the action should be appended to the built-ins.
 | |
|     if ("__insertBeforeActionID" in action) {
 | |
|       // A "semi-built-in" action, probably an action from an extension
 | |
|       // bundled with the browser.  Right now we simply assume that no other
 | |
|       // consumers will use _insertBeforeActionID.
 | |
|       let index =
 | |
|         !action.__insertBeforeActionID ? -1 :
 | |
|         this._builtInActions.findIndex(a => {
 | |
|           return a.id == action.__insertBeforeActionID;
 | |
|         });
 | |
|       if (index < 0) {
 | |
|         // Append the action (excluding transient actions).
 | |
|         index = this._builtInActions.filter(a => !a.__transient).length;
 | |
|       }
 | |
|       this._builtInActions.splice(index, 0, action);
 | |
|     } else if (action.__transient) {
 | |
|       // A transient action.
 | |
|       this._transientActions.push(action);
 | |
|     } else if (gBuiltInActions.find(a => a.id == action.id)) {
 | |
|       // A built-in action.  These are always added on init before all other
 | |
|       // actions, one after the other, so just push onto the array.
 | |
|       this._builtInActions.push(action);
 | |
|     } else {
 | |
|       // A non-built-in action, like a non-bundled extension potentially.
 | |
|       // Keep this list sorted by title.
 | |
|       let index = BinarySearch.insertionIndexOf((a1, a2) => {
 | |
|         return a1.getTitle().localeCompare(a2.getTitle());
 | |
|       }, this._nonBuiltInActions, action);
 | |
|       this._nonBuiltInActions.splice(index, 0, action);
 | |
|     }
 | |
| 
 | |
|     if (this._persistedActions.ids.includes(action.id)) {
 | |
|       // The action has been seen before.  Override its pinnedToUrlbar value
 | |
|       // with the persisted value.  Set the private version of that property
 | |
|       // so that onActionToggledPinnedToUrlbar isn't called, which happens when
 | |
|       // the public version is set.
 | |
|       action._pinnedToUrlbar =
 | |
|         this._persistedActions.idsInUrlbar.includes(action.id);
 | |
|     } else {
 | |
|       // The action is new.  Store it in the persisted actions.
 | |
|       this._persistedActions.ids.push(action.id);
 | |
|       this._updateIDsPinnedToUrlbarForAction(action);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _updateIDsPinnedToUrlbarForAction(action) {
 | |
|     let index = this._persistedActions.idsInUrlbar.indexOf(action.id);
 | |
|     if (action.pinnedToUrlbar) {
 | |
|       if (index < 0) {
 | |
|         index = action.id == ACTION_ID_BOOKMARK ? -1 :
 | |
|                 this._persistedActions.idsInUrlbar.indexOf(ACTION_ID_BOOKMARK);
 | |
|         if (index < 0) {
 | |
|           index = this._persistedActions.idsInUrlbar.length;
 | |
|         }
 | |
|         this._persistedActions.idsInUrlbar.splice(index, 0, action.id);
 | |
|       }
 | |
|     } else if (index >= 0) {
 | |
|       this._persistedActions.idsInUrlbar.splice(index, 1);
 | |
|     }
 | |
|     this._storePersistedActions();
 | |
|   },
 | |
| 
 | |
|   // These keep track of currently registered actions.
 | |
|   _builtInActions: [],
 | |
|   _nonBuiltInActions: [],
 | |
|   _transientActions: [],
 | |
|   _actionsByID: new Map(),
 | |
| 
 | |
|   /**
 | |
|    * Call this when an action is removed.
 | |
|    *
 | |
|    * @param  action (Action object, required)
 | |
|    *         The action that was removed.
 | |
|    */
 | |
|   onActionRemoved(action) {
 | |
|     if (!this.actionForID(action.id)) {
 | |
|       // The action isn't registered (yet).  Not an error.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this._actionsByID.delete(action.id);
 | |
|     let lists = [
 | |
|       this._builtInActions,
 | |
|       this._nonBuiltInActions,
 | |
|       this._transientActions,
 | |
|     ];
 | |
|     for (let list of lists) {
 | |
|       let index = list.findIndex(a => a.id == action.id);
 | |
|       if (index >= 0) {
 | |
|         list.splice(index, 1);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (let bpa of allBrowserPageActions()) {
 | |
|       bpa.removeAction(action);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when an action's pinnedToUrlbar property changes.
 | |
|    *
 | |
|    * @param  action (Action object, required)
 | |
|    *         The action whose pinnedToUrlbar property changed.
 | |
|    */
 | |
|   onActionToggledPinnedToUrlbar(action) {
 | |
|     if (!this.actionForID(action.id)) {
 | |
|       // This may be called before the action has been added.
 | |
|       return;
 | |
|     }
 | |
|     this._updateIDsPinnedToUrlbarForAction(action);
 | |
|     for (let bpa of allBrowserPageActions()) {
 | |
|       bpa.placeActionInUrlbar(action);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   logTelemetry(type, action, node = null) {
 | |
|     if (type == "used") {
 | |
|       type =
 | |
|         node && node.closest("#urlbar-container") ? "urlbar_used" :
 | |
|         "panel_used";
 | |
|     }
 | |
|     let histogramID = "FX_PAGE_ACTION_" + type.toUpperCase();
 | |
|     try {
 | |
|       let histogram = Services.telemetry.getHistogramById(histogramID);
 | |
|       if (action._isBuiltIn) {
 | |
|         histogram.add(action.labelForHistogram);
 | |
|       } else {
 | |
|         histogram.add("other");
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       Cu.reportError(ex);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   // For tests.  See Bug 1413692.
 | |
|   _reset() {
 | |
|     PageActions._purgeUnregisteredPersistedActions();
 | |
|     PageActions._builtInActions = [];
 | |
|     PageActions._nonBuiltInActions = [];
 | |
|     PageActions._transientActions = [];
 | |
|     PageActions._actionsByID = new Map();
 | |
|   },
 | |
| 
 | |
|   _storePersistedActions() {
 | |
|     let json = JSON.stringify(this._persistedActions);
 | |
|     Services.prefs.setStringPref(PREF_PERSISTED_ACTIONS, json);
 | |
|   },
 | |
| 
 | |
|   _loadPersistedActions() {
 | |
|     try {
 | |
|       let json = Services.prefs.getStringPref(PREF_PERSISTED_ACTIONS);
 | |
|       this._persistedActions = this._migratePersistedActions(JSON.parse(json));
 | |
|     } catch (ex) {}
 | |
|   },
 | |
| 
 | |
|   _purgeUnregisteredPersistedActions() {
 | |
|     // Remove all action IDs from persisted state that do not correspond to
 | |
|     // currently registered actions.
 | |
|     for (let name of ["ids", "idsInUrlbar"]) {
 | |
|       this._persistedActions[name] = this._persistedActions[name].filter(id => {
 | |
|         return this.actionForID(id);
 | |
|       });
 | |
|     }
 | |
|     this._storePersistedActions();
 | |
|   },
 | |
| 
 | |
|   _migratePersistedActions(actions) {
 | |
|     // Start with actions.version and migrate one version at a time, all the way
 | |
|     // up to the current version.
 | |
|     for (let version = actions.version || 0;
 | |
|          version < PERSISTED_ACTIONS_CURRENT_VERSION;
 | |
|          version++) {
 | |
|       let methodName = `_migratePersistedActionsTo${version + 1}`;
 | |
|       actions = this[methodName](actions);
 | |
|       actions.version = version + 1;
 | |
|     }
 | |
|     return actions;
 | |
|   },
 | |
| 
 | |
|   _migratePersistedActionsTo1(actions) {
 | |
|     // The `ids` object is a mapping: action ID => true.  Convert it to an array
 | |
|     // to save space in the prefs.
 | |
|     let ids = [];
 | |
|     for (let id in actions.ids) {
 | |
|       ids.push(id);
 | |
|     }
 | |
|     // Move the bookmark ID to the end of idsInUrlbar.  The bookmark action
 | |
|     // should always remain at the end of the urlbar, if present.
 | |
|     let bookmarkIndex = actions.idsInUrlbar.indexOf(ACTION_ID_BOOKMARK);
 | |
|     if (bookmarkIndex >= 0) {
 | |
|       actions.idsInUrlbar.splice(bookmarkIndex, 1);
 | |
|       actions.idsInUrlbar.push(ACTION_ID_BOOKMARK);
 | |
|     }
 | |
|     return {
 | |
|       ids,
 | |
|       idsInUrlbar: actions.idsInUrlbar,
 | |
|     };
 | |
|   },
 | |
| 
 | |
|   // This keeps track of all actions, even those that are not currently
 | |
|   // registered because they have been removed, so long as
 | |
|   // _purgeUnregisteredPersistedActions has not been called.
 | |
|   _persistedActions: {
 | |
|     version: PERSISTED_ACTIONS_CURRENT_VERSION,
 | |
|     // action IDs that have ever been seen and not removed, order not important
 | |
|     ids: [],
 | |
|     // action IDs ordered by position in urlbar
 | |
|     idsInUrlbar: [],
 | |
|   },
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * A single page action.
 | |
|  *
 | |
|  * Each action can have both per-browser-window state and global state.
 | |
|  * Per-window state takes precedence over global state.  This is reflected in
 | |
|  * the title, tooltip, disabled, and icon properties.  Each of these properties
 | |
|  * has a getter method and setter method that takes a browser window.  Pass null
 | |
|  * to get the action's global state.  Pass a browser window to get the per-
 | |
|  * window state.  However, if you pass a window and the action has no state for
 | |
|  * that window, then the global state will be returned.
 | |
|  *
 | |
|  * `options` is a required object with the following properties.  Regarding the
 | |
|  * properties discussed in the previous paragraph, the values in `options` set
 | |
|  * global state.
 | |
|  *
 | |
|  * @param id (string, required)
 | |
|  *        The action's ID.  Treat this like the ID of a DOM node.
 | |
|  * @param title (string, required)
 | |
|  *        The action's title.
 | |
|  * @param anchorIDOverride (string, optional)
 | |
|  *        Pass a string to override the node to which the action's activated-
 | |
|  *        action panel is anchored.
 | |
|  * @param disabled (bool, optional)
 | |
|  *        Pass true to cause the action to be disabled initially in all browser
 | |
|  *        windows.  False by default.
 | |
|  * @param extensionID (string, optional)
 | |
|  *        If the action lives in an extension, pass its ID.
 | |
|  * @param iconURL (string or object, optional)
 | |
|  *        The URL string of the action's icon.  Usually you want to specify an
 | |
|  *        icon in CSS, but this option is useful if that would be a pain for
 | |
|  *        some reason.  You can also pass an object that maps pixel sizes to
 | |
|  *        URLs, like { 16: url16, 32: url32 }.  The best size for the user's
 | |
|  *        screen will be used.
 | |
|  * @param onBeforePlacedInWindow (function, optional)
 | |
|  *        Called before the action is placed in the window:
 | |
|  *        onBeforePlacedInWindow(window)
 | |
|  *        * window: The window that the action will be placed in.
 | |
|  * @param onCommand (function, optional)
 | |
|  *        Called when the action is clicked, but only if it has neither a
 | |
|  *        subview nor an iframe:
 | |
|  *        onCommand(event, buttonNode)
 | |
|  *        * event: The triggering event.
 | |
|  *        * buttonNode: The button node that was clicked.
 | |
|  * @param onIframeHiding (function, optional)
 | |
|  *        Called when the action's iframe is hiding:
 | |
|  *        onIframeHiding(iframeNode, parentPanelNode)
 | |
|  *        * iframeNode: The iframe.
 | |
|  *        * parentPanelNode: The panel node in which the iframe is shown.
 | |
|  * @param onIframeHidden (function, optional)
 | |
|  *        Called when the action's iframe is hidden:
 | |
|  *        onIframeHidden(iframeNode, parentPanelNode)
 | |
|  *        * iframeNode: The iframe.
 | |
|  *        * parentPanelNode: The panel node in which the iframe is shown.
 | |
|  * @param onIframeShowing (function, optional)
 | |
|  *        Called when the action's iframe is showing to the user:
 | |
|  *        onIframeShowing(iframeNode, parentPanelNode)
 | |
|  *        * iframeNode: The iframe.
 | |
|  *        * parentPanelNode: The panel node in which the iframe is shown.
 | |
|  * @param onLocationChange (function, optional)
 | |
|  *        Called after tab switch or when the current <browser>'s location
 | |
|  *        changes:
 | |
|  *        onLocationChange(browserWindow)
 | |
|  *        * browserWindow: The browser window containing the tab switch or
 | |
|  *          changed <browser>.
 | |
|  * @param onPlacedInPanel (function, optional)
 | |
|  *        Called when the action is added to the page action panel in a browser
 | |
|  *        window:
 | |
|  *        onPlacedInPanel(buttonNode)
 | |
|  *        * buttonNode: The action's node in the page action panel.
 | |
|  * @param onPlacedInUrlbar (function, optional)
 | |
|  *        Called when the action is added to the urlbar in a browser window:
 | |
|  *        onPlacedInUrlbar(buttonNode)
 | |
|  *        * buttonNode: The action's node in the urlbar.
 | |
|  * @param onRemovedFromWindow (function, optional)
 | |
|  *        Called after the action is removed from a browser window:
 | |
|  *        onRemovedFromWindow(browserWindow)
 | |
|  *        * browserWindow: The browser window that the action was removed from.
 | |
|  * @param onShowingInPanel (function, optional)
 | |
|  *        Called when a browser window's page action panel is showing:
 | |
|  *        onShowingInPanel(buttonNode)
 | |
|  *        * buttonNode: The action's node in the page action panel.
 | |
|  * @param onSubviewPlaced (function, optional)
 | |
|  *        Called when the action's subview is added to its parent panel in a
 | |
|  *        browser window:
 | |
|  *        onSubviewPlaced(panelViewNode)
 | |
|  *        * panelViewNode: The subview's panelview node.
 | |
|  * @param onSubviewShowing (function, optional)
 | |
|  *        Called when the action's subview is showing in a browser window:
 | |
|  *        onSubviewShowing(panelViewNode)
 | |
|  *        * panelViewNode: The subview's panelview node.
 | |
|  * @param pinnedToUrlbar (bool, optional)
 | |
|  *        Pass true to pin the action to the urlbar.  An action is shown in the
 | |
|  *        urlbar if it's pinned and not disabled.  False by default.
 | |
|  * @param tooltip (string, optional)
 | |
|  *        The action's button tooltip text.
 | |
|  * @param urlbarIDOverride (string, optional)
 | |
|  *        Usually the ID of the action's button in the urlbar will be generated
 | |
|  *        automatically.  Pass a string for this property to override that with
 | |
|  *        your own ID.
 | |
|  * @param wantsIframe (bool, optional)
 | |
|  *        Pass true to make an action that shows an iframe in a panel when
 | |
|  *        clicked.
 | |
|  * @param wantsSubview (bool, optional)
 | |
|  *        Pass true to make an action that shows a panel subview when clicked.
 | |
|  */
 | |
| function Action(options) {
 | |
|   setProperties(this, options, {
 | |
|     id: true,
 | |
|     title: !options._isSeparator,
 | |
|     anchorIDOverride: false,
 | |
|     disabled: false,
 | |
|     extensionID: false,
 | |
|     iconURL: false,
 | |
|     labelForHistogram: false,
 | |
|     onBeforePlacedInWindow: false,
 | |
|     onCommand: false,
 | |
|     onIframeHiding: false,
 | |
|     onIframeHidden: false,
 | |
|     onIframeShowing: false,
 | |
|     onLocationChange: false,
 | |
|     onPlacedInPanel: false,
 | |
|     onPlacedInUrlbar: false,
 | |
|     onRemovedFromWindow: false,
 | |
|     onShowingInPanel: false,
 | |
|     onSubviewPlaced: false,
 | |
|     onSubviewShowing: false,
 | |
|     pinnedToUrlbar: false,
 | |
|     tooltip: false,
 | |
|     urlbarIDOverride: false,
 | |
|     wantsIframe: false,
 | |
|     wantsSubview: false,
 | |
| 
 | |
|     // private
 | |
| 
 | |
|     // (string, optional)
 | |
|     // The ID of another action before which to insert this new action in the
 | |
|     // panel.
 | |
|     _insertBeforeActionID: false,
 | |
| 
 | |
|     // (bool, optional)
 | |
|     // True if this isn't really an action but a separator to be shown in the
 | |
|     // page action panel.
 | |
|     _isSeparator: false,
 | |
| 
 | |
|     // (bool, optional)
 | |
|     // Transient actions have a couple of special properties: (1) They stick to
 | |
|     // the bottom of the panel, and (2) they're hidden in the panel when they're
 | |
|     // disabled.  Other than that they behave like other actions.
 | |
|     _transient: false,
 | |
| 
 | |
|     // (bool, optional)
 | |
|     // True if the action's urlbar button is defined in markup.  In that case, a
 | |
|     // node with the action's urlbar node ID should already exist in the DOM
 | |
|     // (either the auto-generated ID or urlbarIDOverride).  That node will be
 | |
|     // shown when the action is added to the urlbar and hidden when the action
 | |
|     // is removed from the urlbar.
 | |
|     _urlbarNodeInMarkup: false,
 | |
|   });
 | |
| 
 | |
|   /**
 | |
|    * A cache of the pre-computed CSS variable values for a given icon
 | |
|    * URLs object, as passed to _createIconProperties.
 | |
|    */
 | |
|   this._iconProperties = new WeakMap();
 | |
| 
 | |
|   /**
 | |
|    * The global values for the action properties.
 | |
|    */
 | |
|   this._globalProps = {
 | |
|     disabled: this._disabled,
 | |
|     iconURL: this._iconURL,
 | |
|     iconProps: this._createIconProperties(this._iconURL),
 | |
|     title: this._title,
 | |
|     tooltip: this._tooltip,
 | |
|     wantsSubview: this._wantsSubview,
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|    * A mapping of window-specific action property objects, each of which
 | |
|    * derives from the _globalProps object.
 | |
|    */
 | |
|   this._windowProps = new WeakMap();
 | |
| }
 | |
| 
 | |
| Action.prototype = {
 | |
|   /**
 | |
|    * The ID of the action's parent extension (string)
 | |
|    */
 | |
|   get extensionID() {
 | |
|     return this._extensionID;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The action's ID (string)
 | |
|    */
 | |
|   get id() {
 | |
|     return this._id;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * True if the action is pinned to the urlbar.  The action is shown in the
 | |
|    * urlbar if it's pinned and not disabled.  (bool)
 | |
|    */
 | |
|   get pinnedToUrlbar() {
 | |
|     return this._pinnedToUrlbar || false;
 | |
|   },
 | |
|   set pinnedToUrlbar(shown) {
 | |
|     if (this.pinnedToUrlbar != shown) {
 | |
|       this._pinnedToUrlbar = shown;
 | |
|       PageActions.onActionToggledPinnedToUrlbar(this);
 | |
|     }
 | |
|     return this.pinnedToUrlbar;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The action's disabled state (bool)
 | |
|    */
 | |
|   getDisabled(browserWindow = null) {
 | |
|     return !!this._getProperties(browserWindow).disabled;
 | |
|   },
 | |
|   setDisabled(value, browserWindow = null) {
 | |
|     return this._setProperty("disabled", !!value, browserWindow);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The action's icon URL string, or an object mapping sizes to URL strings
 | |
|    * (string or object)
 | |
|    */
 | |
|   getIconURL(browserWindow = null) {
 | |
|     return this._getProperties(browserWindow).iconURL;
 | |
|   },
 | |
|   setIconURL(value, browserWindow = null) {
 | |
|     let props = this._getProperties(browserWindow, !!browserWindow);
 | |
|     props.iconURL = value;
 | |
|     props.iconProps = this._createIconProperties(value);
 | |
| 
 | |
|     this._updateProperty("iconURL", props.iconProps, browserWindow);
 | |
|     return value;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The set of CSS variables which define the action's icons in various
 | |
|    * sizes. This is generated automatically from the iconURL property.
 | |
|    */
 | |
|   getIconProperties(browserWindow = null) {
 | |
|     return this._getProperties(browserWindow).iconProps;
 | |
|   },
 | |
| 
 | |
|   _createIconProperties(urls) {
 | |
|     if (urls && typeof urls == "object") {
 | |
|       let props = this._iconProperties.get(urls);
 | |
|       if (!props) {
 | |
|         props = Object.freeze({
 | |
|           "--pageAction-image-16px": escapeCSSURL(this._iconURLForSize(urls, 16)),
 | |
|           "--pageAction-image-32px": escapeCSSURL(this._iconURLForSize(urls, 32)),
 | |
|         });
 | |
|         this._iconProperties.set(urls, props);
 | |
|       }
 | |
|       return props;
 | |
|     }
 | |
| 
 | |
|     let cssURL = urls ? escapeCSSURL(urls) : null;
 | |
|     return Object.freeze({
 | |
|       "--pageAction-image-16px": cssURL,
 | |
|       "--pageAction-image-32px": cssURL,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The action's title (string)
 | |
|    */
 | |
|   getTitle(browserWindow = null) {
 | |
|     return this._getProperties(browserWindow).title;
 | |
|   },
 | |
|   setTitle(value, browserWindow = null) {
 | |
|     return this._setProperty("title", value, browserWindow);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * The action's tooltip (string)
 | |
|    */
 | |
|   getTooltip(browserWindow = null) {
 | |
|     return this._getProperties(browserWindow).tooltip;
 | |
|   },
 | |
|   setTooltip(value, browserWindow = null) {
 | |
|     return this._setProperty("tooltip", value, browserWindow);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Whether the action wants a subview (bool)
 | |
|    */
 | |
|   getWantsSubview(browserWindow = null) {
 | |
|     return !!this._getProperties(browserWindow).wantsSubview;
 | |
|   },
 | |
|   setWantsSubview(value, browserWindow = null) {
 | |
|     return this._setProperty("wantsSubview", !!value, browserWindow);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Sets a property, optionally for a particular browser window.
 | |
|    *
 | |
|    * @param  name (string, required)
 | |
|    *         The (non-underscored) name of the property.
 | |
|    * @param  value
 | |
|    *         The value.
 | |
|    * @param  browserWindow (DOM window, optional)
 | |
|    *         If given, then the property will be set in this window's state, not
 | |
|    *         globally.
 | |
|    */
 | |
|   _setProperty(name, value, browserWindow) {
 | |
|     let props = this._getProperties(browserWindow, !!browserWindow);
 | |
|     props[name] = value;
 | |
| 
 | |
|     this._updateProperty(name, value, browserWindow);
 | |
|     return value;
 | |
|   },
 | |
| 
 | |
|   _updateProperty(name, value, browserWindow) {
 | |
|     // This may be called before the action has been added.
 | |
|     if (PageActions.actionForID(this.id)) {
 | |
|       for (let bpa of allBrowserPageActions(browserWindow)) {
 | |
|         bpa.updateAction(this, name, { value });
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns the properties object for the given window, if it exists,
 | |
|    * or the global properties object if no window-specific properties
 | |
|    * exist.
 | |
|    *
 | |
|    * @param {Window?} window
 | |
|    *        The window for which to return the properties object, or
 | |
|    *        null to return the global properties object.
 | |
|    * @param {bool} [forceWindowSpecific = false]
 | |
|    *        If true, always returns a window-specific properties object.
 | |
|    *        If a properties object does not exist for the given window,
 | |
|    *        one is created and cached.
 | |
|    * @returns {object}
 | |
|    */
 | |
|   _getProperties(window, forceWindowSpecific = false) {
 | |
|     let props = window && this._windowProps.get(window);
 | |
| 
 | |
|     if (!props && forceWindowSpecific) {
 | |
|       props = Object.create(this._globalProps);
 | |
|       this._windowProps.set(window, props);
 | |
|     }
 | |
| 
 | |
|     return props || this._globalProps;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Override for the ID of the action's activated-action panel anchor (string)
 | |
|    */
 | |
|   get anchorIDOverride() {
 | |
|     return this._anchorIDOverride;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Override for the ID of the action's urlbar node (string)
 | |
|    */
 | |
|   get urlbarIDOverride() {
 | |
|     return this._urlbarIDOverride;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * True if the action is shown in an iframe (bool)
 | |
|    */
 | |
|   get wantsIframe() {
 | |
|     return this._wantsIframe || false;
 | |
|   },
 | |
| 
 | |
|   get labelForHistogram() {
 | |
|     return this._labelForHistogram || this._id;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Selects the best matching icon from the given URLs object for the
 | |
|    * given preferred size.
 | |
|    *
 | |
|    * @param {object} urls
 | |
|    *        An object containing square icons of various sizes. The name
 | |
|    *        of each property is its width, and the value is its image URL.
 | |
|    * @param {integer} peferredSize
 | |
|    *        The preferred icon width. The most appropriate icon in the
 | |
|    *        urls object will be chosen to match that size. An exact
 | |
|    *        match will be preferred, followed by an icon exactly double
 | |
|    *        the size, followed by the smallest icon larger than the
 | |
|    *        preferred size, followed by the largest available icon.
 | |
|    * @returns {string}
 | |
|    *        The chosen icon URL.
 | |
|    */
 | |
|   _iconURLForSize(urls, preferredSize) {
 | |
|     // This case is copied from ExtensionParent.jsm so that our image logic is
 | |
|     // the same, so that WebExtensions page action tests that deal with icons
 | |
|     // pass.
 | |
|     let bestSize = null;
 | |
|     if (urls[preferredSize]) {
 | |
|       bestSize = preferredSize;
 | |
|     } else if (urls[2 * preferredSize]) {
 | |
|       bestSize = 2 * preferredSize;
 | |
|     } else {
 | |
|       let sizes = Object.keys(urls)
 | |
|                         .map(key => parseInt(key, 10))
 | |
|                         .sort((a, b) => a - b);
 | |
|       bestSize = sizes.find(candidate => candidate > preferredSize) || sizes.pop();
 | |
|     }
 | |
|     return urls[bestSize];
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Performs the command for an action.  If the action has an onCommand
 | |
|    * handler, then it's called.  If the action has a subview or iframe, then a
 | |
|    * panel is opened, displaying the subview or iframe.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The browser window in which to perform the action.
 | |
|    */
 | |
|   doCommand(browserWindow) {
 | |
|     browserPageActions(browserWindow).doCommandForAction(this);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when before placing the action in the window.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The browser window the action will be placed in.
 | |
|    */
 | |
|   onBeforePlacedInWindow(browserWindow) {
 | |
|     if (this._onBeforePlacedInWindow) {
 | |
|       this._onBeforePlacedInWindow(browserWindow);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the user activates the action.
 | |
|    *
 | |
|    * @param  event (DOM event, required)
 | |
|    *         The triggering event.
 | |
|    * @param  buttonNode (DOM node, required)
 | |
|    *         The action's panel or urlbar button node that was clicked.
 | |
|    */
 | |
|   onCommand(event, buttonNode) {
 | |
|     if (this._onCommand) {
 | |
|       this._onCommand(event, buttonNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the action's iframe is hiding.
 | |
|    *
 | |
|    * @param  iframeNode (DOM node, required)
 | |
|    *         The iframe that's hiding.
 | |
|    * @param  parentPanelNode (DOM node, required)
 | |
|    *         The panel in which the iframe is hiding.
 | |
|    */
 | |
|   onIframeHiding(iframeNode, parentPanelNode) {
 | |
|     if (this._onIframeHiding) {
 | |
|       this._onIframeHiding(iframeNode, parentPanelNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the action's iframe is hidden.
 | |
|    *
 | |
|    * @param  iframeNode (DOM node, required)
 | |
|    *         The iframe that's being hidden.
 | |
|    * @param  parentPanelNode (DOM node, required)
 | |
|    *         The panel in which the iframe is hidden.
 | |
|    */
 | |
|   onIframeHidden(iframeNode, parentPanelNode) {
 | |
|     if (this._onIframeHidden) {
 | |
|       this._onIframeHidden(iframeNode, parentPanelNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the action's iframe is showing.
 | |
|    *
 | |
|    * @param  iframeNode (DOM node, required)
 | |
|    *         The iframe that's being shown.
 | |
|    * @param  parentPanelNode (DOM node, required)
 | |
|    *         The panel in which the iframe is shown.
 | |
|    */
 | |
|   onIframeShowing(iframeNode, parentPanelNode) {
 | |
|     if (this._onIframeShowing) {
 | |
|       this._onIframeShowing(iframeNode, parentPanelNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this on tab switch or when the current <browser>'s location changes.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The browser window containing the tab switch or changed <browser>.
 | |
|    */
 | |
|   onLocationChange(browserWindow) {
 | |
|     if (this._onLocationChange) {
 | |
|       this._onLocationChange(browserWindow);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when a DOM node for the action is added to the page action panel.
 | |
|    *
 | |
|    * @param  buttonNode (DOM node, required)
 | |
|    *         The action's panel button node.
 | |
|    */
 | |
|   onPlacedInPanel(buttonNode) {
 | |
|     if (this._onPlacedInPanel) {
 | |
|       this._onPlacedInPanel(buttonNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when a DOM node for the action is added to the urlbar.
 | |
|    *
 | |
|    * @param  buttonNode (DOM node, required)
 | |
|    *         The action's urlbar button node.
 | |
|    */
 | |
|   onPlacedInUrlbar(buttonNode) {
 | |
|     if (this._onPlacedInUrlbar) {
 | |
|       this._onPlacedInUrlbar(buttonNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the DOM nodes for the action are removed from a browser
 | |
|    * window.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The browser window the action was removed from.
 | |
|    */
 | |
|   onRemovedFromWindow(browserWindow) {
 | |
|     if (this._onRemovedFromWindow) {
 | |
|       this._onRemovedFromWindow(browserWindow);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when the action's button is shown in the page action panel.
 | |
|    *
 | |
|    * @param  buttonNode (DOM node, required)
 | |
|    *         The action's panel button node.
 | |
|    */
 | |
|   onShowingInPanel(buttonNode) {
 | |
|     if (this._onShowingInPanel) {
 | |
|       this._onShowingInPanel(buttonNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when a panelview node for the action's subview is added to the
 | |
|    * DOM.
 | |
|    *
 | |
|    * @param  panelViewNode (DOM node, required)
 | |
|    *         The subview's panelview node.
 | |
|    */
 | |
|   onSubviewPlaced(panelViewNode) {
 | |
|     if (this._onSubviewPlaced) {
 | |
|       this._onSubviewPlaced(panelViewNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Call this when a panelview node for the action's subview is showing.
 | |
|    *
 | |
|    * @param  panelViewNode (DOM node, required)
 | |
|    *         The subview's panelview node.
 | |
|    */
 | |
|   onSubviewShowing(panelViewNode) {
 | |
|     if (this._onSubviewShowing) {
 | |
|       this._onSubviewShowing(panelViewNode);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes the action's DOM nodes from all browser windows.
 | |
|    *
 | |
|    * PageActions will remember the action's urlbar placement, if any, after this
 | |
|    * method is called until app shutdown.  If the action is not added again
 | |
|    * before shutdown, then PageActions will discard the placement, and the next
 | |
|    * time the action is added, its placement will be reset.
 | |
|    */
 | |
|   remove() {
 | |
|     PageActions.onActionRemoved(this);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns whether the action should be shown in a given window's panel.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The window.
 | |
|    * @return True if the action should be shown and false otherwise.  Actions
 | |
|    *         are always shown in the panel unless they're both transient and
 | |
|    *         disabled.
 | |
|    */
 | |
|   shouldShowInPanel(browserWindow) {
 | |
|     return !this.__transient || !this.getDisabled(browserWindow);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Returns whether the action should be shown in a given window's urlbar.
 | |
|    *
 | |
|    * @param  browserWindow (DOM window, required)
 | |
|    *         The window.
 | |
|    * @return True if the action should be shown and false otherwise.  The action
 | |
|    *         should be shown if it's both pinned and not disabled.
 | |
|    */
 | |
|   shouldShowInUrlbar(browserWindow) {
 | |
|     return this.pinnedToUrlbar && !this.getDisabled(browserWindow);
 | |
|   },
 | |
| 
 | |
|   get _isBuiltIn() {
 | |
|     let builtInIDs = [
 | |
|       "pocket",
 | |
|       "screenshots",
 | |
|       "webcompat-reporter-button",
 | |
|     ].concat(gBuiltInActions.filter(a => !a.__isSeparator).map(a => a.id));
 | |
|     return builtInIDs.includes(this.id);
 | |
|   },
 | |
| };
 | |
| 
 | |
| this.PageActions.Action = Action;
 | |
| 
 | |
| this.PageActions.ACTION_ID_BUILT_IN_SEPARATOR = ACTION_ID_BUILT_IN_SEPARATOR;
 | |
| this.PageActions.ACTION_ID_TRANSIENT_SEPARATOR = ACTION_ID_TRANSIENT_SEPARATOR;
 | |
| 
 | |
| // These are only necessary so that Pocket and the test can use them.
 | |
| this.PageActions.ACTION_ID_BOOKMARK = ACTION_ID_BOOKMARK;
 | |
| this.PageActions.ACTION_ID_BOOKMARK_SEPARATOR = ACTION_ID_BOOKMARK_SEPARATOR;
 | |
| this.PageActions.PREF_PERSISTED_ACTIONS = PREF_PERSISTED_ACTIONS;
 | |
| 
 | |
| 
 | |
| // Sorted in the order in which they should appear in the page action panel.
 | |
| // Does not include the page actions of extensions bundled with the browser.
 | |
| // They're added by the relevant extension code.
 | |
| // NOTE: If you add items to this list (or system add-on actions that we
 | |
| // want to keep track of), make sure to also update Histograms.json for the
 | |
| // new actions.
 | |
| var gBuiltInActions = [
 | |
| 
 | |
|   // bookmark
 | |
|   {
 | |
|     id: ACTION_ID_BOOKMARK,
 | |
|     urlbarIDOverride: "star-button-box",
 | |
|     _urlbarNodeInMarkup: true,
 | |
|     // The title is set in browser-pageActions.js by calling
 | |
|     // BookmarkingUI.updateBookmarkPageMenuItem().
 | |
|     title: "",
 | |
|     pinnedToUrlbar: true,
 | |
|     onShowingInPanel(buttonNode) {
 | |
|       browserPageActions(buttonNode).bookmark.onShowingInPanel(buttonNode);
 | |
|     },
 | |
|     onCommand(event, buttonNode) {
 | |
|       browserPageActions(buttonNode).bookmark.onCommand(event, buttonNode);
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   // separator
 | |
|   {
 | |
|     id: ACTION_ID_BOOKMARK_SEPARATOR,
 | |
|     _isSeparator: true,
 | |
|   },
 | |
| 
 | |
|   // copy URL
 | |
|   {
 | |
|     id: "copyURL",
 | |
|     title: "copyURL-title",
 | |
|     onBeforePlacedInWindow(browserWindow) {
 | |
|       browserPageActions(browserWindow).copyURL
 | |
|         .onBeforePlacedInWindow(browserWindow);
 | |
|     },
 | |
|     onCommand(event, buttonNode) {
 | |
|       browserPageActions(buttonNode).copyURL.onCommand(event, buttonNode);
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   // email link
 | |
|   {
 | |
|     id: "emailLink",
 | |
|     title: "emailLink-title",
 | |
|     onBeforePlacedInWindow(browserWindow) {
 | |
|       browserPageActions(browserWindow).emailLink
 | |
|         .onBeforePlacedInWindow(browserWindow);
 | |
|     },
 | |
|     onCommand(event, buttonNode) {
 | |
|       browserPageActions(buttonNode).emailLink.onCommand(event, buttonNode);
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   // add search engine
 | |
|   {
 | |
|     id: "addSearchEngine",
 | |
|     // The title is set in browser-pageActions.js.
 | |
|     title: "",
 | |
|     _transient: true,
 | |
|     onShowingInPanel(buttonNode) {
 | |
|       browserPageActions(buttonNode).addSearchEngine.onShowingInPanel();
 | |
|     },
 | |
|     onCommand(event, buttonNode) {
 | |
|       browserPageActions(buttonNode).addSearchEngine
 | |
|         .onCommand(event, buttonNode);
 | |
|     },
 | |
|     onSubviewShowing(panelViewNode) {
 | |
|       browserPageActions(panelViewNode).addSearchEngine
 | |
|         .onSubviewShowing(panelViewNode);
 | |
|     },
 | |
|   },
 | |
| ];
 | |
| 
 | |
| if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
 | |
|   gBuiltInActions.push(
 | |
|   // send to device
 | |
|   {
 | |
|     id: "sendToDevice",
 | |
|     title: "sendToDevice-title",
 | |
|     onBeforePlacedInWindow(browserWindow) {
 | |
|       browserPageActions(browserWindow).sendToDevice
 | |
|         .onBeforePlacedInWindow(browserWindow);
 | |
|     },
 | |
|     onLocationChange(browserWindow) {
 | |
|       browserPageActions(browserWindow).sendToDevice.onLocationChange();
 | |
|     },
 | |
|     wantsSubview: true,
 | |
|     onSubviewPlaced(panelViewNode) {
 | |
|       browserPageActions(panelViewNode).sendToDevice
 | |
|         .onSubviewPlaced(panelViewNode);
 | |
|     },
 | |
|     onSubviewShowing(panelViewNode) {
 | |
|       browserPageActions(panelViewNode).sendToDevice
 | |
|         .onShowingSubview(panelViewNode);
 | |
|     },
 | |
|   });
 | |
| }
 | |
| 
 | |
| if (AppConstants.platform == "macosx") {
 | |
|   gBuiltInActions.push(
 | |
|   // Share URL
 | |
|   {
 | |
|     id: "shareURL",
 | |
|     title: "shareURL-title",
 | |
|     onShowingInPanel(buttonNode) {
 | |
|       browserPageActions(buttonNode).shareURL.onShowingInPanel(buttonNode);
 | |
|     },
 | |
|     onBeforePlacedInWindow(browserWindow) {
 | |
|       browserPageActions(browserWindow).shareURL
 | |
|         .onBeforePlacedInWindow(browserWindow);
 | |
|     },
 | |
|     wantsSubview: true,
 | |
|     onSubviewShowing(panelViewNode) {
 | |
|         browserPageActions(panelViewNode).shareURL
 | |
|           .onShowingSubview(panelViewNode);
 | |
|     },
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Gets a BrowserPageActions object in a browser window.
 | |
|  *
 | |
|  * @param  obj
 | |
|  *         Either a DOM node or a browser window.
 | |
|  * @return The BrowserPageActions object in the browser window related to the
 | |
|  *         given object.
 | |
|  */
 | |
| function browserPageActions(obj) {
 | |
|   if (obj.BrowserPageActions) {
 | |
|     return obj.BrowserPageActions;
 | |
|   }
 | |
|   return obj.ownerGlobal.BrowserPageActions;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A generator function for all open browser windows.
 | |
|  *
 | |
|  * @param browserWindow (DOM window, optional)
 | |
|  *        If given, then only this window will be yielded.  That may sound
 | |
|  *        pointless, but it can make callers nicer to write since they don't
 | |
|  *        need two separate cases, one where a window is given and another where
 | |
|  *        it isn't.
 | |
|  */
 | |
| function* allBrowserWindows(browserWindow = null) {
 | |
|   if (browserWindow) {
 | |
|     yield browserWindow;
 | |
|     return;
 | |
|   }
 | |
|   yield* Services.wm.getEnumerator("navigator:browser");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A generator function for BrowserPageActions objects in all open windows.
 | |
|  *
 | |
|  * @param browserWindow (DOM window, optional)
 | |
|  *        If given, then the BrowserPageActions for only this window will be
 | |
|  *        yielded.
 | |
|  */
 | |
| function* allBrowserPageActions(browserWindow = null) {
 | |
|   for (let win of allBrowserWindows(browserWindow)) {
 | |
|     yield browserPageActions(win);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A simple function that sets properties on a given object while doing basic
 | |
|  * required-properties checking.  If a required property isn't specified in the
 | |
|  * given options object, or if the options object has properties that aren't in
 | |
|  * the given schema, then an error is thrown.
 | |
|  *
 | |
|  * @param  obj
 | |
|  *         The object to set properties on.
 | |
|  * @param  options
 | |
|  *         An options object supplied by the consumer.
 | |
|  * @param  schema
 | |
|  *         An object a property for each required and optional property.  The
 | |
|  *         keys are property names; the value of a key is a bool that is true if
 | |
|  *         the property is required.
 | |
|  */
 | |
| function setProperties(obj, options, schema) {
 | |
|   for (let name in schema) {
 | |
|     let required = schema[name];
 | |
|     if (required && !(name in options)) {
 | |
|       throw new Error(`'${name}' must be specified`);
 | |
|     }
 | |
|     let nameInObj = "_" + name;
 | |
|     if (name[0] == "_") {
 | |
|       // The property is "private".  If it's defined in the options, then define
 | |
|       // it on obj exactly as it's defined on options.
 | |
|       if (name in options) {
 | |
|         obj[nameInObj] = options[name];
 | |
|       }
 | |
|     } else {
 | |
|       // The property is "public".  Make sure the property is defined on obj.
 | |
|       obj[nameInObj] = options[name] || null;
 | |
|     }
 | |
|   }
 | |
|   for (let name in options) {
 | |
|     if (!(name in schema)) {
 | |
|       throw new Error(`Unrecognized option '${name}'`);
 | |
|     }
 | |
|   }
 | |
| }
 | 
