Bug 1387077 - Reimplement Pocket animation in the Page Action area. r=adw
authorJared Wein <jwein@mozilla.com>
Tue, 08 Aug 2017 11:56:49 -0400
changeset 374388 c9bc6af2793a6e5fc9a6a6c588ebdd8b1f44f876
parent 374387 1a8d86baaabc1af9d2359a0f078a9ee50aec9351
child 374389 7b68720e4056b9a83763f8f67fd11e1a9cbbcefa
push id93678
push userarchaeopteryx@coole-files.de
push dateSat, 12 Aug 2017 23:17:05 +0000
treeherdermozilla-inbound@a79ccbfacad8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1387077
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1387077 - Reimplement Pocket animation in the Page Action area. r=adw MozReview-Commit-ID: 5pJ96un8W5t
browser/base/content/browser-pageActions.js
browser/base/content/browser.css
browser/base/content/browser.xul
browser/extensions/pocket/bootstrap.js
browser/extensions/pocket/content/main.js
browser/extensions/pocket/skin/shared/pocket-animation.svg
browser/extensions/pocket/skin/shared/pocket-list.svg
browser/extensions/pocket/skin/shared/pocket.css
browser/extensions/pocket/skin/shared/pocket.svg
browser/modules/PageActions.jsm
--- a/browser/base/content/browser-pageActions.js
+++ b/browser/base/content/browser-pageActions.js
@@ -64,16 +64,17 @@ var BrowserPageActions = {
    *         If the action is shown in the urlbar, then this is ID of the action
    *         in the urlbar before which the given action should be inserted.
    */
   placeAction(action, panelInsertBeforeID, urlbarInsertBeforeID) {
     if (action.__isSeparator) {
       this._appendPanelSeparator(action);
       return;
     }
+    action.onBeforePlacedInWindow(window);
     this.placeActionInPanel(action, panelInsertBeforeID);
     this.placeActionInUrlbar(action, urlbarInsertBeforeID);
   },
 
   /**
    * Adds or removes as necessary DOM nodes for the action in the panel.
    *
    * @param  action (PageActions.Action, required)
@@ -171,17 +172,17 @@ var BrowserPageActions = {
     return panelViewNode;
   },
 
   _toggleTempPanelForAction(action) {
     let panelNodeID = this._tempPanelID;
     let panelNode = document.getElementById(panelNodeID);
     if (panelNode) {
       panelNode.hidePopup();
-      return;
+      return null;
     }
 
     panelNode = document.createElement("panel");
     panelNode.id = panelNodeID;
     panelNode.classList.add("cui-widget-panel");
     panelNode.setAttribute("role", "group");
     panelNode.setAttribute("type", "arrow");
     panelNode.setAttribute("flip", "slide");
@@ -200,34 +201,52 @@ var BrowserPageActions = {
       iframeNode = document.createElement("iframe");
       iframeNode.setAttribute("type", "content");
       panelNode.appendChild(iframeNode);
     }
 
     let popupSet = document.getElementById("mainPopupSet");
     popupSet.appendChild(panelNode);
     panelNode.addEventListener("popuphidden", () => {
+      if (iframeNode) {
+        action.onIframeHidden(iframeNode, panelNode);
+      }
       panelNode.remove();
     }, { once: true });
 
+    panelNode.addEventListener("popuphiding", () => {
+      if (iframeNode) {
+        action.onIframeHiding(iframeNode, panelNode);
+      }
+    }, { once: true });
+
     if (panelViewNode) {
       action.subview.onPlaced(panelViewNode);
       action.subview.onShowing(panelViewNode);
     }
 
     this.panelNode.hidePopup();
 
     let urlbarNodeID = this._urlbarButtonNodeIDForActionID(action.id);
-    let anchorNode =
-      document.getElementById(urlbarNodeID) || this.mainButtonNode;
+    let urlbarNode = document.getElementById(urlbarNodeID);
+    let anchorNode;
+    if (urlbarNode && !urlbarNode.hidden) {
+      anchorNode = action.anchorIDOverride ?
+        document.getElementById(action.anchorIDOverride) :
+        urlbarNode;
+    } else {
+      anchorNode = this.mainButtonNode;
+    }
     panelNode.openPopup(anchorNode, "bottomcenter topright");
 
     if (iframeNode) {
       action.onIframeShown(iframeNode, panelNode);
     }
+
+    return panelNode;
   },
 
   get _tempPanelID() {
     return "pageActionTempPanel";
   },
 
   /**
    * Adds or removes as necessary a DOM node for the given action in the urlbar.
@@ -406,16 +425,24 @@ var BrowserPageActions = {
   updateActionTitle(action) {
     let id = this._panelButtonNodeIDForActionID(action.id);
     let node = document.getElementById(id);
     if (node) {
       node.setAttribute("label", action.title);
     }
   },
 
+  doCommandForAction(action) {
+    if (action.subview || action.wantsIframe) {
+      this._toggleTempPanelForAction(action);
+      return;
+    }
+    action.onCommand();
+  },
+
   /**
    * Returns the action for a node.
    *
    * @param  node (DOM node, required)
    *         A button DOM node, either one that's shown in the page action panel
    *         or the urlbar.
    * @return (PageAction.Action) The node's related action, or null if none.
    */
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -659,17 +659,17 @@ html|input.urlbar-input[textoverflow]:no
   transition: none;
 }
 
 #DateTimePickerPanel[active="true"] {
   -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
 }
 
 #urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon,
