browser/base/content/browser-social.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/base/content/browser-social.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1498 @@
     1.4 +// This Source Code Form is subject to the terms of the Mozilla Public
     1.5 +// License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 +// file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.7 +
     1.8 +// the "exported" symbols
     1.9 +let SocialUI,
    1.10 +    SocialChatBar,
    1.11 +    SocialFlyout,
    1.12 +    SocialMarks,
    1.13 +    SocialShare,
    1.14 +    SocialSidebar,
    1.15 +    SocialStatus;
    1.16 +
    1.17 +(function() {
    1.18 +
    1.19 +// The minimum sizes for the auto-resize panel code.
    1.20 +const PANEL_MIN_HEIGHT = 100;
    1.21 +const PANEL_MIN_WIDTH = 330;
    1.22 +
    1.23 +XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
    1.24 +  "resource:///modules/SharedFrame.jsm");
    1.25 +
    1.26 +XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
    1.27 +  let tmp = {};
    1.28 +  Cu.import("resource:///modules/Social.jsm", tmp);
    1.29 +  return tmp.OpenGraphBuilder;
    1.30 +});
    1.31 +
    1.32 +XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
    1.33 +  let tmp = {};
    1.34 +  Cu.import("resource:///modules/Social.jsm", tmp);
    1.35 +  return tmp.DynamicResizeWatcher;
    1.36 +});
    1.37 +
    1.38 +XPCOMUtils.defineLazyGetter(this, "sizeSocialPanelToContent", function() {
    1.39 +  let tmp = {};
    1.40 +  Cu.import("resource:///modules/Social.jsm", tmp);
    1.41 +  return tmp.sizeSocialPanelToContent;
    1.42 +});
    1.43 +
    1.44 +XPCOMUtils.defineLazyGetter(this, "CreateSocialStatusWidget", function() {
    1.45 +  let tmp = {};
    1.46 +  Cu.import("resource:///modules/Social.jsm", tmp);
    1.47 +  return tmp.CreateSocialStatusWidget;
    1.48 +});
    1.49 +
    1.50 +XPCOMUtils.defineLazyGetter(this, "CreateSocialMarkWidget", function() {
    1.51 +  let tmp = {};
    1.52 +  Cu.import("resource:///modules/Social.jsm", tmp);
    1.53 +  return tmp.CreateSocialMarkWidget;
    1.54 +});
    1.55 +
    1.56 +SocialUI = {
    1.57 +  _initialized: false,
    1.58 +
    1.59 +  // Called on delayed startup to initialize the UI
    1.60 +  init: function SocialUI_init() {
    1.61 +    if (this._initialized) {
    1.62 +      return;
    1.63 +    }
    1.64 +
    1.65 +    Services.obs.addObserver(this, "social:ambient-notification-changed", false);
    1.66 +    Services.obs.addObserver(this, "social:profile-changed", false);
    1.67 +    Services.obs.addObserver(this, "social:frameworker-error", false);
    1.68 +    Services.obs.addObserver(this, "social:providers-changed", false);
    1.69 +    Services.obs.addObserver(this, "social:provider-reload", false);
    1.70 +    Services.obs.addObserver(this, "social:provider-enabled", false);
    1.71 +    Services.obs.addObserver(this, "social:provider-disabled", false);
    1.72 +
    1.73 +    Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
    1.74 +
    1.75 +    gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
    1.76 +    document.getElementById("PanelUI-popup").addEventListener("popupshown", SocialMarks.updatePanelButtons, true);
    1.77 +
    1.78 +    // menupopups that list social providers. we only populate them when shown,
    1.79 +    // and if it has not been done already.
    1.80 +    document.getElementById("viewSidebarMenu").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
    1.81 +    document.getElementById("social-statusarea-popup").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
    1.82 +
    1.83 +    Social.init().then((update) => {
    1.84 +      if (update)
    1.85 +        this._providersChanged();
    1.86 +      // handle SessionStore for the sidebar state
    1.87 +      SocialSidebar.restoreWindowState();
    1.88 +    });
    1.89 +
    1.90 +    this._initialized = true;
    1.91 +  },
    1.92 +
    1.93 +  // Called on window unload
    1.94 +  uninit: function SocialUI_uninit() {
    1.95 +    if (!this._initialized) {
    1.96 +      return;
    1.97 +    }
    1.98 +    SocialSidebar.saveWindowState();
    1.99 +
   1.100 +    Services.obs.removeObserver(this, "social:ambient-notification-changed");
   1.101 +    Services.obs.removeObserver(this, "social:profile-changed");
   1.102 +    Services.obs.removeObserver(this, "social:frameworker-error");
   1.103 +    Services.obs.removeObserver(this, "social:providers-changed");
   1.104 +    Services.obs.removeObserver(this, "social:provider-reload");
   1.105 +    Services.obs.removeObserver(this, "social:provider-enabled");
   1.106 +    Services.obs.removeObserver(this, "social:provider-disabled");
   1.107 +
   1.108 +    Services.prefs.removeObserver("social.toast-notifications.enabled", this);
   1.109 +
   1.110 +    document.getElementById("PanelUI-popup").removeEventListener("popupshown", SocialMarks.updatePanelButtons, true);
   1.111 +    document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
   1.112 +    document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
   1.113 +
   1.114 +    this._initialized = false;
   1.115 +  },
   1.116 +
   1.117 +  observe: function SocialUI_observe(subject, topic, data) {
   1.118 +    // Exceptions here sometimes don't get reported properly, report them
   1.119 +    // manually :(
   1.120 +    try {
   1.121 +      switch (topic) {
   1.122 +        case "social:provider-enabled":
   1.123 +          SocialMarks.populateToolbarPalette();
   1.124 +          SocialStatus.populateToolbarPalette();
   1.125 +          break;
   1.126 +        case "social:provider-disabled":
   1.127 +          SocialMarks.removeProvider(data);
   1.128 +          SocialStatus.removeProvider(data);
   1.129 +          SocialSidebar.disableProvider(data);
   1.130 +          break;
   1.131 +        case "social:provider-reload":
   1.132 +          SocialStatus.reloadProvider(data);
   1.133 +          // if the reloaded provider is our current provider, fall through
   1.134 +          // to social:providers-changed so the ui will be reset
   1.135 +          if (!SocialSidebar.provider || SocialSidebar.provider.origin != data)
   1.136 +            return;
   1.137 +          // currently only the sidebar and flyout have a selected provider.
   1.138 +          // sidebar provider has changed (possibly to null), ensure the content
   1.139 +          // is unloaded and the frames are reset, they will be loaded in
   1.140 +          // providers-changed below if necessary.
   1.141 +          SocialSidebar.unloadSidebar();
   1.142 +          SocialFlyout.unload();
   1.143 +          // fall through to providers-changed to ensure the reloaded provider
   1.144 +          // is correctly reflected in any UI and the multi-provider menu
   1.145 +        case "social:providers-changed":
   1.146 +          this._providersChanged();
   1.147 +          break;
   1.148 +
   1.149 +        // Provider-specific notifications
   1.150 +        case "social:ambient-notification-changed":
   1.151 +          SocialStatus.updateButton(data);
   1.152 +          break;
   1.153 +        case "social:profile-changed":
   1.154 +          // make sure anything that happens here only affects the provider for
   1.155 +          // which the profile is changing, and that anything we call actually
   1.156 +          // needs to change based on profile data.
   1.157 +          SocialStatus.updateButton(data);
   1.158 +          break;
   1.159 +        case "social:frameworker-error":
   1.160 +          if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
   1.161 +            SocialSidebar.setSidebarErrorMessage();
   1.162 +          }
   1.163 +          break;
   1.164 +
   1.165 +        case "nsPref:changed":
   1.166 +          if (data == "social.toast-notifications.enabled") {
   1.167 +            SocialSidebar.updateToggleNotifications();
   1.168 +          }
   1.169 +          break;
   1.170 +      }
   1.171 +    } catch (e) {
   1.172 +      Components.utils.reportError(e + "\n" + e.stack);
   1.173 +      throw e;
   1.174 +    }
   1.175 +  },
   1.176 +
   1.177 +  _providersChanged: function() {
   1.178 +    SocialSidebar.clearProviderMenus();
   1.179 +    SocialSidebar.update();
   1.180 +    SocialChatBar.update();
   1.181 +    SocialShare.populateProviderMenu();
   1.182 +    SocialStatus.populateToolbarPalette();
   1.183 +    SocialMarks.populateToolbarPalette();
   1.184 +    SocialShare.update();
   1.185 +  },
   1.186 +
   1.187 +  // This handles "ActivateSocialFeature" events fired against content documents
   1.188 +  // in this window.
   1.189 +  _activationEventHandler: function SocialUI_activationHandler(e) {
   1.190 +    let targetDoc;
   1.191 +    let node;
   1.192 +    if (e.target instanceof HTMLDocument) {
   1.193 +      // version 0 support
   1.194 +      targetDoc = e.target;
   1.195 +      node = targetDoc.documentElement
   1.196 +    } else {
   1.197 +      targetDoc = e.target.ownerDocument;
   1.198 +      node = e.target;
   1.199 +    }
   1.200 +    if (!(targetDoc instanceof HTMLDocument))
   1.201 +      return;
   1.202 +
   1.203 +    // Ignore events fired in background tabs or iframes
   1.204 +    if (targetDoc.defaultView != content)
   1.205 +      return;
   1.206 +
   1.207 +    // If we are in PB mode, we silently do nothing (bug 829404 exists to
   1.208 +    // do something sensible here...)
   1.209 +    if (PrivateBrowsingUtils.isWindowPrivate(window))
   1.210 +      return;
   1.211 +
   1.212 +    // If the last event was received < 1s ago, ignore this one
   1.213 +    let now = Date.now();
   1.214 +    if (now - Social.lastEventReceived < 1000)
   1.215 +      return;
   1.216 +    Social.lastEventReceived = now;
   1.217 +
   1.218 +    // We only want to activate if it is as a result of user input.
   1.219 +    let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
   1.220 +                    .getInterface(Ci.nsIDOMWindowUtils);
   1.221 +    if (!dwu.isHandlingUserInput) {
   1.222 +      Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
   1.223 +      return;
   1.224 +    }
   1.225 +
   1.226 +    let data = node.getAttribute("data-service");
   1.227 +    if (data) {
   1.228 +      try {
   1.229 +        data = JSON.parse(data);
   1.230 +      } catch(e) {
   1.231 +        Cu.reportError("Social Service manifest parse error: "+e);
   1.232 +        return;
   1.233 +      }
   1.234 +    }
   1.235 +    Social.installProvider(targetDoc, data, function(manifest) {
   1.236 +      Social.activateFromOrigin(manifest.origin, function(provider) {
   1.237 +        if (provider.sidebarURL) {
   1.238 +          SocialSidebar.show(provider.origin);
   1.239 +        }
   1.240 +        if (provider.postActivationURL) {
   1.241 +          openUILinkIn(provider.postActivationURL, "tab");
   1.242 +        }
   1.243 +      });
   1.244 +    });
   1.245 +  },
   1.246 +
   1.247 +  showLearnMore: function() {
   1.248 +    let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
   1.249 +    openUILinkIn(url, "tab");
   1.250 +  },
   1.251 +
   1.252 +  closeSocialPanelForLinkTraversal: function (target, linkNode) {
   1.253 +    // No need to close the panel if this traversal was not retargeted
   1.254 +    if (target == "" || target == "_self")
   1.255 +      return;
   1.256 +
   1.257 +    // Check to see whether this link traversal was in a social panel
   1.258 +    let win = linkNode.ownerDocument.defaultView;
   1.259 +    let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
   1.260 +                                  .getInterface(Ci.nsIWebNavigation)
   1.261 +                                  .QueryInterface(Ci.nsIDocShell)
   1.262 +                                  .chromeEventHandler;
   1.263 +    let containerParent = container.parentNode;
   1.264 +    if (containerParent.classList.contains("social-panel") &&
   1.265 +        containerParent instanceof Ci.nsIDOMXULPopupElement) {
   1.266 +      // allow the link traversal to finish before closing the panel
   1.267 +      setTimeout(() => {
   1.268 +        containerParent.hidePopup();
   1.269 +      }, 0);
   1.270 +    }
   1.271 +  },
   1.272 +
   1.273 +  get _chromeless() {
   1.274 +    // Is this a popup window that doesn't want chrome shown?
   1.275 +    let docElem = document.documentElement;
   1.276 +    // extrachrome is not restored during session restore, so we need
   1.277 +    // to check for the toolbar as well.
   1.278 +    let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
   1.279 +                     docElem.getAttribute('chromehidden').contains("toolbar");
   1.280 +    // This property is "fixed" for a window, so avoid doing the check above
   1.281 +    // multiple times...
   1.282 +    delete this._chromeless;
   1.283 +    this._chromeless = chromeless;
   1.284 +    return chromeless;
   1.285 +  },
   1.286 +
   1.287 +  get enabled() {
   1.288 +    // Returns whether social is enabled *for this window*.
   1.289 +    if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
   1.290 +      return false;
   1.291 +    return Social.providers.length > 0;
   1.292 +  },
   1.293 +
   1.294 +  // called on tab/urlbar/location changes and after customization. Update
   1.295 +  // anything that is tab specific.
   1.296 +  updateState: function() {
   1.297 +    if (!this.enabled)
   1.298 +      return;
   1.299 +    SocialMarks.update();
   1.300 +    SocialShare.update();
   1.301 +  }
   1.302 +}
   1.303 +
   1.304 +SocialChatBar = {
   1.305 +  get chatbar() {
   1.306 +    return document.getElementById("pinnedchats");
   1.307 +  },
   1.308 +  // Whether the chatbar is available for this window.  Note that in full-screen
   1.309 +  // mode chats are available, but not shown.
   1.310 +  get isAvailable() {
   1.311 +    return SocialUI.enabled;
   1.312 +  },
   1.313 +  // Does this chatbar have any chats (whether minimized, collapsed or normal)
   1.314 +  get hasChats() {
   1.315 +    return !!this.chatbar.firstElementChild;
   1.316 +  },
   1.317 +  openChat: function(aProvider, aURL, aCallback, aMode) {
   1.318 +    this.update();
   1.319 +    if (!this.isAvailable)
   1.320 +      return false;
   1.321 +    this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
   1.322 +    // We only want to focus the chat if it is as a result of user input.
   1.323 +    let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
   1.324 +                    .getInterface(Ci.nsIDOMWindowUtils);
   1.325 +    if (dwu.isHandlingUserInput)
   1.326 +      this.chatbar.focus();
   1.327 +    return true;
   1.328 +  },
   1.329 +  update: function() {
   1.330 +    let command = document.getElementById("Social:FocusChat");
   1.331 +    if (!this.isAvailable) {
   1.332 +      this.chatbar.hidden = command.hidden = true;
   1.333 +    } else {
   1.334 +      this.chatbar.hidden = command.hidden = false;
   1.335 +    }
   1.336 +    command.setAttribute("disabled", command.hidden ? "true" : "false");
   1.337 +  },
   1.338 +  focus: function SocialChatBar_focus() {
   1.339 +    this.chatbar.focus();
   1.340 +  }
   1.341 +}
   1.342 +
   1.343 +SocialFlyout = {
   1.344 +  get panel() {
   1.345 +    return document.getElementById("social-flyout-panel");
   1.346 +  },
   1.347 +
   1.348 +  get iframe() {
   1.349 +    if (!this.panel.firstChild)
   1.350 +      this._createFrame();
   1.351 +    return this.panel.firstChild;
   1.352 +  },
   1.353 +
   1.354 +  dispatchPanelEvent: function(name) {
   1.355 +    let doc = this.iframe.contentDocument;
   1.356 +    let evt = doc.createEvent("CustomEvent");
   1.357 +    evt.initCustomEvent(name, true, true, {});
   1.358 +    doc.documentElement.dispatchEvent(evt);
   1.359 +  },
   1.360 +
   1.361 +  _createFrame: function() {
   1.362 +    let panel = this.panel;
   1.363 +    if (!SocialUI.enabled || panel.firstChild)
   1.364 +      return;
   1.365 +    // create and initialize the panel for this window
   1.366 +    let iframe = document.createElement("iframe");
   1.367 +    iframe.setAttribute("type", "content");
   1.368 +    iframe.setAttribute("class", "social-panel-frame");
   1.369 +    iframe.setAttribute("flex", "1");
   1.370 +    iframe.setAttribute("tooltip", "aHTMLTooltip");
   1.371 +    iframe.setAttribute("origin", SocialSidebar.provider.origin);
   1.372 +    panel.appendChild(iframe);
   1.373 +  },
   1.374 +
   1.375 +  setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
   1.376 +    this.iframe.removeAttribute("src");
   1.377 +    this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
   1.378 +                                 encodeURIComponent(this.iframe.getAttribute("origin")),
   1.379 +                                 null, null, null, null);
   1.380 +    sizeSocialPanelToContent(this.panel, this.iframe);
   1.381 +  },
   1.382 +
   1.383 +  unload: function() {
   1.384 +    let panel = this.panel;
   1.385 +    panel.hidePopup();
   1.386 +    if (!panel.firstChild)
   1.387 +      return
   1.388 +    let iframe = panel.firstChild;
   1.389 +    if (iframe.socialErrorListener)
   1.390 +      iframe.socialErrorListener.remove();
   1.391 +    panel.removeChild(iframe);
   1.392 +  },
   1.393 +
   1.394 +  onShown: function(aEvent) {
   1.395 +    let panel = this.panel;
   1.396 +    let iframe = this.iframe;
   1.397 +    this._dynamicResizer = new DynamicResizeWatcher();
   1.398 +    iframe.docShell.isActive = true;
   1.399 +    iframe.docShell.isAppTab = true;
   1.400 +    if (iframe.contentDocument.readyState == "complete") {
   1.401 +      this._dynamicResizer.start(panel, iframe);
   1.402 +      this.dispatchPanelEvent("socialFrameShow");
   1.403 +    } else {
   1.404 +      // first time load, wait for load and dispatch after load
   1.405 +      iframe.addEventListener("load", function panelBrowserOnload(e) {
   1.406 +        iframe.removeEventListener("load", panelBrowserOnload, true);
   1.407 +        setTimeout(function() {
   1.408 +          if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
   1.409 +            SocialFlyout._dynamicResizer.start(panel, iframe);
   1.410 +            SocialFlyout.dispatchPanelEvent("socialFrameShow");
   1.411 +          }
   1.412 +        }, 0);
   1.413 +      }, true);
   1.414 +    }
   1.415 +  },
   1.416 +
   1.417 +  onHidden: function(aEvent) {
   1.418 +    this._dynamicResizer.stop();
   1.419 +    this._dynamicResizer = null;
   1.420 +    this.iframe.docShell.isActive = false;
   1.421 +    this.dispatchPanelEvent("socialFrameHide");
   1.422 +  },
   1.423 +
   1.424 +  load: function(aURL, cb) {
   1.425 +    if (!SocialSidebar.provider)
   1.426 +      return;
   1.427 +
   1.428 +    this.panel.hidden = false;
   1.429 +    let iframe = this.iframe;
   1.430 +    // same url with only ref difference does not cause a new load, so we
   1.431 +    // want to go right to the callback
   1.432 +    let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
   1.433 +    if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
   1.434 +      iframe.addEventListener("load", function documentLoaded() {
   1.435 +        iframe.removeEventListener("load", documentLoaded, true);
   1.436 +        cb();
   1.437 +      }, true);
   1.438 +      // Force a layout flush by calling .clientTop so
   1.439 +      // that the docShell of this frame is created
   1.440 +      iframe.clientTop;
   1.441 +      Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
   1.442 +      iframe.setAttribute("src", aURL);
   1.443 +    } else {
   1.444 +      // we still need to set the src to trigger the contents hashchange event
   1.445 +      // for ref changes
   1.446 +      iframe.setAttribute("src", aURL);
   1.447 +      cb();
   1.448 +    }
   1.449 +  },
   1.450 +
   1.451 +  open: function(aURL, yOffset, aCallback) {
   1.452 +    // Hide any other social panels that may be open.
   1.453 +    document.getElementById("social-notification-panel").hidePopup();
   1.454 +
   1.455 +    if (!SocialUI.enabled)
   1.456 +      return;
   1.457 +    let panel = this.panel;
   1.458 +    let iframe = this.iframe;
   1.459 +
   1.460 +    this.load(aURL, function() {
   1.461 +      sizeSocialPanelToContent(panel, iframe);
   1.462 +      let anchor = document.getElementById("social-sidebar-browser");
   1.463 +      if (panel.state == "open") {
   1.464 +        panel.moveToAnchor(anchor, "start_before", 0, yOffset, false);
   1.465 +      } else {
   1.466 +        panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
   1.467 +      }
   1.468 +      if (aCallback) {
   1.469 +        try {
   1.470 +          aCallback(iframe.contentWindow);
   1.471 +        } catch(e) {
   1.472 +          Cu.reportError(e);
   1.473 +        }
   1.474 +      }
   1.475 +    });
   1.476 +  }
   1.477 +}
   1.478 +
   1.479 +SocialShare = {
   1.480 +  get panel() {
   1.481 +    return document.getElementById("social-share-panel");
   1.482 +  },
   1.483 +
   1.484 +  get iframe() {
   1.485 +    // first element is our menu vbox.
   1.486 +    if (this.panel.childElementCount == 1)
   1.487 +      return null;
   1.488 +    else
   1.489 +      return this.panel.lastChild;
   1.490 +  },
   1.491 +
   1.492 +  uninit: function () {
   1.493 +    if (this.iframe) {
   1.494 +      this.iframe.remove();
   1.495 +    }
   1.496 +  },
   1.497 +
   1.498 +  _createFrame: function() {
   1.499 +    let panel = this.panel;
   1.500 +    if (!SocialUI.enabled || this.iframe)
   1.501 +      return;
   1.502 +    this.panel.hidden = false;
   1.503 +    // create and initialize the panel for this window
   1.504 +    let iframe = document.createElement("iframe");
   1.505 +    iframe.setAttribute("type", "content");
   1.506 +    iframe.setAttribute("class", "social-share-frame");
   1.507 +    iframe.setAttribute("context", "contentAreaContextMenu");
   1.508 +    iframe.setAttribute("tooltip", "aHTMLTooltip");
   1.509 +    iframe.setAttribute("flex", "1");
   1.510 +    panel.appendChild(iframe);
   1.511 +    this.populateProviderMenu();
   1.512 +  },
   1.513 +
   1.514 +  getSelectedProvider: function() {
   1.515 +    let provider;
   1.516 +    let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
   1.517 +    if (lastProviderOrigin) {
   1.518 +      provider = Social._getProviderFromOrigin(lastProviderOrigin);
   1.519 +    }
   1.520 +    // if they have a provider selected in the sidebar use that for the initial
   1.521 +    // default in share
   1.522 +    if (!provider)
   1.523 +      provider = SocialSidebar.provider;
   1.524 +    // if our provider has no shareURL, select the first one that does
   1.525 +    if (!provider || !provider.shareURL) {
   1.526 +      let providers = [p for (p of Social.providers) if (p.shareURL)];
   1.527 +      provider = providers.length > 0  && providers[0];
   1.528 +    }
   1.529 +    return provider;
   1.530 +  },
   1.531 +
   1.532 +  populateProviderMenu: function() {
   1.533 +    if (!this.iframe)
   1.534 +      return;
   1.535 +    let providers = [p for (p of Social.providers) if (p.shareURL)];
   1.536 +    let hbox = document.getElementById("social-share-provider-buttons");
   1.537 +    // selectable providers are inserted before the provider-menu seperator,
   1.538 +    // remove any menuitems in that area
   1.539 +    while (hbox.firstChild) {
   1.540 +      hbox.removeChild(hbox.firstChild);
   1.541 +    }
   1.542 +    // reset our share toolbar
   1.543 +    // only show a selection if there is more than one
   1.544 +    if (!SocialUI.enabled || providers.length < 2) {
   1.545 +      this.panel.firstChild.hidden = true;
   1.546 +      return;
   1.547 +    }
   1.548 +    let selectedProvider = this.getSelectedProvider();
   1.549 +    for (let provider of providers) {
   1.550 +      let button = document.createElement("toolbarbutton");
   1.551 +      button.setAttribute("class", "toolbarbutton share-provider-button");
   1.552 +      button.setAttribute("type", "radio");
   1.553 +      button.setAttribute("group", "share-providers");
   1.554 +      button.setAttribute("image", provider.iconURL);
   1.555 +      button.setAttribute("tooltiptext", provider.name);
   1.556 +      button.setAttribute("origin", provider.origin);
   1.557 +      button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin')); this.checked=true;");
   1.558 +      if (provider == selectedProvider) {
   1.559 +        this.defaultButton = button;
   1.560 +      }
   1.561 +      hbox.appendChild(button);
   1.562 +    }
   1.563 +    if (!this.defaultButton) {
   1.564 +      this.defaultButton = hbox.firstChild
   1.565 +    }
   1.566 +    this.defaultButton.setAttribute("checked", "true");
   1.567 +    this.panel.firstChild.hidden = false;
   1.568 +  },
   1.569 +
   1.570 +  get shareButton() {
   1.571 +    return document.getElementById("social-share-button");
   1.572 +  },
   1.573 +
   1.574 +  canSharePage: function(aURI) {
   1.575 +    // we do not enable sharing from private sessions
   1.576 +    if (PrivateBrowsingUtils.isWindowPrivate(window))
   1.577 +      return false;
   1.578 +
   1.579 +    if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')))
   1.580 +      return false;
   1.581 +    return true;
   1.582 +  },
   1.583 +
   1.584 +  update: function() {
   1.585 +    let shareButton = this.shareButton;
   1.586 +    shareButton.hidden = !SocialUI.enabled ||
   1.587 +                         [p for (p of Social.providers) if (p.shareURL)].length == 0;
   1.588 +    shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
   1.589 +
   1.590 +    // also update the relevent command's disabled state so the keyboard
   1.591 +    // shortcut only works when available.
   1.592 +    let cmd = document.getElementById("Social:SharePage");
   1.593 +    if (shareButton.disabled)
   1.594 +      cmd.setAttribute("disabled", "true");
   1.595 +    else
   1.596 +      cmd.removeAttribute("disabled");
   1.597 +  },
   1.598 +
   1.599 +  onShowing: function() {
   1.600 +    this.shareButton.setAttribute("open", "true");
   1.601 +  },
   1.602 +
   1.603 +  onHidden: function() {
   1.604 +    this.shareButton.removeAttribute("open");
   1.605 +    this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
   1.606 +    this.currentShare = null;
   1.607 +  },
   1.608 +
   1.609 +  setErrorMessage: function() {
   1.610 +    let iframe = this.iframe;
   1.611 +    if (!iframe)
   1.612 +      return;
   1.613 +
   1.614 +    iframe.removeAttribute("src");
   1.615 +    iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
   1.616 +                                 encodeURIComponent(iframe.getAttribute("origin")),
   1.617 +                                 null, null, null, null);
   1.618 +    sizeSocialPanelToContent(this.panel, iframe);
   1.619 +  },
   1.620 +
   1.621 +  sharePage: function(providerOrigin, graphData) {
   1.622 +    // if providerOrigin is undefined, we use the last-used provider, or the
   1.623 +    // current/default provider.  The provider selection in the share panel
   1.624 +    // will call sharePage with an origin for us to switch to.
   1.625 +    this._createFrame();
   1.626 +    let iframe = this.iframe;
   1.627 +    let provider;
   1.628 +    if (providerOrigin)
   1.629 +      provider = Social._getProviderFromOrigin(providerOrigin);
   1.630 +    else
   1.631 +      provider = this.getSelectedProvider();
   1.632 +    if (!provider || !provider.shareURL)
   1.633 +      return;
   1.634 +
   1.635 +    // graphData is an optional param that either defines the full set of data
   1.636 +    // to be shared, or partial data about the current page. It is set by a call
   1.637 +    // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
   1.638 +    // define at least url. If it is undefined, we're sharing the current url in
   1.639 +    // the browser tab.
   1.640 +    let sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) :
   1.641 +                                gBrowser.currentURI;
   1.642 +    if (!this.canSharePage(sharedURI))
   1.643 +      return;
   1.644 +
   1.645 +    // the point of this action type is that we can use existing share
   1.646 +    // endpoints (e.g. oexchange) that do not support additional
   1.647 +    // socialapi functionality.  One tweak is that we shoot an event
   1.648 +    // containing the open graph data.
   1.649 +    let pageData = graphData ? graphData : this.currentShare;
   1.650 +    if (!pageData || sharedURI == gBrowser.currentURI) {
   1.651 +      pageData = OpenGraphBuilder.getData(gBrowser);
   1.652 +      if (graphData) {
   1.653 +        // overwrite data retreived from page with data given to us as a param
   1.654 +        for (let p in graphData) {
   1.655 +          pageData[p] = graphData[p];
   1.656 +        }
   1.657 +      }
   1.658 +    }
   1.659 +    this.currentShare = pageData;
   1.660 +
   1.661 +    let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
   1.662 +
   1.663 +    this._dynamicResizer = new DynamicResizeWatcher();
   1.664 +    // if we've already loaded this provider/page share endpoint, we don't want
   1.665 +    // to add another load event listener.
   1.666 +    let reload = true;
   1.667 +    let endpointMatch = shareEndpoint == iframe.getAttribute("src");
   1.668 +    let docLoaded = iframe.contentDocument && iframe.contentDocument.readyState == "complete";
   1.669 +    if (endpointMatch && docLoaded) {
   1.670 +      reload = shareEndpoint != iframe.contentDocument.location.spec;
   1.671 +    }
   1.672 +    if (!reload) {
   1.673 +      this._dynamicResizer.start(this.panel, iframe);
   1.674 +      iframe.docShell.isActive = true;
   1.675 +      iframe.docShell.isAppTab = true;
   1.676 +      let evt = iframe.contentDocument.createEvent("CustomEvent");
   1.677 +      evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
   1.678 +      iframe.contentDocument.documentElement.dispatchEvent(evt);
   1.679 +    } else {
   1.680 +      // first time load, wait for load and dispatch after load
   1.681 +      iframe.addEventListener("load", function panelBrowserOnload(e) {
   1.682 +        iframe.removeEventListener("load", panelBrowserOnload, true);
   1.683 +        iframe.docShell.isActive = true;
   1.684 +        iframe.docShell.isAppTab = true;
   1.685 +        setTimeout(function() {
   1.686 +          if (SocialShare._dynamicResizer) { // may go null if hidden quickly
   1.687 +            SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
   1.688 +          }
   1.689 +        }, 0);
   1.690 +        let evt = iframe.contentDocument.createEvent("CustomEvent");
   1.691 +        evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
   1.692 +        iframe.contentDocument.documentElement.dispatchEvent(evt);
   1.693 +      }, true);
   1.694 +    }
   1.695 +    // always ensure that origin belongs to the endpoint
   1.696 +    let uri = Services.io.newURI(shareEndpoint, null, null);
   1.697 +    iframe.setAttribute("origin", provider.origin);
   1.698 +    iframe.setAttribute("src", shareEndpoint);
   1.699 +
   1.700 +    let navBar = document.getElementById("nav-bar");
   1.701 +    let anchor = document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
   1.702 +    this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
   1.703 +    Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
   1.704 +  }
   1.705 +};
   1.706 +
   1.707 +SocialSidebar = {
   1.708 +  // Whether the sidebar can be shown for this window.
   1.709 +  get canShow() {
   1.710 +    if (!SocialUI.enabled || document.mozFullScreen)
   1.711 +      return false;
   1.712 +    return Social.providers.some(p => p.sidebarURL);
   1.713 +  },
   1.714 +
   1.715 +  // Whether the user has toggled the sidebar on (for windows where it can appear)
   1.716 +  get opened() {
   1.717 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
   1.718 +    return !broadcaster.hidden;
   1.719 +  },
   1.720 +
   1.721 +  restoreWindowState: function() {
   1.722 +    // Window state is used to allow different sidebar providers in each window.
   1.723 +    // We also store the provider used in a pref as the default sidebar to
   1.724 +    // maintain that state for users who do not restore window state. The
   1.725 +    // existence of social.sidebar.provider means the sidebar is open with that
   1.726 +    // provider.
   1.727 +    this._initialized = true;
   1.728 +    if (!this.canShow)
   1.729 +      return;
   1.730 +
   1.731 +    if (Services.prefs.prefHasUserValue("social.provider.current")) {
   1.732 +      // "upgrade" when the first window opens if we have old prefs.  We get the
   1.733 +      // values from prefs this one time, window state will be saved when this
   1.734 +      // window is closed.
   1.735 +      let origin = Services.prefs.getCharPref("social.provider.current");
   1.736 +      Services.prefs.clearUserPref("social.provider.current");
   1.737 +      // social.sidebar.open default was true, but we only opened if there was
   1.738 +      // a current provider
   1.739 +      let opened = origin && true;
   1.740 +      if (Services.prefs.prefHasUserValue("social.sidebar.open")) {
   1.741 +        opened = origin && Services.prefs.getBoolPref("social.sidebar.open");
   1.742 +        Services.prefs.clearUserPref("social.sidebar.open");
   1.743 +      }
   1.744 +      let data = {
   1.745 +        "hidden": !opened,
   1.746 +        "origin": origin
   1.747 +      };
   1.748 +      SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
   1.749 +    }
   1.750 +
   1.751 +    let data = SessionStore.getWindowValue(window, "socialSidebar");
   1.752 +    // if this window doesn't have it's own state, use the state from the opener
   1.753 +    if (!data && window.opener && !window.opener.closed) {
   1.754 +      try {
   1.755 +        data = SessionStore.getWindowValue(window.opener, "socialSidebar");
   1.756 +      } catch(e) {
   1.757 +        // Window is not tracked, which happens on osx if the window is opened
   1.758 +        // from the hidden window. That happens when you close the last window
   1.759 +        // without quiting firefox, then open a new window.
   1.760 +      }
   1.761 +    }
   1.762 +    if (data) {
   1.763 +      data = JSON.parse(data);
   1.764 +      document.getElementById("social-sidebar-browser").setAttribute("origin", data.origin);
   1.765 +      if (!data.hidden)
   1.766 +        this.show(data.origin);
   1.767 +    } else if (Services.prefs.prefHasUserValue("social.sidebar.provider")) {
   1.768 +      // no window state, use the global state if it is available
   1.769 +      this.show(Services.prefs.getCharPref("social.sidebar.provider"));
   1.770 +    }
   1.771 +  },
   1.772 +
   1.773 +  saveWindowState: function() {
   1.774 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
   1.775 +    let sidebarOrigin = document.getElementById("social-sidebar-browser").getAttribute("origin");
   1.776 +    let data = {
   1.777 +      "hidden": broadcaster.hidden,
   1.778 +      "origin": sidebarOrigin
   1.779 +    };
   1.780 +
   1.781 +    // Save a global state for users who do not restore state.
   1.782 +    if (broadcaster.hidden)
   1.783 +      Services.prefs.clearUserPref("social.sidebar.provider");
   1.784 +    else
   1.785 +      Services.prefs.setCharPref("social.sidebar.provider", sidebarOrigin);
   1.786 +
   1.787 +    try {
   1.788 +      SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
   1.789 +    } catch(e) {
   1.790 +      // window not tracked during uninit
   1.791 +    }
   1.792 +  },
   1.793 +
   1.794 +  setSidebarVisibilityState: function(aEnabled) {
   1.795 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.796 +    // it's possible we'll be called twice with aEnabled=false so let's
   1.797 +    // just assume we may often be called with the same state.
   1.798 +    if (aEnabled == sbrowser.docShellIsActive)
   1.799 +      return;
   1.800 +    sbrowser.docShellIsActive = aEnabled;
   1.801 +    let evt = sbrowser.contentDocument.createEvent("CustomEvent");
   1.802 +    evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
   1.803 +    sbrowser.contentDocument.documentElement.dispatchEvent(evt);
   1.804 +  },
   1.805 +
   1.806 +  updateToggleNotifications: function() {
   1.807 +    let command = document.getElementById("Social:ToggleNotifications");
   1.808 +    command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
   1.809 +    command.setAttribute("hidden", !SocialUI.enabled);
   1.810 +  },
   1.811 +
   1.812 +  update: function SocialSidebar_update() {
   1.813 +    // ensure we never update before restoreWindowState
   1.814 +    if (!this._initialized)
   1.815 +      return;
   1.816 +    this.ensureProvider();
   1.817 +    this.updateToggleNotifications();
   1.818 +    this._updateHeader();
   1.819 +    clearTimeout(this._unloadTimeoutId);
   1.820 +    // Hide the toggle menu item if the sidebar cannot appear
   1.821 +    let command = document.getElementById("Social:ToggleSidebar");
   1.822 +    command.setAttribute("hidden", this.canShow ? "false" : "true");
   1.823 +
   1.824 +    // Hide the sidebar if it cannot appear, or has been toggled off.
   1.825 +    // Also set the command "checked" state accordingly.
   1.826 +    let hideSidebar = !this.canShow || !this.opened;
   1.827 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
   1.828 +    broadcaster.hidden = hideSidebar;
   1.829 +    command.setAttribute("checked", !hideSidebar);
   1.830 +
   1.831 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.832 +
   1.833 +    if (hideSidebar) {
   1.834 +      sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
   1.835 +      this.setSidebarVisibilityState(false);
   1.836 +      // If we've been disabled, unload the sidebar content immediately;
   1.837 +      // if the sidebar was just toggled to invisible, wait a timeout
   1.838 +      // before unloading.
   1.839 +      if (!this.canShow) {
   1.840 +        this.unloadSidebar();
   1.841 +      } else {
   1.842 +        this._unloadTimeoutId = setTimeout(
   1.843 +          this.unloadSidebar,
   1.844 +          Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
   1.845 +        );
   1.846 +      }
   1.847 +    } else {
   1.848 +      sbrowser.setAttribute("origin", this.provider.origin);
   1.849 +      if (this.provider.errorState == "frameworker-error") {
   1.850 +        SocialSidebar.setSidebarErrorMessage();
   1.851 +        return;
   1.852 +      }
   1.853 +
   1.854 +      // Make sure the right sidebar URL is loaded
   1.855 +      if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
   1.856 +        // we check readyState right after setting src, we need a new content
   1.857 +        // viewer to ensure we are checking against the correct document.
   1.858 +        sbrowser.docShell.createAboutBlankContentViewer(null);
   1.859 +        Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
   1.860 +        // setting isAppTab causes clicks on untargeted links to open new tabs
   1.861 +        sbrowser.docShell.isAppTab = true;
   1.862 +        sbrowser.setAttribute("src", this.provider.sidebarURL);
   1.863 +        PopupNotifications.locationChange(sbrowser);
   1.864 +      }
   1.865 +
   1.866 +      // if the document has not loaded, delay until it is
   1.867 +      if (sbrowser.contentDocument.readyState != "complete") {
   1.868 +        document.getElementById("social-sidebar-button").setAttribute("loading", "true");
   1.869 +        sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
   1.870 +      } else {
   1.871 +        this.setSidebarVisibilityState(true);
   1.872 +      }
   1.873 +    }
   1.874 +    this._updateCheckedMenuItems(this.opened && this.provider ? this.provider.origin : null);
   1.875 +  },
   1.876 +
   1.877 +  _loadListener: function SocialSidebar_loadListener() {
   1.878 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.879 +    sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
   1.880 +    document.getElementById("social-sidebar-button").removeAttribute("loading");
   1.881 +    SocialSidebar.setSidebarVisibilityState(true);
   1.882 +  },
   1.883 +
   1.884 +  unloadSidebar: function SocialSidebar_unloadSidebar() {
   1.885 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.886 +    if (!sbrowser.hasAttribute("origin"))
   1.887 +      return;
   1.888 +
   1.889 +    sbrowser.stop();
   1.890 +    sbrowser.removeAttribute("origin");
   1.891 +    sbrowser.setAttribute("src", "about:blank");
   1.892 +    // We need to explicitly create a new content viewer because the old one
   1.893 +    // doesn't get destroyed until about:blank has loaded (which does not happen
   1.894 +    // as long as the element is hidden).
   1.895 +    sbrowser.docShell.createAboutBlankContentViewer(null);
   1.896 +    SocialFlyout.unload();
   1.897 +  },
   1.898 +
   1.899 +  _unloadTimeoutId: 0,
   1.900 +
   1.901 +  setSidebarErrorMessage: function() {
   1.902 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.903 +    // a frameworker error "trumps" a sidebar error.
   1.904 +    let origin = sbrowser.getAttribute("origin");
   1.905 +    if (origin) {
   1.906 +      origin = "&origin=" + encodeURIComponent(origin);
   1.907 +    }
   1.908 +    if (this.provider.errorState == "frameworker-error") {
   1.909 +      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure" + origin);
   1.910 +    } else {
   1.911 +      let url = encodeURIComponent(this.provider.sidebarURL);
   1.912 +      sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url + origin, null, null);
   1.913 +    }
   1.914 +  },
   1.915 +
   1.916 +  _provider: null,
   1.917 +  ensureProvider: function() {
   1.918 +    if (this._provider)
   1.919 +      return;
   1.920 +    // origin for sidebar is persisted, so get the previously selected sidebar
   1.921 +    // first, otherwise fallback to the first provider in the list
   1.922 +    let sbrowser = document.getElementById("social-sidebar-browser");
   1.923 +    let origin = sbrowser.getAttribute("origin");
   1.924 +    let providers = [p for (p of Social.providers) if (p.sidebarURL)];
   1.925 +    let provider;
   1.926 +    if (origin)
   1.927 +      provider = Social._getProviderFromOrigin(origin);
   1.928 +    if (!provider && providers.length > 0)
   1.929 +      provider = providers[0];
   1.930 +    if (provider)
   1.931 +      this.provider = provider;
   1.932 +  },
   1.933 +
   1.934 +  get provider() {
   1.935 +    return this._provider;
   1.936 +  },
   1.937 +
   1.938 +  set provider(provider) {
   1.939 +    if (!provider || provider.sidebarURL) {
   1.940 +      this._provider = provider;
   1.941 +      this._updateHeader();
   1.942 +      this._updateCheckedMenuItems(provider && provider.origin);
   1.943 +      this.update();
   1.944 +    }
   1.945 +  },
   1.946 +
   1.947 +  disableProvider: function(origin) {
   1.948 +    if (this._provider && this._provider.origin == origin) {
   1.949 +      this._provider = null;
   1.950 +      // force a selection of the next provider if there is one
   1.951 +      this.ensureProvider();
   1.952 +    }
   1.953 +  },
   1.954 +
   1.955 +  _updateHeader: function() {
   1.956 +    let provider = this.provider;
   1.957 +    let image, title;
   1.958 +    if (provider) {
   1.959 +      image = "url(" + (provider.icon32URL || provider.iconURL) + ")";
   1.960 +      title = provider.name;
   1.961 +    }
   1.962 +    document.getElementById("social-sidebar-favico").style.listStyleImage = image;
   1.963 +    document.getElementById("social-sidebar-title").value = title;
   1.964 +  },
   1.965 +
   1.966 +  _updateCheckedMenuItems: function(origin) {
   1.967 +    // update selected menuitems
   1.968 +    let menuitems = document.getElementsByClassName("social-provider-menuitem");
   1.969 +    for (let mi of menuitems) {
   1.970 +      if (origin && mi.getAttribute("origin") == origin) {
   1.971 +        mi.setAttribute("checked", "true");
   1.972 +        mi.setAttribute("oncommand", "SocialSidebar.hide();");
   1.973 +      } else if (mi.getAttribute("checked")) {
   1.974 +        mi.removeAttribute("checked");
   1.975 +        mi.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
   1.976 +      }
   1.977 +    }
   1.978 +  },
   1.979 +
   1.980 +  show: function(origin) {
   1.981 +    // always show the sidebar, and set the provider
   1.982 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
   1.983 +    broadcaster.hidden = false;
   1.984 +    if (origin)
   1.985 +      this.provider = Social._getProviderFromOrigin(origin);
   1.986 +    else
   1.987 +      SocialSidebar.update();
   1.988 +    this.saveWindowState();
   1.989 +  },
   1.990 +
   1.991 +  hide: function() {
   1.992 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
   1.993 +    broadcaster.hidden = true;
   1.994 +    this._updateCheckedMenuItems();
   1.995 +    this.clearProviderMenus();
   1.996 +    SocialSidebar.update();
   1.997 +    this.saveWindowState();
   1.998 +  },
   1.999 +
  1.1000 +  toggleSidebar: function SocialSidebar_toggle() {
  1.1001 +    let broadcaster = document.getElementById("socialSidebarBroadcaster");
  1.1002 +    if (broadcaster.hidden)
  1.1003 +      this.show();
  1.1004 +    else
  1.1005 +      this.hide();
  1.1006 +  },
  1.1007 +
  1.1008 +  populateSidebarMenu: function(event) {
  1.1009 +    // Providers are removed from the view->sidebar menu when there is a change
  1.1010 +    // in providers, so we only have to populate onshowing if there are no
  1.1011 +    // provider menus. We populate this menu so long as there are enabled
  1.1012 +    // providers with sidebars.
  1.1013 +    let popup = event.target;
  1.1014 +    let providerMenuSeps = popup.getElementsByClassName("social-provider-menu");
  1.1015 +    if (providerMenuSeps[0].previousSibling.nodeName == "menuseparator")
  1.1016 +      SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
  1.1017 +  },
  1.1018 +
  1.1019 +  clearProviderMenus: function() {
  1.1020 +    // called when there is a change in the provider list we clear all menus,
  1.1021 +    // they will be repopulated when the menu is shown
  1.1022 +    let providerMenuSeps = document.getElementsByClassName("social-provider-menu");
  1.1023 +    for (let providerMenuSep of providerMenuSeps) {
  1.1024 +      while (providerMenuSep.previousSibling.nodeName == "menuitem") {
  1.1025 +        let menu = providerMenuSep.parentNode;
  1.1026 +        menu.removeChild(providerMenuSep.previousSibling);
  1.1027 +      }
  1.1028 +    }
  1.1029 +  },
  1.1030 +
  1.1031 +  populateProviderMenu: function(providerMenuSep) {
  1.1032 +    let menu = providerMenuSep.parentNode;
  1.1033 +    // selectable providers are inserted before the provider-menu seperator,
  1.1034 +    // remove any menuitems in that area
  1.1035 +    while (providerMenuSep.previousSibling.nodeName == "menuitem") {
  1.1036 +      menu.removeChild(providerMenuSep.previousSibling);
  1.1037 +    }
  1.1038 +    // only show a selection in the sidebar header menu if there is more than one
  1.1039 +    let providers = [p for (p of Social.providers) if (p.sidebarURL)];
  1.1040 +    if (providers.length < 2 && menu.id != "viewSidebarMenu") {
  1.1041 +      providerMenuSep.hidden = true;
  1.1042 +      return;
  1.1043 +    }
  1.1044 +    let topSep = providerMenuSep.previousSibling;
  1.1045 +    for (let provider of providers) {
  1.1046 +      let menuitem = document.createElement("menuitem");
  1.1047 +      menuitem.className = "menuitem-iconic social-provider-menuitem";
  1.1048 +      menuitem.setAttribute("image", provider.iconURL);
  1.1049 +      menuitem.setAttribute("label", provider.name);
  1.1050 +      menuitem.setAttribute("origin", provider.origin);
  1.1051 +      if (this.opened && provider == this.provider) {
  1.1052 +        menuitem.setAttribute("checked", "true");
  1.1053 +        menuitem.setAttribute("oncommand", "SocialSidebar.hide();");
  1.1054 +      } else {
  1.1055 +        menuitem.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
  1.1056 +      }
  1.1057 +      menu.insertBefore(menuitem, providerMenuSep);
  1.1058 +    }
  1.1059 +    topSep.hidden = topSep.nextSibling == providerMenuSep;
  1.1060 +    providerMenuSep.hidden = !providerMenuSep.nextSibling;
  1.1061 +  }
  1.1062 +}
  1.1063 +
  1.1064 +// this helper class is used by removable/customizable buttons to handle
  1.1065 +// widget creation/destruction
  1.1066 +
  1.1067 +// When a provider is installed we show all their UI so the user will see the
  1.1068 +// functionality of what they installed. The user can later customize the UI,
  1.1069 +// moving buttons around or off the toolbar.
  1.1070 +//
  1.1071 +// On startup, we create the button widgets of any enabled provider.
  1.1072 +// CustomizableUI handles placement and persistence of placement.
  1.1073 +function ToolbarHelper(type, createButtonFn, listener) {
  1.1074 +  this._createButton = createButtonFn;
  1.1075 +  this._type = type;
  1.1076 +
  1.1077 +  if (listener) {
  1.1078 +    CustomizableUI.addListener(listener);
  1.1079 +    // remove this listener on window close
  1.1080 +    window.addEventListener("unload", () => {
  1.1081 +      CustomizableUI.removeListener(listener);
  1.1082 +    });
  1.1083 +  }
  1.1084 +}
  1.1085 +
  1.1086 +ToolbarHelper.prototype = {
  1.1087 +  idFromOrigin: function(origin) {
  1.1088 +    // this id needs to pass the checks in CustomizableUI, so remove characters
  1.1089 +    // that wont pass.
  1.1090 +    return this._type + "-" + Services.io.newURI(origin, null, null).hostPort.replace(/[\.:]/g,'-');
  1.1091 +  },
  1.1092 +
  1.1093 +  // should be called on disable of a provider
  1.1094 +  removeProviderButton: function(origin) {
  1.1095 +    CustomizableUI.destroyWidget(this.idFromOrigin(origin));
  1.1096 +  },
  1.1097 +
  1.1098 +  clearPalette: function() {
  1.1099 +    [this.removeProviderButton(p.origin) for (p of Social.providers)];
  1.1100 +  },
  1.1101 +
  1.1102 +  // should be called on enable of a provider
  1.1103 +  populatePalette: function() {
  1.1104 +    if (!Social.enabled) {
  1.1105 +      this.clearPalette();
  1.1106 +      return;
  1.1107 +    }
  1.1108 +
  1.1109 +    // create any buttons that do not exist yet if they have been persisted
  1.1110 +    // as a part of the UI (otherwise they belong in the palette).
  1.1111 +    for (let provider of Social.providers) {
  1.1112 +      let id = this.idFromOrigin(provider.origin);
  1.1113 +      this._createButton(id, provider);
  1.1114 +    }
  1.1115 +  }
  1.1116 +}
  1.1117 +
  1.1118 +let SocialStatusWidgetListener = {
  1.1119 +  _getNodeOrigin: function(aWidgetId) {
  1.1120 +    // we rely on the button id being the same as the widget.
  1.1121 +    let node = document.getElementById(aWidgetId);
  1.1122 +    if (!node)
  1.1123 +      return null
  1.1124 +    if (!node.classList.contains("social-status-button"))
  1.1125 +      return null
  1.1126 +    return node.getAttribute("origin");
  1.1127 +  },
  1.1128 +  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
  1.1129 +    let origin = this._getNodeOrigin(aWidgetId);
  1.1130 +    if (origin)
  1.1131 +      SocialStatus.updateButton(origin);
  1.1132 +  },
  1.1133 +  onWidgetRemoved: function(aWidgetId, aPrevArea) {
  1.1134 +    let origin = this._getNodeOrigin(aWidgetId);
  1.1135 +    if (!origin)
  1.1136 +      return;
  1.1137 +    // When a widget is demoted to the palette ('removed'), it's visual
  1.1138 +    // style should change.
  1.1139 +    SocialStatus.updateButton(origin);
  1.1140 +    SocialStatus._removeFrame(origin);
  1.1141 +  }
  1.1142 +}
  1.1143 +
  1.1144 +SocialStatus = {
  1.1145 +  populateToolbarPalette: function() {
  1.1146 +    this._toolbarHelper.populatePalette();
  1.1147 +
  1.1148 +    for (let provider of Social.providers)
  1.1149 +      this.updateButton(provider.origin);
  1.1150 +  },
  1.1151 +
  1.1152 +  removeProvider: function(origin) {
  1.1153 +    this._removeFrame(origin);
  1.1154 +    this._toolbarHelper.removeProviderButton(origin);
  1.1155 +  },
  1.1156 +
  1.1157 +  reloadProvider: function(origin) {
  1.1158 +    let button = document.getElementById(this._toolbarHelper.idFromOrigin(origin));
  1.1159 +    if (button && button.getAttribute("open") == "true")
  1.1160 +      document.getElementById("social-notification-panel").hidePopup();
  1.1161 +    this._removeFrame(origin);
  1.1162 +  },
  1.1163 +
  1.1164 +  _removeFrame: function(origin) {
  1.1165 +    let notificationFrameId = "social-status-" + origin;
  1.1166 +    let frame = document.getElementById(notificationFrameId);
  1.1167 +    if (frame) {
  1.1168 +      SharedFrame.forgetGroup(frame.id);
  1.1169 +      frame.parentNode.removeChild(frame);
  1.1170 +    }
  1.1171 +  },
  1.1172 +
  1.1173 +  get _toolbarHelper() {
  1.1174 +    delete this._toolbarHelper;
  1.1175 +    this._toolbarHelper = new ToolbarHelper("social-status-button",
  1.1176 +                                            CreateSocialStatusWidget,
  1.1177 +                                            SocialStatusWidgetListener);
  1.1178 +    return this._toolbarHelper;
  1.1179 +  },
  1.1180 +
  1.1181 +  get _dynamicResizer() {
  1.1182 +    delete this._dynamicResizer;
  1.1183 +    this._dynamicResizer = new DynamicResizeWatcher();
  1.1184 +    return this._dynamicResizer;
  1.1185 +  },
  1.1186 +
  1.1187 +  // status panels are one-per button per-process, we swap the docshells between
  1.1188 +  // windows when necessary
  1.1189 +  _attachNotificatonPanel: function(aParent, aButton, provider) {
  1.1190 +    aParent.hidden = !SocialUI.enabled;
  1.1191 +    let notificationFrameId = "social-status-" + provider.origin;
  1.1192 +    let frame = document.getElementById(notificationFrameId);
  1.1193 +
  1.1194 +    // If the button was customized to a new location, we we'll destroy the
  1.1195 +    // iframe and start fresh.
  1.1196 +    if (frame && frame.parentNode != aParent) {
  1.1197 +      SharedFrame.forgetGroup(frame.id);
  1.1198 +      frame.parentNode.removeChild(frame);
  1.1199 +      frame = null;
  1.1200 +    }
  1.1201 +
  1.1202 +    if (!frame) {
  1.1203 +      frame = SharedFrame.createFrame(
  1.1204 +        notificationFrameId, /* frame name */
  1.1205 +        aParent, /* parent */
  1.1206 +        {
  1.1207 +          "type": "content",
  1.1208 +          "mozbrowser": "true",
  1.1209 +          "class": "social-panel-frame",
  1.1210 +          "id": notificationFrameId,
  1.1211 +          "tooltip": "aHTMLTooltip",
  1.1212 +          "context": "contentAreaContextMenu",
  1.1213 +          "flex": "1",
  1.1214 +
  1.1215 +          // work around bug 793057 - by making the panel roughly the final size
  1.1216 +          // we are more likely to have the anchor in the correct position.
  1.1217 +          "style": "width: " + PANEL_MIN_WIDTH + "px;",
  1.1218 +
  1.1219 +          "origin": provider.origin,
  1.1220 +          "src": provider.statusURL
  1.1221 +        }
  1.1222 +      );
  1.1223 +
  1.1224 +      if (frame.socialErrorListener)
  1.1225 +        frame.socialErrorListener.remove();
  1.1226 +      if (frame.docShell) {
  1.1227 +        frame.docShell.isActive = false;
  1.1228 +        Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
  1.1229 +      }
  1.1230 +    } else {
  1.1231 +      frame.setAttribute("origin", provider.origin);
  1.1232 +      SharedFrame.updateURL(notificationFrameId, provider.statusURL);
  1.1233 +    }
  1.1234 +    aButton.setAttribute("notificationFrameId", notificationFrameId);
  1.1235 +  },
  1.1236 +
  1.1237 +  updateButton: function(origin) {
  1.1238 +    let id = this._toolbarHelper.idFromOrigin(origin);
  1.1239 +    let widget = CustomizableUI.getWidget(id);
  1.1240 +    if (!widget)
  1.1241 +      return;
  1.1242 +    let button = widget.forWindow(window).node;
  1.1243 +    if (button) {
  1.1244 +      // we only grab the first notification, ignore all others
  1.1245 +      let place = CustomizableUI.getPlaceForItem(button);
  1.1246 +      let provider = Social._getProviderFromOrigin(origin);
  1.1247 +      let icons = provider.ambientNotificationIcons;
  1.1248 +      let iconNames = Object.keys(icons);
  1.1249 +      let notif = icons[iconNames[0]];
  1.1250 +
  1.1251 +      // The image and tooltip need to be updated for both
  1.1252 +      // ambient notification and profile changes.
  1.1253 +      let iconURL = provider.icon32URL || provider.iconURL;
  1.1254 +      let tooltiptext;
  1.1255 +      if (!notif || place == "palette") {
  1.1256 +        button.style.listStyleImage = "url(" + iconURL + ")";
  1.1257 +        button.setAttribute("badge", "");
  1.1258 +        button.setAttribute("aria-label", "");
  1.1259 +        button.setAttribute("tooltiptext", provider.name);
  1.1260 +        return;
  1.1261 +      }
  1.1262 +      button.style.listStyleImage = "url(" + (notif.iconURL || iconURL) + ")";
  1.1263 +      button.setAttribute("tooltiptext", notif.label || provider.name);
  1.1264 +
  1.1265 +      let badge = notif.counter || "";
  1.1266 +      button.setAttribute("badge", badge);
  1.1267 +      let ariaLabel = notif.label;
  1.1268 +      // if there is a badge value, we must use a localizable string to insert it.
  1.1269 +      if (badge)
  1.1270 +        ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
  1.1271 +                                                        [ariaLabel, badge]);
  1.1272 +      button.setAttribute("aria-label", ariaLabel);
  1.1273 +    }
  1.1274 +  },
  1.1275 +
  1.1276 +  showPopup: function(aToolbarButton) {
  1.1277 +    // attach our notification panel if necessary
  1.1278 +    let origin = aToolbarButton.getAttribute("origin");
  1.1279 +    let provider = Social._getProviderFromOrigin(origin);
  1.1280 +
  1.1281 +    // if we're a slice in the hamburger, use that panel instead
  1.1282 +    let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
  1.1283 +    let widget = widgetGroup.forWindow(window);
  1.1284 +    let panel, showingEvent, hidingEvent;
  1.1285 +    let inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
  1.1286 +    if (inMenuPanel) {
  1.1287 +      panel = document.getElementById("PanelUI-socialapi");
  1.1288 +      this._attachNotificatonPanel(panel, aToolbarButton, provider);
  1.1289 +      widget.node.setAttribute("closemenu", "none");
  1.1290 +      showingEvent = "ViewShowing";
  1.1291 +      hidingEvent = "ViewHiding";
  1.1292 +    } else {
  1.1293 +      panel = document.getElementById("social-notification-panel");
  1.1294 +      this._attachNotificatonPanel(panel, aToolbarButton, provider);
  1.1295 +      showingEvent = "popupshown";
  1.1296 +      hidingEvent = "popuphidden";
  1.1297 +    }
  1.1298 +    let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
  1.1299 +    let notificationFrame = document.getElementById(notificationFrameId);
  1.1300 +
  1.1301 +    let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
  1.1302 +    SharedFrame.setOwner(notificationFrameId, notificationFrame);
  1.1303 +
  1.1304 +    // Clear dimensions on all browsers so the panel size will
  1.1305 +    // only use the selected browser.
  1.1306 +    let frameIter = panel.firstElementChild;
  1.1307 +    while (frameIter) {
  1.1308 +      frameIter.collapsed = (frameIter != notificationFrame);
  1.1309 +      frameIter = frameIter.nextElementSibling;
  1.1310 +    }
  1.1311 +
  1.1312 +    function dispatchPanelEvent(name) {
  1.1313 +      let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
  1.1314 +      evt.initCustomEvent(name, true, true, {});
  1.1315 +      notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
  1.1316 +    }
  1.1317 +
  1.1318 +    // we only use a dynamic resizer when we're located the toolbar.
  1.1319 +    let dynamicResizer = inMenuPanel ? null : this._dynamicResizer;
  1.1320 +    panel.addEventListener(hidingEvent, function onpopuphiding() {
  1.1321 +      panel.removeEventListener(hidingEvent, onpopuphiding);
  1.1322 +      aToolbarButton.removeAttribute("open");
  1.1323 +      if (dynamicResizer)
  1.1324 +        dynamicResizer.stop();
  1.1325 +      notificationFrame.docShell.isActive = false;
  1.1326 +      dispatchPanelEvent("socialFrameHide");
  1.1327 +    });
  1.1328 +
  1.1329 +    panel.addEventListener(showingEvent, function onpopupshown() {
  1.1330 +      panel.removeEventListener(showingEvent, onpopupshown);
  1.1331 +      // This attribute is needed on both the button and the
  1.1332 +      // containing toolbaritem since the buttons on OS X have
  1.1333 +      // moz-appearance:none, while their container gets
  1.1334 +      // moz-appearance:toolbarbutton due to the way that toolbar buttons
  1.1335 +      // get combined on OS X.
  1.1336 +      let initFrameShow = () => {
  1.1337 +        notificationFrame.docShell.isActive = true;
  1.1338 +        notificationFrame.docShell.isAppTab = true;
  1.1339 +        if (dynamicResizer)
  1.1340 +          dynamicResizer.start(panel, notificationFrame);
  1.1341 +        dispatchPanelEvent("socialFrameShow");
  1.1342 +      };
  1.1343 +      if (!inMenuPanel)
  1.1344 +        aToolbarButton.setAttribute("open", "true");
  1.1345 +      if (notificationFrame.contentDocument &&
  1.1346 +          notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
  1.1347 +        initFrameShow();
  1.1348 +      } else {
  1.1349 +        // first time load, wait for load and dispatch after load
  1.1350 +        notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
  1.1351 +          notificationFrame.removeEventListener("load", panelBrowserOnload, true);
  1.1352 +          initFrameShow();
  1.1353 +        }, true);
  1.1354 +      }
  1.1355 +    });
  1.1356 +
  1.1357 +    if (inMenuPanel) {
  1.1358 +      PanelUI.showSubView("PanelUI-socialapi", widget.node,
  1.1359 +                          CustomizableUI.AREA_PANEL);
  1.1360 +    } else {
  1.1361 +      let anchor = document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
  1.1362 +      // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
  1.1363 +      // handling from preventing it being opened in some cases.
  1.1364 +      setTimeout(function() {
  1.1365 +        panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
  1.1366 +      }, 0);
  1.1367 +    }
  1.1368 +  },
  1.1369 +
  1.1370 +  setPanelErrorMessage: function(aNotificationFrame) {
  1.1371 +    if (!aNotificationFrame)
  1.1372 +      return;
  1.1373 +
  1.1374 +    let src = aNotificationFrame.getAttribute("src");
  1.1375 +    aNotificationFrame.removeAttribute("src");
  1.1376 +    let origin = aNotificationFrame.getAttribute("origin");
  1.1377 +    aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
  1.1378 +                                            encodeURIComponent(src) + "&origin=" +
  1.1379 +                                            encodeURIComponent(origin),
  1.1380 +                                            null, null, null, null);
  1.1381 +    let panel = aNotificationFrame.parentNode;
  1.1382 +    sizeSocialPanelToContent(panel, aNotificationFrame);
  1.1383 +  },
  1.1384 +
  1.1385 +};
  1.1386 +
  1.1387 +
  1.1388 +/**
  1.1389 + * SocialMarks
  1.1390 + *
  1.1391 + * Handles updates to toolbox and signals all buttons to update when necessary.
  1.1392 + */
  1.1393 +SocialMarks = {
  1.1394 +  update: function() {
  1.1395 +    // signal each button to update itself
  1.1396 +    let currentButtons = document.querySelectorAll('toolbarbutton[type="socialmark"]');
  1.1397 +    for (let elt of currentButtons)
  1.1398 +      elt.update();
  1.1399 +  },
  1.1400 +
  1.1401 +  updatePanelButtons: function() {
  1.1402 +    // querySelectorAll does not work on the menu panel the panel, so we have to
  1.1403 +    // do this the hard way.
  1.1404 +    let providers = SocialMarks.getProviders();
  1.1405 +    let panel =  document.getElementById("PanelUI-popup");
  1.1406 +    for (let p of providers) {
  1.1407 +      let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
  1.1408 +      let widget = CustomizableUI.getWidget(widgetId);
  1.1409 +      if (!widget)
  1.1410 +        continue;
  1.1411 +      let node = widget.forWindow(window).node;
  1.1412 +      if (node)
  1.1413 +        node.update();
  1.1414 +    }
  1.1415 +  },
  1.1416 +
  1.1417 +  getProviders: function() {
  1.1418 +    // only rely on providers that the user has placed in the UI somewhere. This
  1.1419 +    // also means that populateToolbarPalette must be called prior to using this
  1.1420 +    // method, otherwise you get a big fat zero. For our use case with context
  1.1421 +    // menu's, this is ok.
  1.1422 +    let tbh = this._toolbarHelper;
  1.1423 +    return [p for (p of Social.providers) if (p.markURL &&
  1.1424 +                                              document.getElementById(tbh.idFromOrigin(p.origin)))];
  1.1425 +  },
  1.1426 +
  1.1427 +  populateContextMenu: function() {
  1.1428 +    // only show a selection if enabled and there is more than one
  1.1429 +    let providers = this.getProviders();
  1.1430 +
  1.1431 +    // remove all previous entries by class
  1.1432 +    let menus = [m for (m of document.getElementsByClassName("context-socialmarks"))];
  1.1433 +    [m.parentNode.removeChild(m) for (m of menus)];
  1.1434 +
  1.1435 +    let contextMenus = [
  1.1436 +      {
  1.1437 +        type: "link",
  1.1438 +        id: "context-marklinkMenu",
  1.1439 +        label: "social.marklinkMenu.label"
  1.1440 +      },
  1.1441 +      {
  1.1442 +        type: "page",
  1.1443 +        id: "context-markpageMenu",
  1.1444 +        label: "social.markpageMenu.label"
  1.1445 +      }
  1.1446 +    ];
  1.1447 +    for (let cfg of contextMenus) {
  1.1448 +      this._populateContextPopup(cfg, providers);
  1.1449 +    }
  1.1450 +    this.updatePanelButtons();
  1.1451 +  },
  1.1452 +
  1.1453 +  MENU_LIMIT: 3, // adjustable for testing
  1.1454 +  _populateContextPopup: function(menuInfo, providers) {
  1.1455 +    let menu = document.getElementById(menuInfo.id);
  1.1456 +    let popup = menu.firstChild;
  1.1457 +    for (let provider of providers) {
  1.1458 +      // We show up to MENU_LIMIT providers as single menuitems's at the top
  1.1459 +      // level of the context menu, if we have more than that, dump them *all*
  1.1460 +      // into the menu popup.
  1.1461 +      let mi = document.createElement("menuitem");
  1.1462 +      mi.setAttribute("oncommand", "gContextMenu.markLink(this.getAttribute('origin'));");
  1.1463 +      mi.setAttribute("origin", provider.origin);
  1.1464 +      mi.setAttribute("image", provider.iconURL);
  1.1465 +      if (providers.length <= this.MENU_LIMIT) {
  1.1466 +        // an extra class to make enable/disable easy
  1.1467 +        mi.setAttribute("class", "menuitem-iconic context-socialmarks context-mark"+menuInfo.type);
  1.1468 +        let menuLabel = gNavigatorBundle.getFormattedString(menuInfo.label, [provider.name]);
  1.1469 +        mi.setAttribute("label", menuLabel);
  1.1470 +        menu.parentNode.insertBefore(mi, menu);
  1.1471 +      } else {
  1.1472 +        mi.setAttribute("class", "menuitem-iconic context-socialmarks");
  1.1473 +        mi.setAttribute("label", provider.name);
  1.1474 +        popup.appendChild(mi);
  1.1475 +      }
  1.1476 +    }
  1.1477 +  },
  1.1478 +
  1.1479 +  populateToolbarPalette: function() {
  1.1480 +    this._toolbarHelper.populatePalette();
  1.1481 +    this.populateContextMenu();
  1.1482 +  },
  1.1483 +
  1.1484 +  removeProvider: function(origin) {
  1.1485 +    this._toolbarHelper.removeProviderButton(origin);
  1.1486 +  },
  1.1487 +
  1.1488 +  get _toolbarHelper() {
  1.1489 +    delete this._toolbarHelper;
  1.1490 +    this._toolbarHelper = new ToolbarHelper("social-mark-button", CreateSocialMarkWidget);
  1.1491 +    return this._toolbarHelper;
  1.1492 +  },
  1.1493 +
  1.1494 +  markLink: function(aOrigin, aUrl) {
  1.1495 +    // find the button for this provider, and open it
  1.1496 +    let id = this._toolbarHelper.idFromOrigin(aOrigin);
  1.1497 +    document.getElementById(id).markLink(aUrl);
  1.1498 +  }
  1.1499 +};
  1.1500 +
  1.1501 +})();

mercurial