-#urlbar[pageproxystate="invalid"] > #urlbar-icons > #star-button-box > .urlbar-icon,
+#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon-wrapper > .urlbar-icon,
 .urlbar-go-button[pageproxystate="valid"],
 .urlbar-go-button:not([parentfocused="true"]),
 #urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
 #urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
   visibility: collapse;
 }
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -894,16 +894,17 @@
                        tooltip="dynamic-shortcut-tooltip"
                        hidden="true"/>
                 <image id="pageActionButton"
                        class="urlbar-icon"
                        tooltiptext="&pageActionButton.tooltip;"
                        onclick="BrowserPageActions.mainButtonClicked(event);"/>
                 <hbox id="star-button-box"
                       hidden="true"
+                      class="urlbar-icon-wrapper"
                       context="pageActionPanelContextMenu"
                       oncontextmenu="BrowserPageActions.onContextMenu(event);"
                       onclick="BrowserPageActions.bookmark.onUrlbarNodeClicked(event);">
                   <image id="star-button" class="urlbar-icon">
                     <observes element="bookmarkThisPageBroadcaster" attribute="starred"/>
                     <observes element="bookmarkThisPageBroadcaster" attribute="tooltiptext"/>
                   </image>
                   <hbox id="star-button-animatable-box">
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -5,32 +5,34 @@
 
 /* global ADDON_ENABLE:false, ADDON_DISABLE:false, APP_SHUTDOWN: false */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, manager: Cm} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
-                                  "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
-                                  "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutPocket",
+                                  "chrome://pocket/content/AboutPocket.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+                                  "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
                                   "resource:///modules/PageActions.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
-                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
+                                  "chrome://pocket/content/Pocket.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
                                   "resource://gre/modules/ReaderMode.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Pocket",
-                                  "chrome://pocket/content/Pocket.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AboutPocket",
-                                  "chrome://pocket/content/AboutPocket.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+                                  "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "gPocketBundle", function() {
   return Services.strings.createBundle("chrome://pocket/locale/pocket.properties");
 });
 XPCOMUtils.defineLazyGetter(this, "gPocketStyleURI", function() {
   return Services.io.newURI("chrome://pocket/skin/pocket.css");
 });
 
 // Due to bug 1051238 frame scripts are cached forever, so we can't update them
@@ -153,16 +155,17 @@ function CreatePocketWidget(reason) {
 
 function isPocketEnabled() {
   return PocketPageAction.shouldUse ? PocketPageAction.enabled :
          !!CustomizableUI.getPlacementOfWidget("pocket-button");
 }
 
 var PocketPageAction = {
   pageAction: null,
+  urlbarNode: null,
 
   get shouldUse() {
     return !Services.prefs.getBranch(PREF_BRANCH)
                     .getBoolPref("disablePageAction", false);
   },
 
   get enabled() {
     return !!this.pageAction;
@@ -175,34 +178,141 @@ var PocketPageAction = {
     let id = "pocket";
     this.pageAction = PageActions.actionForID(id);
     if (!this.pageAction) {
       this.pageAction = PageActions.addAction(new PageActions.Action({
         id,
         title: gPocketBundle.GetStringFromName("pocket-button.label"),
         shownInUrlbar: true,
         wantsIframe: true,
+        urlbarIDOverride: "pocket-button-box",
+        anchorIDOverride: "pocket-button",
         _insertBeforeActionID: PageActions.ACTION_ID_BOOKMARK_SEPARATOR,
+        _urlbarNodeInMarkup: true,
+        onBeforePlacedInWindow(window) {
+          let doc = window.document;
+
+          if (doc.getElementById("pocket-button-box")) {
+            return;
+          }
+
+          let wrapper = doc.createElement("hbox");
+          wrapper.id = "pocket-button-box";
+          wrapper.classList.add("urlbar-icon-wrapper");
+          wrapper.setAttribute("context", "pageActionPanelContextMenu");
+          wrapper.addEventListener("contextmenu", event => {
+            window.BrowserPageActions.onContextMenu(event);
+          });
+          let animatableBox = doc.createElement("hbox");
+          animatableBox.id = "pocket-animatable-box";
+          let animatableImage = doc.createElement("image");
+          animatableImage.id = "pocket-animatable-image";
+          let pocketButton = doc.createElement("image");
+          pocketButton.id = "pocket-button";
+          pocketButton.classList.add("urlbar-icon");
+
+          wrapper.appendChild(pocketButton);
+          wrapper.appendChild(animatableBox);
+          animatableBox.appendChild(animatableImage);
+          let iconBox = doc.getElementById("urlbar-icons");
+          iconBox.appendChild(wrapper);
+          wrapper.hidden = true;
+          wrapper.addEventListener("click", event => {
+            PocketPageAction.onUrlbarNodeClicked(event);
+          });
+        },
         onPlacedInPanel(panelNode, urlbarNode) {
           PocketOverlay.onWindowOpened(panelNode.ownerGlobal);
         },
         onIframeShown(iframe, panel) {
           Pocket.onShownInPhotonPageActionPanel(panel, iframe);
+
+          let doc = panel.ownerDocument;
+          let urlbarNode = doc.getElementById("pocket-button-box");
+          if (!urlbarNode || urlbarNode.hidden) {
+            return;
+          }
+
+          PocketPageAction.urlbarNode = urlbarNode;
+          PocketPageAction.urlbarNode.setAttribute("open", "true");
+          if (Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled")) {
+            PocketPageAction.urlbarNode.setAttribute("animate", "true");
+          }
+        },
+        onIframeHiding(iframe, panel) {
+          if (iframe.getAttribute("itemAdded") == "true") {
+            PocketPageAction.startLibraryAnimation(iframe.ownerDocument);
+          }
+        },
+        onIframeHidden(iframe, panel) {
+          if (!PocketPageAction.urlbarNode) {
+            return;
+          }
+          PocketPageAction.urlbarNode.removeAttribute("animate");
+          PocketPageAction.urlbarNode.removeAttribute("open");
+          PocketPageAction.urlbarNode = null;
         },
       }));
     }
   },
 
   shutdown() {
     if (!this.pageAction) {
       return;
     }
+
+    for (let win of browserWindows()) {
+      let doc = win.document;
+      let pocketButtonBox = doc.getElementById("pocket-button-box");
+      if (pocketButtonBox) {
+        pocketButtonBox.remove();
+      }
+    }
+
     this.pageAction.remove();
     this.pageAction = null;
   },
+
+  onUrlbarNodeClicked(event) {
+    if (event.type == "click" && event.button != 0) {
+      return;
+    }
+
+    BrowserUtils.setToolbarButtonHeightProperty(event.target);
+
+    let win = event.target.ownerGlobal;
+    let browserPageActions = win.BrowserPageActions;
+    browserPageActions.doCommandForAction(PocketPageAction.pageAction);
+  },
+
+  startLibraryAnimation(doc) {
+    var libraryButton = doc.getElementById("library-button");
+    if (!Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled") ||
+        !libraryButton ||
+        libraryButton.getAttribute("cui-areatype") == "menu-panel" ||
+        libraryButton.getAttribute("overflowedItem") == "true" ||
+        !libraryButton.closest("#nav-bar")) {
+      return;
+    }
+    libraryButton.removeAttribute("fade");
+    libraryButton.setAttribute("animate", "pocket");
+    libraryButton.addEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
+  },
+
+  onLibraryButtonAnimationEnd(event) {
+    let doc = event.target.ownerDocument;
+    let libraryButton = doc.getElementById("library-button");
+    if (event.animationName.startsWith("library-pocket-animation")) {
+      libraryButton.setAttribute("fade", "true");
+    } else if (event.animationName == "library-pocket-fade") {
+      libraryButton.removeEventListener("animationend", PocketPageAction.onLibraryButtonAnimationEnd);
+      libraryButton.removeAttribute("animate");
+      libraryButton.removeAttribute("fade");
+    }
+  },
 };
 
 // PocketContextMenu
 // When the context menu is opened check if we need to build and enable pocket UI.
 var PocketContextMenu = {
   init() {
     Services.obs.addObserver(this, "on-build-contextmenu");
   },
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -220,16 +220,17 @@ var pktUI = (function() {
         if (inOverflowMenu) {
             startheight = overflowMenuHeight;
         }
 
         var panelId = showPanel("about:pocket-saved?pockethost=" + Services.prefs.getCharPref("extensions.pocket.site") + "&premiumStatus=" + (pktApi.isPremiumUser() ? "1" : "0") + "&inoverflowmenu=" + inOverflowMenu + "&locale=" + getUILocale(), {
             onShow() {
                 var saveLinkMessageId = "saveLink";
                 _lastAddSucceeded = false;
+                getPanelFrame().setAttribute("itemAdded", "false");
 
                 // Send error message for invalid url
                 if (!isValidURL) {
                     // TODO: Pass key for localized error in error object
                     let error = {
                         message: "Only links can be saved",
                         localizedKey: "onlylinkssaved"
                     };
@@ -252,16 +253,17 @@ var pktUI = (function() {
                     success(data, request) {
                         var item = data.item;
                         var successResponse = {
                             status: "success",
                             item
                         };
                         pktUIMessaging.sendMessageToPanel(panelId, saveLinkMessageId, successResponse);
                         _lastAddSucceeded = true;
+                        getPanelFrame().setAttribute("itemAdded", "true");
                     },
                     error(error, request) {
                         // If user is not authorized show singup page
                         if (request.status === 401) {
                             showSignUp();
                             return;
                         }
 
@@ -482,16 +484,17 @@ var pktUI = (function() {
         // Based on clicking "remove page" CTA, and passed unique item id, remove the item
         var _deleteItemMessageId = "deleteItem";
         pktUIMessaging.addMessageListener(iframe, _deleteItemMessageId, function(panelId, data) {
             pktApi.deleteItem(data.itemId, {
                 success(data, response) {
                     var successResponse = {status: "success"};
                     pktUIMessaging.sendResponseMessageToPanel(panelId, _deleteItemMessageId, successResponse);
                     _lastAddSucceeded = false;
+                    getPanelFrame().setAttribute("itemAdded", "false");
                 },
                 error(error, response) {
                     pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _deleteItemMessageId, error);
                 }
             })
         });
 
         var _initL10NMessageId = "initL10N";
--- a/browser/extensions/pocket/skin/shared/pocket-animation.svg
+++ b/browser/extensions/pocket/skin/shared/pocket-animation.svg
@@ -1,157 +1,243 @@
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="240" height="18" fill="context-fill">
-  <svg width="20" height="18">
+<svg xmlns="http://www.w3.org/2000/svg" width="260" height="16" fill="context-fill">
+  <svg>
     <defs>
-      <filter id="a" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="a">
+        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
+      </filter>
+    </defs>
+    <g>
+      <g>
+        <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
+      </g>
+      <g filter="url(#a)" transform="translate(1.749 1.092) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
+  </svg>
+  <svg x="20">
+    <defs>
+      <filter id="b">
+        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
+      </filter>
+    </defs>
+    <g>
+      <g>
+        <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
+      </g>
+      <g filter="url(#b)" transform="translate(1.749 1.092) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
+  </svg>
+  <svg x="40">
+    <defs>
+      <filter id="c">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
-      <g fill-opacity=".25">
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
+      <g>
+        <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
+      </g>
+      <g filter="url(#c)" transform="translate(1.749 1.86) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
       </g>
-      <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#a)" transform="translate(10.005 9.286) scale(1.02438)"/>
+    </g>
+  </svg>
+  <svg x="60">
+    <defs>
+      <filter id="d">
+        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
+      </filter>
+    </defs>
+    <g>
+      <g>
+        <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
+      </g>
+      <g filter="url(#d)" transform="translate(1.752 1.862) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
     </g>
   </svg>
-  <svg width="20" height="18" x="20">
+  <svg x="80">
+    <defs>
+      <filter id="e">
+        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
+      </filter>
+    </defs>
+    <g>
+      <g>
+        <path d="M10.016 15.97c-4.978 0 -9.014 -4.036 -9.014 -9.014v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.521a2.253 2.253 0 0 1 2.254 2.254v4.507c0 4.978 -4.036 9.014 -9.015 9.014z"/>
+      </g>
+      <g filter="url(#e)" transform="translate(1.749 1.86) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
+  </svg>
+  <svg x="100">
     <defs>
-      <filter id="b" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="f">
+        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
+      </filter>
+    </defs>
+    <g>
+      <g>
+        <path d="M10.016 15.94c-4.958 0 -8.979 -4.021 -8.979 -8.98v-4.49a2.244 2.244 0 0 1 2.245 -2.244h13.469a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.958 -4.02 8.979 -8.98 8.979z"/>
+      </g>
+      <g filter="url(#f)" transform="translate(1.749 1.817) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
+  </svg>
+  <svg x="120">
+    <defs>
+      <filter id="g">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
     <g>
-      <g fill-opacity=".25">
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
-        <path d="M10 16.41c-4.527 0 -8.179 -3.826 -8.179 -8.352v-4.097a2.05 2.05 0 0 1 2.05 -2.05h12.253a2.05 2.05 0 0 1 2.049 2.05v4.097c0 4.526 -3.648 8.352 -8.174 8.352z"/>
+      <g>
+        <path d="M10.016 15.858c-4.907 0 -8.886 -3.979 -8.886 -8.886v-4.443a2.221 2.221 0 0 1 2.222 -2.222h13.33a2.221 2.221 0 0 1 2.22 2.222v4.443c0 4.907 -3.978 8.886 -8.886 8.886z"/>
       </g>
-      <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#b)" transform="translate(10.005 9.286) scale(1.02438)"/>
+      <g filter="url(#g)" transform="translate(1.749 1.705) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
     </g>
   </svg>
-  <svg width="20" height="18" x="40">
+  <svg x="140">
     <defs>
-      <filter id="c" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
-        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
-      </filter>
-    </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#c)" transform="translate(10.005 10.054) scale(1.02438)"/>
-  </svg>
-  <svg width="20" height="18" x="60">
-    <defs>
-      <filter id="d" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="h">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#d)" transform="translate(10.009 10.057) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.743c-4.835 0 -8.755 -3.92 -8.755 -8.755v-4.377a2.188 2.188 0 0 1 2.19 -2.19h13.132c1.21 0 2.189 0.98 2.189 2.19v4.377c0 4.835 -3.92 8.755 -8.756 8.755z"/>
+      </g>
+      <g filter="url(#h)" transform="translate(1.749 1.552) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="80">
+  <svg x="160">
     <defs>
-      <filter id="e" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="i">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#e)" transform="translate(10.005 10.054) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.612c-4.752 0 -8.604 -3.853 -8.604 -8.605v-4.302a2.15 2.15 0 0 1 2.15 -2.152h12.908a2.15 2.15 0 0 1 2.151 2.152v4.302c0 4.752 -3.853 8.605 -8.605 8.605z"/>
+      </g>
+      <g filter="url(#i)" transform="translate(1.749 1.387) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="100">
+  <svg x="180">
     <defs>
-      <filter id="f" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
-        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
-      </filter>
-    </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#f)" transform="translate(10.005 10.016) scale(1.02438)"/>
-  </svg>
-  <svg width="20" height="18" x="120">
-    <defs>
-      <filter id="g" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="j">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#g)" transform="translate(10.005 9.925) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.48c-4.668 0 -8.454 -3.785 -8.454 -8.454v-4.227a2.113 2.113 0 0 1 2.114 -2.114h12.681a2.113 2.113 0 0 1 2.114 2.114v4.227c0 4.669 -3.786 8.454 -8.455 8.454z"/>
+      </g>
+      <g filter="url(#j)" transform="translate(1.749 1.238) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="140">
+  <svg x="200">
     <defs>
-      <filter id="h" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="k">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#h)" transform="translate(10.005 9.802) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.365c-4.596 0 -8.323 -3.726 -8.323 -8.323v-4.161a2.08 2.08 0 0 1 2.081 -2.081h12.485a2.08 2.08 0 0 1 2.08 2.08v4.162c0 4.597 -3.726 8.323 -8.323 8.323z"/>
+      </g>
+      <g filter="url(#k)" transform="translate(1.749 1.132) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="160">
+  <svg x="220">
     <defs>
-      <filter id="i" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="l">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill-opacity=".25" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path fill="#EE4055" d="M9.997 16.955c-4.978 0 -8.995 -4.208 -8.995 -9.187v-4.507a2.253 2.253 0 0 1 2.254 -2.254h13.479a2.253 2.253 0 0 1 2.253 2.254v4.507c0 4.979 -4.012 9.187 -8.99 9.187z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#i)" transform="translate(10.005 9.66) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.284c-4.545 0 -8.23 -3.685 -8.23 -8.23v-4.115a2.057 2.057 0 0 1 2.058 -2.058h12.345a2.057 2.057 0 0 1 2.058 2.058v4.115c0 4.545 -3.685 8.23 -8.23 8.23z"/>
+      </g>
+      <g filter="url(#l)" transform="translate(1.749 1.092) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="180">
+  <svg x="240">
     <defs>
-      <filter id="j" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="m">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.997 16.924c-4.958 0 -8.96 -4.192 -8.96 -9.151v-4.49a2.244 2.244 0 0 1 2.245 -2.245h13.426a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.959 -3.997 9.15 -8.956 9.15z"/>
-    <path fill-opacity=".25" d="M9.997 16.924c-4.958 0 -8.96 -4.192 -8.96 -9.151v-4.49a2.244 2.244 0 0 1 2.245 -2.245h13.426a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.959 -3.997 9.15 -8.956 9.15z"/>
-    <path fill-opacity=".25" d="M9.997 16.924c-4.958 0 -8.96 -4.192 -8.96 -9.151v-4.49a2.244 2.244 0 0 1 2.245 -2.245h13.426a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.959 -3.997 9.15 -8.956 9.15z"/>
-    <path fill="#EE4055" d="M9.997 16.924c-4.958 0 -8.96 -4.192 -8.96 -9.151v-4.49a2.244 2.244 0 0 1 2.245 -2.245h13.426a2.244 2.244 0 0 1 2.245 2.245v4.49c0 4.959 -3.997 9.15 -8.956 9.15z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#j)" transform="translate(10.005 9.512) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
+      </g>
+      <g filter="url(#m)" transform="translate(1.749 1.092) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
-  <svg width="20" height="18" x="200">
+  <svg x="260">
     <defs>
-      <filter id="k" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
+      <filter id="n">
         <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
       </filter>
     </defs>
-    <path fill-opacity=".25" d="M9.998 16.84c-4.908 0 -8.868 -4.148 -8.868 -9.056v-4.443a2.222 2.222 0 0 1 2.222 -2.222h13.287a2.222 2.222 0 0 1 2.222 2.222v4.443c0 4.908 -3.956 9.057 -8.863 9.057z"/>
-    <path fill-opacity=".25" d="M9.998 16.84c-4.908 0 -8.868 -4.148 -8.868 -9.056v-4.443a2.222 2.222 0 0 1 2.222 -2.222h13.287a2.222 2.222 0 0 1 2.222 2.222v4.443c0 4.908 -3.956 9.057 -8.863 9.057z"/>
-    <path fill-opacity=".25" d="M9.998 16.84c-4.908 0 -8.868 -4.148 -8.868 -9.056v-4.443a2.222 2.222 0 0 1 2.222 -2.222h13.287a2.222 2.222 0 0 1 2.222 2.222v4.443c0 4.908 -3.956 9.057 -8.863 9.057z"/>
-    <path fill="#EE4055" d="M9.998 16.84c-4.908 0 -8.868 -4.148 -8.868 -9.056v-4.443a2.222 2.222 0 0 1 2.222 -2.222h13.287a2.222 2.222 0 0 1 2.222 2.222v4.443c0 4.908 -3.956 9.057 -8.863 9.057z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#k)" transform="translate(10.005 9.372) scale(1.02438)"/>
-  </svg>
-  <svg width="20" height="18" x="220">
-    <defs>
-      <filter id="l" width="100%" height="100%" x="0%" y="0%" filterUnits="objectBoundingBox">
-        <feColorMatrix color-interpolation-filters="sRGB" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/>
-      </filter>
-    </defs>
-    <path fill-opacity=".25" d="M9.998 16.723c-4.835 0 -8.737 -4.087 -8.737 -8.922v-4.378a2.189 2.189 0 0 1 2.19 -2.189h13.09a2.19 2.19 0 0 1 2.19 2.19v4.376c0 4.835 -3.898 8.922 -8.733 8.922z"/>
-    <path fill-opacity=".25" d="M9.998 16.723c-4.835 0 -8.737 -4.087 -8.737 -8.922v-4.378a2.189 2.189 0 0 1 2.19 -2.189h13.09a2.19 2.19 0 0 1 2.19 2.19v4.376c0 4.835 -3.898 8.922 -8.733 8.922z"/>
-    <path fill-opacity=".25" d="M9.998 16.723c-4.835 0 -8.737 -4.087 -8.737 -8.922v-4.378a2.189 2.189 0 0 1 2.19 -2.189h13.09a2.19 2.19 0 0 1 2.19 2.19v4.376c0 4.835 -3.898 8.922 -8.733 8.922z"/>
-    <path fill="#EE4055" d="M9.998 16.723c-4.835 0 -8.737 -4.087 -8.737 -8.922v-4.378a2.189 2.189 0 0 1 2.19 -2.189h13.09a2.19 2.19 0 0 1 2.19 2.19v4.376c0 4.835 -3.898 8.922 -8.733 8.922z"/>
-    <path d="M3.986 -3.055a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 -0.715 -1.711z" filter="url(#l)" transform="translate(10.005 9.286) scale(1.02438)"/>
+    <g>
+      <g>
+        <path d="M10.016 15.253c-4.525 0 -8.195 -3.669 -8.195 -8.195v-4.097a2.048 2.048 0 0 1 2.05 -2.05h12.292a2.048 2.048 0 0 1 2.049 2.05v4.097c0 4.526 -3.67 8.195 -8.196 8.195z"/>
+      </g>
+      <g filter="url(#n)" transform="translate(1.749 1.092) scale(1.02438)">
+        <g>
+          <path d="M11.985 3.968a0.991 0.991 0 0 0 -0.726 0.319l-3.281 3.284 -3.224 -3.235a0.984 0.984 0 0 0 -0.754 -0.368 1.001 1.001 0 0 0 -0.715 1.7l-0.016 0.011 3.294 3.306 0.706 0.707a1 1 0 0 0 1.414 0l0.707 -0.707 3.31 -3.306a1.001 1.001 0 0 0 0.22 -1.097 1 1 0 0 0 -0.935 -0.614z"/>
+        </g>
+      </g>
+    </g>
   </svg>
 </svg>
deleted file mode 100644
--- a/browser/extensions/pocket/skin/shared/pocket-list.svg
+++ /dev/null
@@ -1,7 +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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
-  <path fill="context-fill" d="M4 7a4 4 0 0 1-4-4V1a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4zm1.993-5.016a.5.5 0 0 0-.362.159L3.989 3.785 2.378 2.168A.492.492 0 0 0 2 1.984a.5.5 0 0 0-.357.85l-.008.006 1.647 1.653.353.354a.5.5 0 0 0 .707 0l.358-.354L6.35 2.84a.5.5 0 0 0-.357-.856z"/>
-  <path fill="context-fill" d="M13 1h-3a1 1 0 0 0 0 2h3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V8a1 1 0 0 0-2 0v4a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V4a3 3 0 0 0-3-3z"/>
-</svg>
--- a/browser/extensions/pocket/skin/shared/pocket.css
+++ b/browser/extensions/pocket/skin/shared/pocket.css
@@ -12,81 +12,104 @@ panelmultiview[mainViewId=PanelUI-pocket
 #PanelUI-pocketView {
   overflow: visible;
 }
 
 #pocket-button {
   list-style-image: url("chrome://pocket-shared/skin/pocket.svg");
 }
 
+#pocket-button-box[animate="true"] > #pocket-button,
 #pocket-button[open="true"][animationsenabled] > .toolbarbutton-icon {
   fill: transparent;
 }
 
+#pocket-button-box[open="true"] > #pocket-button {
+  fill: rgb(213,32,20);
+  fill-opacity: 1;
+}
+
 @keyframes pocket-animation {
   from {
     transform: translateX(0);
   }
   to {
-    transform: translateX(-220px);
+    transform: translateX(-240px);
   }
 }
 
 @keyframes pocket-animation-rtl {
   from {
     transform: scaleX(-1) translateX(0);
   }
   to {
-    transform: scaleX(-1) translateX(-220px);
+    transform: scaleX(-1) translateX(-240px);
   }
 }
 
+#pocket-button-box > #pocket-animatable-box,
 #pocket-button > .toolbarbutton-animatable-box {
   position: absolute;
   overflow: hidden;
-  top: calc(50% - 9px); /* 9px is half the height of the sprite */
+  top: calc(50% - 8px); /* 8px is half the height of the sprite */
   /* Since .toolbarbutton-icon uses a different width than the animatable box,
      we need to set a padding relative to the difference in widths. */
   margin-inline-start: calc((16px + 2 * var(--toolbarbutton-inner-padding) - 20px) / 2);
   /* Set the min- and max- width and height of the box equal to that
      of each frame of the SVG sprite. Setting the width and height via
      the `width` and `height` CSS properties causes an assertion for
      `inline-size less than zero: 'aContainingBlockISize >= 0'` (bug 1379332). */
   min-width: 20px;
   max-width: 20px;
-  min-height: 18px;
-  max-height: 18px;
+  min-height: 16px;
+  max-height: 16px;
 }
 
+#pocket-button-box > #pocket-animatable-box {
+  /* Match the 6px margin-inline-start of .urlbar-icon plus 1px for internal padding in the animation sprite. */
+  margin-inline-start: 7px;
+}
+
+#pocket-button-box > #pocket-animatable-box > #pocket-animatable-image,
 #pocket-button > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
   height: var(--toolbarbutton-height); /* Height must be equal to height of toolbarbutton padding-box */
 }
 
+#pocket-button-box[animate="true"],
 #pocket-button[open="true"][animationsenabled][cui-areatype="toolbar"]:not([overflowedItem="true"]) {
   position: relative;
 }
 
+#pocket-button-box:not([animate="true"]):not(:hover) > #pocket-animatable-box {
+  display: none;
+}
+
 /* Preload pocket-animation.svg and library-pocket-animation.svg to prevent
    a flicker at the start of either animation. The preloading of the library
    animation is triggered off of hovering the pocket button since the pocket
    button always animates before the library button. */
+#pocket-button-box:not([animate="true"]):hover > #pocket-animatable-box > #pocket-animatable-image,
 #pocket-button[animationsenabled][cui-areatype="toolbar"]:not([overflowedItem="true"]):not([open="true"]):hover > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
   background-image: url("chrome://pocket-shared/skin/pocket-animation.svg"),
                     url("chrome://pocket-shared/skin/library-pocket-animation.svg");
   background-size: 0, 0;
 }
 
+#pocket-button-box[animate="true"] > #pocket-animatable-box > #pocket-animatable-image,
 #pocket-button[open="true"][animationsenabled][cui-areatype="toolbar"]:not([overflowedItem="true"]) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
   animation-name: pocket-animation;
-  animation-timing-function: steps(11);
-  animation-duration: 184ms;
+  animation-timing-function: steps(12);
+  animation-duration: 200ms;
   background-image: url("chrome://pocket-shared/skin/pocket-animation.svg");
-  width: 240px;
+  fill: rgb(213,32,20);
+  -moz-context-properties: fill;
+  width: 260px;
 }
 
+#pocket-button-box[animate="true"]:-moz-locale-dir(rtl) > #pocket-animatable-box > #pocket-animatable-image,
 #pocket-button[open="true"][animationsenabled][cui-areatype="toolbar"]:not([overflowedItem="true"]):-moz-locale-dir(rtl) > .toolbarbutton-animatable-box > .toolbarbutton-animatable-image {
   animation-name: pocket-animation-rtl;
 }
 
 #library-button[animate="pocket"] > .toolbarbutton-icon {
   fill: transparent;
 }
 
@@ -218,17 +241,17 @@ panelmultiview[mainViewId=PanelUI-pocket
   }
 
   #pocket-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
     -moz-image-region: rect(64px, 64px, 128px, 0);
   }
 }
 
 #appMenu-library-pocket-button {
-  list-style-image: url("chrome://pocket-shared/skin/pocket-list.svg");
+  list-style-image: url("chrome://pocket-shared/skin/pocket.svg");
 }
 
 #panelMenu_pocket,
 #menu_pocket,
 #BMB_pocket {
   list-style-image: url("chrome://pocket/content/panels/img/pocketmenuitem16.png");
 }
 
@@ -240,11 +263,11 @@ panelmultiview[mainViewId=PanelUI-pocket
   }
 
   #panelMenu_pocket > .toolbarbutton-icon {
     width: 16px;
   }
 }
 
 #pageAction-panel-pocket,
-#pageAction-urlbar-pocket {
+#pocket-button {
   list-style-image: url("chrome://pocket-shared/skin/pocket.svg");
 }
--- a/browser/extensions/pocket/skin/shared/pocket.svg
+++ b/browser/extensions/pocket/skin/shared/pocket.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
-  <path fill-opacity="context-fill-opacity" fill="context-fill" d="M9,15.969a8,8,0,0,1-8-8v-4a2,2,0,0,1,2-2H15a2,2,0,0,1,2,2v4A8,8,0,0,1,9,15.969ZM12.985,5.937a0.99,0.99,0,0,0-.725.319L8.978,9.539,5.755,6.305A0.984,0.984,0,0,0,5,5.937a1,1,0,0,0-.714,1.7L4.27,7.648l3.293,3.306h0l0.707,0.707a1,1,0,0,0,1.414,0l0.707-.707h0L13.7,7.648l0,0A1,1,0,0,0,12.985,5.937Z"/>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+  <path fill-opacity="context-fill-opacity" fill="context-fill" d="M8 15a8 8 0 0 1-8-8V3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v4a8 8 0 0 1-8 8zm3.985-10.032a.99.99 0 0 0-.725.319L7.978 8.57 4.755 5.336A.984.984 0 0 0 4 4.968a1 1 0 0 0-.714 1.7l-.016.011 3.293 3.306.707.707a1 1 0 0 0 1.414 0l.707-.707L12.7 6.679a1 1 0 0 0-.715-1.711z"/>
 </svg>
--- a/browser/modules/PageActions.jsm
+++ b/browser/modules/PageActions.jsm
@@ -375,29 +375,48 @@ this.PageActions = {
  * A single page action.
  *
  * @param  options (object, required)
  *         An object with the following properties:
  *         @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 for this property if to override which element
+ *                that the temporary panel is anchored to.
  *         @param iconURL (string, optional)
  *                The URL 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 -- like your code is in an embedded
  *                WebExtension.
  *         @param nodeAttributes (object, optional)
  *                An object of name-value pairs.  Each pair will be added as
  *                an attribute to DOM nodes created for this action.
+ *         @param onBeforePlacedInWindow (function, optional)
+ *                Called before the action is placed in the window. Passed the
+ *                following arguments:
+ *                * 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.  Passed the following arguments:
  *                * event: The triggering event.
  *                * buttonNode: The button node that was clicked.
+ *         @param onIframeHiding (function, optional)
+ *                Called when the action's iframe is hiding. Passed the
+ *                following arguments:
+ *                * 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. Passed the
+ *                following arguments:
+ *                * iframeNode: The iframe.
+ *                * parentPanelNode: The panel node in which the iframe is
+ *                  shown.
  *         @param onIframeShown (function, optional)
  *                Called when the action's iframe is shown to the user.  Passed
  *                the following arguments:
  *                * iframeNode: The iframe.
  *                * parentPanelNode: The panel node in which the iframe is
  *                  shown.
  *         @param onPlacedInPanel (function, optional)
  *                Called when the action is added to the page action panel in
@@ -427,19 +446,23 @@ this.PageActions = {
  *         @param wantsIframe (bool, optional)
  *                Pass true to make an action that shows an iframe in a panel
  *                when clicked.
  */
 function Action(options) {
   setProperties(this, options, {
     id: true,
     title: !options._isSeparator,
+    anchorIDOverride: false,
     iconURL: false,
     nodeAttributes: false,
+    onBeforePlacedInWindow: false,
     onCommand: false,
+    onIframeHiding: false,
+    onIframeHidden: false,
     onIframeShown: false,
     onPlacedInPanel: false,
     onPlacedInUrlbar: false,
     onShowingInPanel: false,
     shownInUrlbar: false,
     subview: false,
     tooltip: false,
     urlbarIDOverride: false,
@@ -527,16 +550,23 @@ Action.prototype = {
   /**
    * The action's tooltip (string, nullable)
    */
   get tooltip() {
     return this._tooltip;
   },
 
   /**
+   * Override for the ID of the action's temporary panel anchor (string, nullable)
+   */
+  get anchorIDOverride() {
+    return this._anchorIDOverride;
+  },
+
+  /**
    * Override for the ID of the action's urlbar node (string, nullable)
    */
   get urlbarIDOverride() {
     return this._urlbarIDOverride;
   },
 
   /**
    * True if the action is shown in an iframe (bool, nonnull)
@@ -548,30 +578,70 @@ Action.prototype = {
   /**
    * A Subview object if the action wants a subview (Subview, nullable)
    */
   get subview() {
     return this._subview;
   },
 
   /**
+   * Call this when before placing the action in the window.
+   *
+   * @param  window (DOM window, required)
+   *         The window the action will be placed in.
+   */
+  onBeforePlacedInWindow(window) {
+    if (this._onBeforePlacedInWindow) {
+      this._onBeforePlacedInWindow(window);
+    }
+  },
+
+  /**
    * 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 shown.
    *
    * @param  iframeNode (DOM node, required)
    *         The iframe that's being shown.
    * @param  parentPanelNode (DOM node, required)
    *         The panel in which the iframe is shown.
    */
   onIframeShown(iframeNode, parentPanelNode) {