browser/base/content/browser-social.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 // This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 // License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 4
michael@0 5 // the "exported" symbols
michael@0 6 let SocialUI,
michael@0 7 SocialChatBar,
michael@0 8 SocialFlyout,
michael@0 9 SocialMarks,
michael@0 10 SocialShare,
michael@0 11 SocialSidebar,
michael@0 12 SocialStatus;
michael@0 13
michael@0 14 (function() {
michael@0 15
michael@0 16 // The minimum sizes for the auto-resize panel code.
michael@0 17 const PANEL_MIN_HEIGHT = 100;
michael@0 18 const PANEL_MIN_WIDTH = 330;
michael@0 19
michael@0 20 XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
michael@0 21 "resource:///modules/SharedFrame.jsm");
michael@0 22
michael@0 23 XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
michael@0 24 let tmp = {};
michael@0 25 Cu.import("resource:///modules/Social.jsm", tmp);
michael@0 26 return tmp.OpenGraphBuilder;
michael@0 27 });
michael@0 28
michael@0 29 XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
michael@0 30 let tmp = {};
michael@0 31 Cu.import("resource:///modules/Social.jsm", tmp);
michael@0 32 return tmp.DynamicResizeWatcher;
michael@0 33 });
michael@0 34
michael@0 35 XPCOMUtils.defineLazyGetter(this, "sizeSocialPanelToContent", function() {
michael@0 36 let tmp = {};
michael@0 37 Cu.import("resource:///modules/Social.jsm", tmp);
michael@0 38 return tmp.sizeSocialPanelToContent;
michael@0 39 });
michael@0 40
michael@0 41 XPCOMUtils.defineLazyGetter(this, "CreateSocialStatusWidget", function() {
michael@0 42 let tmp = {};
michael@0 43 Cu.import("resource:///modules/Social.jsm", tmp);
michael@0 44 return tmp.CreateSocialStatusWidget;
michael@0 45 });
michael@0 46
michael@0 47 XPCOMUtils.defineLazyGetter(this, "CreateSocialMarkWidget", function() {
michael@0 48 let tmp = {};
michael@0 49 Cu.import("resource:///modules/Social.jsm", tmp);
michael@0 50 return tmp.CreateSocialMarkWidget;
michael@0 51 });
michael@0 52
michael@0 53 SocialUI = {
michael@0 54 _initialized: false,
michael@0 55
michael@0 56 // Called on delayed startup to initialize the UI
michael@0 57 init: function SocialUI_init() {
michael@0 58 if (this._initialized) {
michael@0 59 return;
michael@0 60 }
michael@0 61
michael@0 62 Services.obs.addObserver(this, "social:ambient-notification-changed", false);
michael@0 63 Services.obs.addObserver(this, "social:profile-changed", false);
michael@0 64 Services.obs.addObserver(this, "social:frameworker-error", false);
michael@0 65 Services.obs.addObserver(this, "social:providers-changed", false);
michael@0 66 Services.obs.addObserver(this, "social:provider-reload", false);
michael@0 67 Services.obs.addObserver(this, "social:provider-enabled", false);
michael@0 68 Services.obs.addObserver(this, "social:provider-disabled", false);
michael@0 69
michael@0 70 Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
michael@0 71
michael@0 72 gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
michael@0 73 document.getElementById("PanelUI-popup").addEventListener("popupshown", SocialMarks.updatePanelButtons, true);
michael@0 74
michael@0 75 // menupopups that list social providers. we only populate them when shown,
michael@0 76 // and if it has not been done already.
michael@0 77 document.getElementById("viewSidebarMenu").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
michael@0 78 document.getElementById("social-statusarea-popup").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
michael@0 79
michael@0 80 Social.init().then((update) => {
michael@0 81 if (update)
michael@0 82 this._providersChanged();
michael@0 83 // handle SessionStore for the sidebar state
michael@0 84 SocialSidebar.restoreWindowState();
michael@0 85 });
michael@0 86
michael@0 87 this._initialized = true;
michael@0 88 },
michael@0 89
michael@0 90 // Called on window unload
michael@0 91 uninit: function SocialUI_uninit() {
michael@0 92 if (!this._initialized) {
michael@0 93 return;
michael@0 94 }
michael@0 95 SocialSidebar.saveWindowState();
michael@0 96
michael@0 97 Services.obs.removeObserver(this, "social:ambient-notification-changed");
michael@0 98 Services.obs.removeObserver(this, "social:profile-changed");
michael@0 99 Services.obs.removeObserver(this, "social:frameworker-error");
michael@0 100 Services.obs.removeObserver(this, "social:providers-changed");
michael@0 101 Services.obs.removeObserver(this, "social:provider-reload");
michael@0 102 Services.obs.removeObserver(this, "social:provider-enabled");
michael@0 103 Services.obs.removeObserver(this, "social:provider-disabled");
michael@0 104
michael@0 105 Services.prefs.removeObserver("social.toast-notifications.enabled", this);
michael@0 106
michael@0 107 document.getElementById("PanelUI-popup").removeEventListener("popupshown", SocialMarks.updatePanelButtons, true);
michael@0 108 document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
michael@0 109 document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
michael@0 110
michael@0 111 this._initialized = false;
michael@0 112 },
michael@0 113
michael@0 114 observe: function SocialUI_observe(subject, topic, data) {
michael@0 115 // Exceptions here sometimes don't get reported properly, report them
michael@0 116 // manually :(
michael@0 117 try {
michael@0 118 switch (topic) {
michael@0 119 case "social:provider-enabled":
michael@0 120 SocialMarks.populateToolbarPalette();
michael@0 121 SocialStatus.populateToolbarPalette();
michael@0 122 break;
michael@0 123 case "social:provider-disabled":
michael@0 124 SocialMarks.removeProvider(data);
michael@0 125 SocialStatus.removeProvider(data);
michael@0 126 SocialSidebar.disableProvider(data);
michael@0 127 break;
michael@0 128 case "social:provider-reload":
michael@0 129 SocialStatus.reloadProvider(data);
michael@0 130 // if the reloaded provider is our current provider, fall through
michael@0 131 // to social:providers-changed so the ui will be reset
michael@0 132 if (!SocialSidebar.provider || SocialSidebar.provider.origin != data)
michael@0 133 return;
michael@0 134 // currently only the sidebar and flyout have a selected provider.
michael@0 135 // sidebar provider has changed (possibly to null), ensure the content
michael@0 136 // is unloaded and the frames are reset, they will be loaded in
michael@0 137 // providers-changed below if necessary.
michael@0 138 SocialSidebar.unloadSidebar();
michael@0 139 SocialFlyout.unload();
michael@0 140 // fall through to providers-changed to ensure the reloaded provider
michael@0 141 // is correctly reflected in any UI and the multi-provider menu
michael@0 142 case "social:providers-changed":
michael@0 143 this._providersChanged();
michael@0 144 break;
michael@0 145
michael@0 146 // Provider-specific notifications
michael@0 147 case "social:ambient-notification-changed":
michael@0 148 SocialStatus.updateButton(data);
michael@0 149 break;
michael@0 150 case "social:profile-changed":
michael@0 151 // make sure anything that happens here only affects the provider for
michael@0 152 // which the profile is changing, and that anything we call actually
michael@0 153 // needs to change based on profile data.
michael@0 154 SocialStatus.updateButton(data);
michael@0 155 break;
michael@0 156 case "social:frameworker-error":
michael@0 157 if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
michael@0 158 SocialSidebar.setSidebarErrorMessage();
michael@0 159 }
michael@0 160 break;
michael@0 161
michael@0 162 case "nsPref:changed":
michael@0 163 if (data == "social.toast-notifications.enabled") {
michael@0 164 SocialSidebar.updateToggleNotifications();
michael@0 165 }
michael@0 166 break;
michael@0 167 }
michael@0 168 } catch (e) {
michael@0 169 Components.utils.reportError(e + "\n" + e.stack);
michael@0 170 throw e;
michael@0 171 }
michael@0 172 },
michael@0 173
michael@0 174 _providersChanged: function() {
michael@0 175 SocialSidebar.clearProviderMenus();
michael@0 176 SocialSidebar.update();
michael@0 177 SocialChatBar.update();
michael@0 178 SocialShare.populateProviderMenu();
michael@0 179 SocialStatus.populateToolbarPalette();
michael@0 180 SocialMarks.populateToolbarPalette();
michael@0 181 SocialShare.update();
michael@0 182 },
michael@0 183
michael@0 184 // This handles "ActivateSocialFeature" events fired against content documents
michael@0 185 // in this window.
michael@0 186 _activationEventHandler: function SocialUI_activationHandler(e) {
michael@0 187 let targetDoc;
michael@0 188 let node;
michael@0 189 if (e.target instanceof HTMLDocument) {
michael@0 190 // version 0 support
michael@0 191 targetDoc = e.target;
michael@0 192 node = targetDoc.documentElement
michael@0 193 } else {
michael@0 194 targetDoc = e.target.ownerDocument;
michael@0 195 node = e.target;
michael@0 196 }
michael@0 197 if (!(targetDoc instanceof HTMLDocument))
michael@0 198 return;
michael@0 199
michael@0 200 // Ignore events fired in background tabs or iframes
michael@0 201 if (targetDoc.defaultView != content)
michael@0 202 return;
michael@0 203
michael@0 204 // If we are in PB mode, we silently do nothing (bug 829404 exists to
michael@0 205 // do something sensible here...)
michael@0 206 if (PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 207 return;
michael@0 208
michael@0 209 // If the last event was received < 1s ago, ignore this one
michael@0 210 let now = Date.now();
michael@0 211 if (now - Social.lastEventReceived < 1000)
michael@0 212 return;
michael@0 213 Social.lastEventReceived = now;
michael@0 214
michael@0 215 // We only want to activate if it is as a result of user input.
michael@0 216 let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 217 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 218 if (!dwu.isHandlingUserInput) {
michael@0 219 Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
michael@0 220 return;
michael@0 221 }
michael@0 222
michael@0 223 let data = node.getAttribute("data-service");
michael@0 224 if (data) {
michael@0 225 try {
michael@0 226 data = JSON.parse(data);
michael@0 227 } catch(e) {
michael@0 228 Cu.reportError("Social Service manifest parse error: "+e);
michael@0 229 return;
michael@0 230 }
michael@0 231 }
michael@0 232 Social.installProvider(targetDoc, data, function(manifest) {
michael@0 233 Social.activateFromOrigin(manifest.origin, function(provider) {
michael@0 234 if (provider.sidebarURL) {
michael@0 235 SocialSidebar.show(provider.origin);
michael@0 236 }
michael@0 237 if (provider.postActivationURL) {
michael@0 238 openUILinkIn(provider.postActivationURL, "tab");
michael@0 239 }
michael@0 240 });
michael@0 241 });
michael@0 242 },
michael@0 243
michael@0 244 showLearnMore: function() {
michael@0 245 let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
michael@0 246 openUILinkIn(url, "tab");
michael@0 247 },
michael@0 248
michael@0 249 closeSocialPanelForLinkTraversal: function (target, linkNode) {
michael@0 250 // No need to close the panel if this traversal was not retargeted
michael@0 251 if (target == "" || target == "_self")
michael@0 252 return;
michael@0 253
michael@0 254 // Check to see whether this link traversal was in a social panel
michael@0 255 let win = linkNode.ownerDocument.defaultView;
michael@0 256 let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 257 .getInterface(Ci.nsIWebNavigation)
michael@0 258 .QueryInterface(Ci.nsIDocShell)
michael@0 259 .chromeEventHandler;
michael@0 260 let containerParent = container.parentNode;
michael@0 261 if (containerParent.classList.contains("social-panel") &&
michael@0 262 containerParent instanceof Ci.nsIDOMXULPopupElement) {
michael@0 263 // allow the link traversal to finish before closing the panel
michael@0 264 setTimeout(() => {
michael@0 265 containerParent.hidePopup();
michael@0 266 }, 0);
michael@0 267 }
michael@0 268 },
michael@0 269
michael@0 270 get _chromeless() {
michael@0 271 // Is this a popup window that doesn't want chrome shown?
michael@0 272 let docElem = document.documentElement;
michael@0 273 // extrachrome is not restored during session restore, so we need
michael@0 274 // to check for the toolbar as well.
michael@0 275 let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
michael@0 276 docElem.getAttribute('chromehidden').contains("toolbar");
michael@0 277 // This property is "fixed" for a window, so avoid doing the check above
michael@0 278 // multiple times...
michael@0 279 delete this._chromeless;
michael@0 280 this._chromeless = chromeless;
michael@0 281 return chromeless;
michael@0 282 },
michael@0 283
michael@0 284 get enabled() {
michael@0 285 // Returns whether social is enabled *for this window*.
michael@0 286 if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 287 return false;
michael@0 288 return Social.providers.length > 0;
michael@0 289 },
michael@0 290
michael@0 291 // called on tab/urlbar/location changes and after customization. Update
michael@0 292 // anything that is tab specific.
michael@0 293 updateState: function() {
michael@0 294 if (!this.enabled)
michael@0 295 return;
michael@0 296 SocialMarks.update();
michael@0 297 SocialShare.update();
michael@0 298 }
michael@0 299 }
michael@0 300
michael@0 301 SocialChatBar = {
michael@0 302 get chatbar() {
michael@0 303 return document.getElementById("pinnedchats");
michael@0 304 },
michael@0 305 // Whether the chatbar is available for this window. Note that in full-screen
michael@0 306 // mode chats are available, but not shown.
michael@0 307 get isAvailable() {
michael@0 308 return SocialUI.enabled;
michael@0 309 },
michael@0 310 // Does this chatbar have any chats (whether minimized, collapsed or normal)
michael@0 311 get hasChats() {
michael@0 312 return !!this.chatbar.firstElementChild;
michael@0 313 },
michael@0 314 openChat: function(aProvider, aURL, aCallback, aMode) {
michael@0 315 this.update();
michael@0 316 if (!this.isAvailable)
michael@0 317 return false;
michael@0 318 this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
michael@0 319 // We only want to focus the chat if it is as a result of user input.
michael@0 320 let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 321 .getInterface(Ci.nsIDOMWindowUtils);
michael@0 322 if (dwu.isHandlingUserInput)
michael@0 323 this.chatbar.focus();
michael@0 324 return true;
michael@0 325 },
michael@0 326 update: function() {
michael@0 327 let command = document.getElementById("Social:FocusChat");
michael@0 328 if (!this.isAvailable) {
michael@0 329 this.chatbar.hidden = command.hidden = true;
michael@0 330 } else {
michael@0 331 this.chatbar.hidden = command.hidden = false;
michael@0 332 }
michael@0 333 command.setAttribute("disabled", command.hidden ? "true" : "false");
michael@0 334 },
michael@0 335 focus: function SocialChatBar_focus() {
michael@0 336 this.chatbar.focus();
michael@0 337 }
michael@0 338 }
michael@0 339
michael@0 340 SocialFlyout = {
michael@0 341 get panel() {
michael@0 342 return document.getElementById("social-flyout-panel");
michael@0 343 },
michael@0 344
michael@0 345 get iframe() {
michael@0 346 if (!this.panel.firstChild)
michael@0 347 this._createFrame();
michael@0 348 return this.panel.firstChild;
michael@0 349 },
michael@0 350
michael@0 351 dispatchPanelEvent: function(name) {
michael@0 352 let doc = this.iframe.contentDocument;
michael@0 353 let evt = doc.createEvent("CustomEvent");
michael@0 354 evt.initCustomEvent(name, true, true, {});
michael@0 355 doc.documentElement.dispatchEvent(evt);
michael@0 356 },
michael@0 357
michael@0 358 _createFrame: function() {
michael@0 359 let panel = this.panel;
michael@0 360 if (!SocialUI.enabled || panel.firstChild)
michael@0 361 return;
michael@0 362 // create and initialize the panel for this window
michael@0 363 let iframe = document.createElement("iframe");
michael@0 364 iframe.setAttribute("type", "content");
michael@0 365 iframe.setAttribute("class", "social-panel-frame");
michael@0 366 iframe.setAttribute("flex", "1");
michael@0 367 iframe.setAttribute("tooltip", "aHTMLTooltip");
michael@0 368 iframe.setAttribute("origin", SocialSidebar.provider.origin);
michael@0 369 panel.appendChild(iframe);
michael@0 370 },
michael@0 371
michael@0 372 setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
michael@0 373 this.iframe.removeAttribute("src");
michael@0 374 this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
michael@0 375 encodeURIComponent(this.iframe.getAttribute("origin")),
michael@0 376 null, null, null, null);
michael@0 377 sizeSocialPanelToContent(this.panel, this.iframe);
michael@0 378 },
michael@0 379
michael@0 380 unload: function() {
michael@0 381 let panel = this.panel;
michael@0 382 panel.hidePopup();
michael@0 383 if (!panel.firstChild)
michael@0 384 return
michael@0 385 let iframe = panel.firstChild;
michael@0 386 if (iframe.socialErrorListener)
michael@0 387 iframe.socialErrorListener.remove();
michael@0 388 panel.removeChild(iframe);
michael@0 389 },
michael@0 390
michael@0 391 onShown: function(aEvent) {
michael@0 392 let panel = this.panel;
michael@0 393 let iframe = this.iframe;
michael@0 394 this._dynamicResizer = new DynamicResizeWatcher();
michael@0 395 iframe.docShell.isActive = true;
michael@0 396 iframe.docShell.isAppTab = true;
michael@0 397 if (iframe.contentDocument.readyState == "complete") {
michael@0 398 this._dynamicResizer.start(panel, iframe);
michael@0 399 this.dispatchPanelEvent("socialFrameShow");
michael@0 400 } else {
michael@0 401 // first time load, wait for load and dispatch after load
michael@0 402 iframe.addEventListener("load", function panelBrowserOnload(e) {
michael@0 403 iframe.removeEventListener("load", panelBrowserOnload, true);
michael@0 404 setTimeout(function() {
michael@0 405 if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
michael@0 406 SocialFlyout._dynamicResizer.start(panel, iframe);
michael@0 407 SocialFlyout.dispatchPanelEvent("socialFrameShow");
michael@0 408 }
michael@0 409 }, 0);
michael@0 410 }, true);
michael@0 411 }
michael@0 412 },
michael@0 413
michael@0 414 onHidden: function(aEvent) {
michael@0 415 this._dynamicResizer.stop();
michael@0 416 this._dynamicResizer = null;
michael@0 417 this.iframe.docShell.isActive = false;
michael@0 418 this.dispatchPanelEvent("socialFrameHide");
michael@0 419 },
michael@0 420
michael@0 421 load: function(aURL, cb) {
michael@0 422 if (!SocialSidebar.provider)
michael@0 423 return;
michael@0 424
michael@0 425 this.panel.hidden = false;
michael@0 426 let iframe = this.iframe;
michael@0 427 // same url with only ref difference does not cause a new load, so we
michael@0 428 // want to go right to the callback
michael@0 429 let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
michael@0 430 if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
michael@0 431 iframe.addEventListener("load", function documentLoaded() {
michael@0 432 iframe.removeEventListener("load", documentLoaded, true);
michael@0 433 cb();
michael@0 434 }, true);
michael@0 435 // Force a layout flush by calling .clientTop so
michael@0 436 // that the docShell of this frame is created
michael@0 437 iframe.clientTop;
michael@0 438 Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
michael@0 439 iframe.setAttribute("src", aURL);
michael@0 440 } else {
michael@0 441 // we still need to set the src to trigger the contents hashchange event
michael@0 442 // for ref changes
michael@0 443 iframe.setAttribute("src", aURL);
michael@0 444 cb();
michael@0 445 }
michael@0 446 },
michael@0 447
michael@0 448 open: function(aURL, yOffset, aCallback) {
michael@0 449 // Hide any other social panels that may be open.
michael@0 450 document.getElementById("social-notification-panel").hidePopup();
michael@0 451
michael@0 452 if (!SocialUI.enabled)
michael@0 453 return;
michael@0 454 let panel = this.panel;
michael@0 455 let iframe = this.iframe;
michael@0 456
michael@0 457 this.load(aURL, function() {
michael@0 458 sizeSocialPanelToContent(panel, iframe);
michael@0 459 let anchor = document.getElementById("social-sidebar-browser");
michael@0 460 if (panel.state == "open") {
michael@0 461 panel.moveToAnchor(anchor, "start_before", 0, yOffset, false);
michael@0 462 } else {
michael@0 463 panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
michael@0 464 }
michael@0 465 if (aCallback) {
michael@0 466 try {
michael@0 467 aCallback(iframe.contentWindow);
michael@0 468 } catch(e) {
michael@0 469 Cu.reportError(e);
michael@0 470 }
michael@0 471 }
michael@0 472 });
michael@0 473 }
michael@0 474 }
michael@0 475
michael@0 476 SocialShare = {
michael@0 477 get panel() {
michael@0 478 return document.getElementById("social-share-panel");
michael@0 479 },
michael@0 480
michael@0 481 get iframe() {
michael@0 482 // first element is our menu vbox.
michael@0 483 if (this.panel.childElementCount == 1)
michael@0 484 return null;
michael@0 485 else
michael@0 486 return this.panel.lastChild;
michael@0 487 },
michael@0 488
michael@0 489 uninit: function () {
michael@0 490 if (this.iframe) {
michael@0 491 this.iframe.remove();
michael@0 492 }
michael@0 493 },
michael@0 494
michael@0 495 _createFrame: function() {
michael@0 496 let panel = this.panel;
michael@0 497 if (!SocialUI.enabled || this.iframe)
michael@0 498 return;
michael@0 499 this.panel.hidden = false;
michael@0 500 // create and initialize the panel for this window
michael@0 501 let iframe = document.createElement("iframe");
michael@0 502 iframe.setAttribute("type", "content");
michael@0 503 iframe.setAttribute("class", "social-share-frame");
michael@0 504 iframe.setAttribute("context", "contentAreaContextMenu");
michael@0 505 iframe.setAttribute("tooltip", "aHTMLTooltip");
michael@0 506 iframe.setAttribute("flex", "1");
michael@0 507 panel.appendChild(iframe);
michael@0 508 this.populateProviderMenu();
michael@0 509 },
michael@0 510
michael@0 511 getSelectedProvider: function() {
michael@0 512 let provider;
michael@0 513 let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
michael@0 514 if (lastProviderOrigin) {
michael@0 515 provider = Social._getProviderFromOrigin(lastProviderOrigin);
michael@0 516 }
michael@0 517 // if they have a provider selected in the sidebar use that for the initial
michael@0 518 // default in share
michael@0 519 if (!provider)
michael@0 520 provider = SocialSidebar.provider;
michael@0 521 // if our provider has no shareURL, select the first one that does
michael@0 522 if (!provider || !provider.shareURL) {
michael@0 523 let providers = [p for (p of Social.providers) if (p.shareURL)];
michael@0 524 provider = providers.length > 0 && providers[0];
michael@0 525 }
michael@0 526 return provider;
michael@0 527 },
michael@0 528
michael@0 529 populateProviderMenu: function() {
michael@0 530 if (!this.iframe)
michael@0 531 return;
michael@0 532 let providers = [p for (p of Social.providers) if (p.shareURL)];
michael@0 533 let hbox = document.getElementById("social-share-provider-buttons");
michael@0 534 // selectable providers are inserted before the provider-menu seperator,
michael@0 535 // remove any menuitems in that area
michael@0 536 while (hbox.firstChild) {
michael@0 537 hbox.removeChild(hbox.firstChild);
michael@0 538 }
michael@0 539 // reset our share toolbar
michael@0 540 // only show a selection if there is more than one
michael@0 541 if (!SocialUI.enabled || providers.length < 2) {
michael@0 542 this.panel.firstChild.hidden = true;
michael@0 543 return;
michael@0 544 }
michael@0 545 let selectedProvider = this.getSelectedProvider();
michael@0 546 for (let provider of providers) {
michael@0 547 let button = document.createElement("toolbarbutton");
michael@0 548 button.setAttribute("class", "toolbarbutton share-provider-button");
michael@0 549 button.setAttribute("type", "radio");
michael@0 550 button.setAttribute("group", "share-providers");
michael@0 551 button.setAttribute("image", provider.iconURL);
michael@0 552 button.setAttribute("tooltiptext", provider.name);
michael@0 553 button.setAttribute("origin", provider.origin);
michael@0 554 button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin')); this.checked=true;");
michael@0 555 if (provider == selectedProvider) {
michael@0 556 this.defaultButton = button;
michael@0 557 }
michael@0 558 hbox.appendChild(button);
michael@0 559 }
michael@0 560 if (!this.defaultButton) {
michael@0 561 this.defaultButton = hbox.firstChild
michael@0 562 }
michael@0 563 this.defaultButton.setAttribute("checked", "true");
michael@0 564 this.panel.firstChild.hidden = false;
michael@0 565 },
michael@0 566
michael@0 567 get shareButton() {
michael@0 568 return document.getElementById("social-share-button");
michael@0 569 },
michael@0 570
michael@0 571 canSharePage: function(aURI) {
michael@0 572 // we do not enable sharing from private sessions
michael@0 573 if (PrivateBrowsingUtils.isWindowPrivate(window))
michael@0 574 return false;
michael@0 575
michael@0 576 if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')))
michael@0 577 return false;
michael@0 578 return true;
michael@0 579 },
michael@0 580
michael@0 581 update: function() {
michael@0 582 let shareButton = this.shareButton;
michael@0 583 shareButton.hidden = !SocialUI.enabled ||
michael@0 584 [p for (p of Social.providers) if (p.shareURL)].length == 0;
michael@0 585 shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
michael@0 586
michael@0 587 // also update the relevent command's disabled state so the keyboard
michael@0 588 // shortcut only works when available.
michael@0 589 let cmd = document.getElementById("Social:SharePage");
michael@0 590 if (shareButton.disabled)
michael@0 591 cmd.setAttribute("disabled", "true");
michael@0 592 else
michael@0 593 cmd.removeAttribute("disabled");
michael@0 594 },
michael@0 595
michael@0 596 onShowing: function() {
michael@0 597 this.shareButton.setAttribute("open", "true");
michael@0 598 },
michael@0 599
michael@0 600 onHidden: function() {
michael@0 601 this.shareButton.removeAttribute("open");
michael@0 602 this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
michael@0 603 this.currentShare = null;
michael@0 604 },
michael@0 605
michael@0 606 setErrorMessage: function() {
michael@0 607 let iframe = this.iframe;
michael@0 608 if (!iframe)
michael@0 609 return;
michael@0 610
michael@0 611 iframe.removeAttribute("src");
michael@0 612 iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
michael@0 613 encodeURIComponent(iframe.getAttribute("origin")),
michael@0 614 null, null, null, null);
michael@0 615 sizeSocialPanelToContent(this.panel, iframe);
michael@0 616 },
michael@0 617
michael@0 618 sharePage: function(providerOrigin, graphData) {
michael@0 619 // if providerOrigin is undefined, we use the last-used provider, or the
michael@0 620 // current/default provider. The provider selection in the share panel
michael@0 621 // will call sharePage with an origin for us to switch to.
michael@0 622 this._createFrame();
michael@0 623 let iframe = this.iframe;
michael@0 624 let provider;
michael@0 625 if (providerOrigin)
michael@0 626 provider = Social._getProviderFromOrigin(providerOrigin);
michael@0 627 else
michael@0 628 provider = this.getSelectedProvider();
michael@0 629 if (!provider || !provider.shareURL)
michael@0 630 return;
michael@0 631
michael@0 632 // graphData is an optional param that either defines the full set of data
michael@0 633 // to be shared, or partial data about the current page. It is set by a call
michael@0 634 // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
michael@0 635 // define at least url. If it is undefined, we're sharing the current url in
michael@0 636 // the browser tab.
michael@0 637 let sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) :
michael@0 638 gBrowser.currentURI;
michael@0 639 if (!this.canSharePage(sharedURI))
michael@0 640 return;
michael@0 641
michael@0 642 // the point of this action type is that we can use existing share
michael@0 643 // endpoints (e.g. oexchange) that do not support additional
michael@0 644 // socialapi functionality. One tweak is that we shoot an event
michael@0 645 // containing the open graph data.
michael@0 646 let pageData = graphData ? graphData : this.currentShare;
michael@0 647 if (!pageData || sharedURI == gBrowser.currentURI) {
michael@0 648 pageData = OpenGraphBuilder.getData(gBrowser);
michael@0 649 if (graphData) {
michael@0 650 // overwrite data retreived from page with data given to us as a param
michael@0 651 for (let p in graphData) {
michael@0 652 pageData[p] = graphData[p];
michael@0 653 }
michael@0 654 }
michael@0 655 }
michael@0 656 this.currentShare = pageData;
michael@0 657
michael@0 658 let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
michael@0 659
michael@0 660 this._dynamicResizer = new DynamicResizeWatcher();
michael@0 661 // if we've already loaded this provider/page share endpoint, we don't want
michael@0 662 // to add another load event listener.
michael@0 663 let reload = true;
michael@0 664 let endpointMatch = shareEndpoint == iframe.getAttribute("src");
michael@0 665 let docLoaded = iframe.contentDocument && iframe.contentDocument.readyState == "complete";
michael@0 666 if (endpointMatch && docLoaded) {
michael@0 667 reload = shareEndpoint != iframe.contentDocument.location.spec;
michael@0 668 }
michael@0 669 if (!reload) {
michael@0 670 this._dynamicResizer.start(this.panel, iframe);
michael@0 671 iframe.docShell.isActive = true;
michael@0 672 iframe.docShell.isAppTab = true;
michael@0 673 let evt = iframe.contentDocument.createEvent("CustomEvent");
michael@0 674 evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
michael@0 675 iframe.contentDocument.documentElement.dispatchEvent(evt);
michael@0 676 } else {
michael@0 677 // first time load, wait for load and dispatch after load
michael@0 678 iframe.addEventListener("load", function panelBrowserOnload(e) {
michael@0 679 iframe.removeEventListener("load", panelBrowserOnload, true);
michael@0 680 iframe.docShell.isActive = true;
michael@0 681 iframe.docShell.isAppTab = true;
michael@0 682 setTimeout(function() {
michael@0 683 if (SocialShare._dynamicResizer) { // may go null if hidden quickly
michael@0 684 SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
michael@0 685 }
michael@0 686 }, 0);
michael@0 687 let evt = iframe.contentDocument.createEvent("CustomEvent");
michael@0 688 evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
michael@0 689 iframe.contentDocument.documentElement.dispatchEvent(evt);
michael@0 690 }, true);
michael@0 691 }
michael@0 692 // always ensure that origin belongs to the endpoint
michael@0 693 let uri = Services.io.newURI(shareEndpoint, null, null);
michael@0 694 iframe.setAttribute("origin", provider.origin);
michael@0 695 iframe.setAttribute("src", shareEndpoint);
michael@0 696
michael@0 697 let navBar = document.getElementById("nav-bar");
michael@0 698 let anchor = document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
michael@0 699 this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
michael@0 700 Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
michael@0 701 }
michael@0 702 };
michael@0 703
michael@0 704 SocialSidebar = {
michael@0 705 // Whether the sidebar can be shown for this window.
michael@0 706 get canShow() {
michael@0 707 if (!SocialUI.enabled || document.mozFullScreen)
michael@0 708 return false;
michael@0 709 return Social.providers.some(p => p.sidebarURL);
michael@0 710 },
michael@0 711
michael@0 712 // Whether the user has toggled the sidebar on (for windows where it can appear)
michael@0 713 get opened() {
michael@0 714 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 715 return !broadcaster.hidden;
michael@0 716 },
michael@0 717
michael@0 718 restoreWindowState: function() {
michael@0 719 // Window state is used to allow different sidebar providers in each window.
michael@0 720 // We also store the provider used in a pref as the default sidebar to
michael@0 721 // maintain that state for users who do not restore window state. The
michael@0 722 // existence of social.sidebar.provider means the sidebar is open with that
michael@0 723 // provider.
michael@0 724 this._initialized = true;
michael@0 725 if (!this.canShow)
michael@0 726 return;
michael@0 727
michael@0 728 if (Services.prefs.prefHasUserValue("social.provider.current")) {
michael@0 729 // "upgrade" when the first window opens if we have old prefs. We get the
michael@0 730 // values from prefs this one time, window state will be saved when this
michael@0 731 // window is closed.
michael@0 732 let origin = Services.prefs.getCharPref("social.provider.current");
michael@0 733 Services.prefs.clearUserPref("social.provider.current");
michael@0 734 // social.sidebar.open default was true, but we only opened if there was
michael@0 735 // a current provider
michael@0 736 let opened = origin && true;
michael@0 737 if (Services.prefs.prefHasUserValue("social.sidebar.open")) {
michael@0 738 opened = origin && Services.prefs.getBoolPref("social.sidebar.open");
michael@0 739 Services.prefs.clearUserPref("social.sidebar.open");
michael@0 740 }
michael@0 741 let data = {
michael@0 742 "hidden": !opened,
michael@0 743 "origin": origin
michael@0 744 };
michael@0 745 SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
michael@0 746 }
michael@0 747
michael@0 748 let data = SessionStore.getWindowValue(window, "socialSidebar");
michael@0 749 // if this window doesn't have it's own state, use the state from the opener
michael@0 750 if (!data && window.opener && !window.opener.closed) {
michael@0 751 try {
michael@0 752 data = SessionStore.getWindowValue(window.opener, "socialSidebar");
michael@0 753 } catch(e) {
michael@0 754 // Window is not tracked, which happens on osx if the window is opened
michael@0 755 // from the hidden window. That happens when you close the last window
michael@0 756 // without quiting firefox, then open a new window.
michael@0 757 }
michael@0 758 }
michael@0 759 if (data) {
michael@0 760 data = JSON.parse(data);
michael@0 761 document.getElementById("social-sidebar-browser").setAttribute("origin", data.origin);
michael@0 762 if (!data.hidden)
michael@0 763 this.show(data.origin);
michael@0 764 } else if (Services.prefs.prefHasUserValue("social.sidebar.provider")) {
michael@0 765 // no window state, use the global state if it is available
michael@0 766 this.show(Services.prefs.getCharPref("social.sidebar.provider"));
michael@0 767 }
michael@0 768 },
michael@0 769
michael@0 770 saveWindowState: function() {
michael@0 771 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 772 let sidebarOrigin = document.getElementById("social-sidebar-browser").getAttribute("origin");
michael@0 773 let data = {
michael@0 774 "hidden": broadcaster.hidden,
michael@0 775 "origin": sidebarOrigin
michael@0 776 };
michael@0 777
michael@0 778 // Save a global state for users who do not restore state.
michael@0 779 if (broadcaster.hidden)
michael@0 780 Services.prefs.clearUserPref("social.sidebar.provider");
michael@0 781 else
michael@0 782 Services.prefs.setCharPref("social.sidebar.provider", sidebarOrigin);
michael@0 783
michael@0 784 try {
michael@0 785 SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
michael@0 786 } catch(e) {
michael@0 787 // window not tracked during uninit
michael@0 788 }
michael@0 789 },
michael@0 790
michael@0 791 setSidebarVisibilityState: function(aEnabled) {
michael@0 792 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 793 // it's possible we'll be called twice with aEnabled=false so let's
michael@0 794 // just assume we may often be called with the same state.
michael@0 795 if (aEnabled == sbrowser.docShellIsActive)
michael@0 796 return;
michael@0 797 sbrowser.docShellIsActive = aEnabled;
michael@0 798 let evt = sbrowser.contentDocument.createEvent("CustomEvent");
michael@0 799 evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
michael@0 800 sbrowser.contentDocument.documentElement.dispatchEvent(evt);
michael@0 801 },
michael@0 802
michael@0 803 updateToggleNotifications: function() {
michael@0 804 let command = document.getElementById("Social:ToggleNotifications");
michael@0 805 command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
michael@0 806 command.setAttribute("hidden", !SocialUI.enabled);
michael@0 807 },
michael@0 808
michael@0 809 update: function SocialSidebar_update() {
michael@0 810 // ensure we never update before restoreWindowState
michael@0 811 if (!this._initialized)
michael@0 812 return;
michael@0 813 this.ensureProvider();
michael@0 814 this.updateToggleNotifications();
michael@0 815 this._updateHeader();
michael@0 816 clearTimeout(this._unloadTimeoutId);
michael@0 817 // Hide the toggle menu item if the sidebar cannot appear
michael@0 818 let command = document.getElementById("Social:ToggleSidebar");
michael@0 819 command.setAttribute("hidden", this.canShow ? "false" : "true");
michael@0 820
michael@0 821 // Hide the sidebar if it cannot appear, or has been toggled off.
michael@0 822 // Also set the command "checked" state accordingly.
michael@0 823 let hideSidebar = !this.canShow || !this.opened;
michael@0 824 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 825 broadcaster.hidden = hideSidebar;
michael@0 826 command.setAttribute("checked", !hideSidebar);
michael@0 827
michael@0 828 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 829
michael@0 830 if (hideSidebar) {
michael@0 831 sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
michael@0 832 this.setSidebarVisibilityState(false);
michael@0 833 // If we've been disabled, unload the sidebar content immediately;
michael@0 834 // if the sidebar was just toggled to invisible, wait a timeout
michael@0 835 // before unloading.
michael@0 836 if (!this.canShow) {
michael@0 837 this.unloadSidebar();
michael@0 838 } else {
michael@0 839 this._unloadTimeoutId = setTimeout(
michael@0 840 this.unloadSidebar,
michael@0 841 Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
michael@0 842 );
michael@0 843 }
michael@0 844 } else {
michael@0 845 sbrowser.setAttribute("origin", this.provider.origin);
michael@0 846 if (this.provider.errorState == "frameworker-error") {
michael@0 847 SocialSidebar.setSidebarErrorMessage();
michael@0 848 return;
michael@0 849 }
michael@0 850
michael@0 851 // Make sure the right sidebar URL is loaded
michael@0 852 if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
michael@0 853 // we check readyState right after setting src, we need a new content
michael@0 854 // viewer to ensure we are checking against the correct document.
michael@0 855 sbrowser.docShell.createAboutBlankContentViewer(null);
michael@0 856 Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
michael@0 857 // setting isAppTab causes clicks on untargeted links to open new tabs
michael@0 858 sbrowser.docShell.isAppTab = true;
michael@0 859 sbrowser.setAttribute("src", this.provider.sidebarURL);
michael@0 860 PopupNotifications.locationChange(sbrowser);
michael@0 861 }
michael@0 862
michael@0 863 // if the document has not loaded, delay until it is
michael@0 864 if (sbrowser.contentDocument.readyState != "complete") {
michael@0 865 document.getElementById("social-sidebar-button").setAttribute("loading", "true");
michael@0 866 sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
michael@0 867 } else {
michael@0 868 this.setSidebarVisibilityState(true);
michael@0 869 }
michael@0 870 }
michael@0 871 this._updateCheckedMenuItems(this.opened && this.provider ? this.provider.origin : null);
michael@0 872 },
michael@0 873
michael@0 874 _loadListener: function SocialSidebar_loadListener() {
michael@0 875 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 876 sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
michael@0 877 document.getElementById("social-sidebar-button").removeAttribute("loading");
michael@0 878 SocialSidebar.setSidebarVisibilityState(true);
michael@0 879 },
michael@0 880
michael@0 881 unloadSidebar: function SocialSidebar_unloadSidebar() {
michael@0 882 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 883 if (!sbrowser.hasAttribute("origin"))
michael@0 884 return;
michael@0 885
michael@0 886 sbrowser.stop();
michael@0 887 sbrowser.removeAttribute("origin");
michael@0 888 sbrowser.setAttribute("src", "about:blank");
michael@0 889 // We need to explicitly create a new content viewer because the old one
michael@0 890 // doesn't get destroyed until about:blank has loaded (which does not happen
michael@0 891 // as long as the element is hidden).
michael@0 892 sbrowser.docShell.createAboutBlankContentViewer(null);
michael@0 893 SocialFlyout.unload();
michael@0 894 },
michael@0 895
michael@0 896 _unloadTimeoutId: 0,
michael@0 897
michael@0 898 setSidebarErrorMessage: function() {
michael@0 899 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 900 // a frameworker error "trumps" a sidebar error.
michael@0 901 let origin = sbrowser.getAttribute("origin");
michael@0 902 if (origin) {
michael@0 903 origin = "&origin=" + encodeURIComponent(origin);
michael@0 904 }
michael@0 905 if (this.provider.errorState == "frameworker-error") {
michael@0 906 sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure" + origin);
michael@0 907 } else {
michael@0 908 let url = encodeURIComponent(this.provider.sidebarURL);
michael@0 909 sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url + origin, null, null);
michael@0 910 }
michael@0 911 },
michael@0 912
michael@0 913 _provider: null,
michael@0 914 ensureProvider: function() {
michael@0 915 if (this._provider)
michael@0 916 return;
michael@0 917 // origin for sidebar is persisted, so get the previously selected sidebar
michael@0 918 // first, otherwise fallback to the first provider in the list
michael@0 919 let sbrowser = document.getElementById("social-sidebar-browser");
michael@0 920 let origin = sbrowser.getAttribute("origin");
michael@0 921 let providers = [p for (p of Social.providers) if (p.sidebarURL)];
michael@0 922 let provider;
michael@0 923 if (origin)
michael@0 924 provider = Social._getProviderFromOrigin(origin);
michael@0 925 if (!provider && providers.length > 0)
michael@0 926 provider = providers[0];
michael@0 927 if (provider)
michael@0 928 this.provider = provider;
michael@0 929 },
michael@0 930
michael@0 931 get provider() {
michael@0 932 return this._provider;
michael@0 933 },
michael@0 934
michael@0 935 set provider(provider) {
michael@0 936 if (!provider || provider.sidebarURL) {
michael@0 937 this._provider = provider;
michael@0 938 this._updateHeader();
michael@0 939 this._updateCheckedMenuItems(provider && provider.origin);
michael@0 940 this.update();
michael@0 941 }
michael@0 942 },
michael@0 943
michael@0 944 disableProvider: function(origin) {
michael@0 945 if (this._provider && this._provider.origin == origin) {
michael@0 946 this._provider = null;
michael@0 947 // force a selection of the next provider if there is one
michael@0 948 this.ensureProvider();
michael@0 949 }
michael@0 950 },
michael@0 951
michael@0 952 _updateHeader: function() {
michael@0 953 let provider = this.provider;
michael@0 954 let image, title;
michael@0 955 if (provider) {
michael@0 956 image = "url(" + (provider.icon32URL || provider.iconURL) + ")";
michael@0 957 title = provider.name;
michael@0 958 }
michael@0 959 document.getElementById("social-sidebar-favico").style.listStyleImage = image;
michael@0 960 document.getElementById("social-sidebar-title").value = title;
michael@0 961 },
michael@0 962
michael@0 963 _updateCheckedMenuItems: function(origin) {
michael@0 964 // update selected menuitems
michael@0 965 let menuitems = document.getElementsByClassName("social-provider-menuitem");
michael@0 966 for (let mi of menuitems) {
michael@0 967 if (origin && mi.getAttribute("origin") == origin) {
michael@0 968 mi.setAttribute("checked", "true");
michael@0 969 mi.setAttribute("oncommand", "SocialSidebar.hide();");
michael@0 970 } else if (mi.getAttribute("checked")) {
michael@0 971 mi.removeAttribute("checked");
michael@0 972 mi.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
michael@0 973 }
michael@0 974 }
michael@0 975 },
michael@0 976
michael@0 977 show: function(origin) {
michael@0 978 // always show the sidebar, and set the provider
michael@0 979 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 980 broadcaster.hidden = false;
michael@0 981 if (origin)
michael@0 982 this.provider = Social._getProviderFromOrigin(origin);
michael@0 983 else
michael@0 984 SocialSidebar.update();
michael@0 985 this.saveWindowState();
michael@0 986 },
michael@0 987
michael@0 988 hide: function() {
michael@0 989 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 990 broadcaster.hidden = true;
michael@0 991 this._updateCheckedMenuItems();
michael@0 992 this.clearProviderMenus();
michael@0 993 SocialSidebar.update();
michael@0 994 this.saveWindowState();
michael@0 995 },
michael@0 996
michael@0 997 toggleSidebar: function SocialSidebar_toggle() {
michael@0 998 let broadcaster = document.getElementById("socialSidebarBroadcaster");
michael@0 999 if (broadcaster.hidden)
michael@0 1000 this.show();
michael@0 1001 else
michael@0 1002 this.hide();
michael@0 1003 },
michael@0 1004
michael@0 1005 populateSidebarMenu: function(event) {
michael@0 1006 // Providers are removed from the view->sidebar menu when there is a change
michael@0 1007 // in providers, so we only have to populate onshowing if there are no
michael@0 1008 // provider menus. We populate this menu so long as there are enabled
michael@0 1009 // providers with sidebars.
michael@0 1010 let popup = event.target;
michael@0 1011 let providerMenuSeps = popup.getElementsByClassName("social-provider-menu");
michael@0 1012 if (providerMenuSeps[0].previousSibling.nodeName == "menuseparator")
michael@0 1013 SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
michael@0 1014 },
michael@0 1015
michael@0 1016 clearProviderMenus: function() {
michael@0 1017 // called when there is a change in the provider list we clear all menus,
michael@0 1018 // they will be repopulated when the menu is shown
michael@0 1019 let providerMenuSeps = document.getElementsByClassName("social-provider-menu");
michael@0 1020 for (let providerMenuSep of providerMenuSeps) {
michael@0 1021 while (providerMenuSep.previousSibling.nodeName == "menuitem") {
michael@0 1022 let menu = providerMenuSep.parentNode;
michael@0 1023 menu.removeChild(providerMenuSep.previousSibling);
michael@0 1024 }
michael@0 1025 }
michael@0 1026 },
michael@0 1027
michael@0 1028 populateProviderMenu: function(providerMenuSep) {
michael@0 1029 let menu = providerMenuSep.parentNode;
michael@0 1030 // selectable providers are inserted before the provider-menu seperator,
michael@0 1031 // remove any menuitems in that area
michael@0 1032 while (providerMenuSep.previousSibling.nodeName == "menuitem") {
michael@0 1033 menu.removeChild(providerMenuSep.previousSibling);
michael@0 1034 }
michael@0 1035 // only show a selection in the sidebar header menu if there is more than one
michael@0 1036 let providers = [p for (p of Social.providers) if (p.sidebarURL)];
michael@0 1037 if (providers.length < 2 && menu.id != "viewSidebarMenu") {
michael@0 1038 providerMenuSep.hidden = true;
michael@0 1039 return;
michael@0 1040 }
michael@0 1041 let topSep = providerMenuSep.previousSibling;
michael@0 1042 for (let provider of providers) {
michael@0 1043 let menuitem = document.createElement("menuitem");
michael@0 1044 menuitem.className = "menuitem-iconic social-provider-menuitem";
michael@0 1045 menuitem.setAttribute("image", provider.iconURL);
michael@0 1046 menuitem.setAttribute("label", provider.name);
michael@0 1047 menuitem.setAttribute("origin", provider.origin);
michael@0 1048 if (this.opened && provider == this.provider) {
michael@0 1049 menuitem.setAttribute("checked", "true");
michael@0 1050 menuitem.setAttribute("oncommand", "SocialSidebar.hide();");
michael@0 1051 } else {
michael@0 1052 menuitem.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
michael@0 1053 }
michael@0 1054 menu.insertBefore(menuitem, providerMenuSep);
michael@0 1055 }
michael@0 1056 topSep.hidden = topSep.nextSibling == providerMenuSep;
michael@0 1057 providerMenuSep.hidden = !providerMenuSep.nextSibling;
michael@0 1058 }
michael@0 1059 }
michael@0 1060
michael@0 1061 // this helper class is used by removable/customizable buttons to handle
michael@0 1062 // widget creation/destruction
michael@0 1063
michael@0 1064 // When a provider is installed we show all their UI so the user will see the
michael@0 1065 // functionality of what they installed. The user can later customize the UI,
michael@0 1066 // moving buttons around or off the toolbar.
michael@0 1067 //
michael@0 1068 // On startup, we create the button widgets of any enabled provider.
michael@0 1069 // CustomizableUI handles placement and persistence of placement.
michael@0 1070 function ToolbarHelper(type, createButtonFn, listener) {
michael@0 1071 this._createButton = createButtonFn;
michael@0 1072 this._type = type;
michael@0 1073
michael@0 1074 if (listener) {
michael@0 1075 CustomizableUI.addListener(listener);
michael@0 1076 // remove this listener on window close
michael@0 1077 window.addEventListener("unload", () => {
michael@0 1078 CustomizableUI.removeListener(listener);
michael@0 1079 });
michael@0 1080 }
michael@0 1081 }
michael@0 1082
michael@0 1083 ToolbarHelper.prototype = {
michael@0 1084 idFromOrigin: function(origin) {
michael@0 1085 // this id needs to pass the checks in CustomizableUI, so remove characters
michael@0 1086 // that wont pass.
michael@0 1087 return this._type + "-" + Services.io.newURI(origin, null, null).hostPort.replace(/[\.:]/g,'-');
michael@0 1088 },
michael@0 1089
michael@0 1090 // should be called on disable of a provider
michael@0 1091 removeProviderButton: function(origin) {
michael@0 1092 CustomizableUI.destroyWidget(this.idFromOrigin(origin));
michael@0 1093 },
michael@0 1094
michael@0 1095 clearPalette: function() {
michael@0 1096 [this.removeProviderButton(p.origin) for (p of Social.providers)];
michael@0 1097 },
michael@0 1098
michael@0 1099 // should be called on enable of a provider
michael@0 1100 populatePalette: function() {
michael@0 1101 if (!Social.enabled) {
michael@0 1102 this.clearPalette();
michael@0 1103 return;
michael@0 1104 }
michael@0 1105
michael@0 1106 // create any buttons that do not exist yet if they have been persisted
michael@0 1107 // as a part of the UI (otherwise they belong in the palette).
michael@0 1108 for (let provider of Social.providers) {
michael@0 1109 let id = this.idFromOrigin(provider.origin);
michael@0 1110 this._createButton(id, provider);
michael@0 1111 }
michael@0 1112 }
michael@0 1113 }
michael@0 1114
michael@0 1115 let SocialStatusWidgetListener = {
michael@0 1116 _getNodeOrigin: function(aWidgetId) {
michael@0 1117 // we rely on the button id being the same as the widget.
michael@0 1118 let node = document.getElementById(aWidgetId);
michael@0 1119 if (!node)
michael@0 1120 return null
michael@0 1121 if (!node.classList.contains("social-status-button"))
michael@0 1122 return null
michael@0 1123 return node.getAttribute("origin");
michael@0 1124 },
michael@0 1125 onWidgetAdded: function(aWidgetId, aArea, aPosition) {
michael@0 1126 let origin = this._getNodeOrigin(aWidgetId);
michael@0 1127 if (origin)
michael@0 1128 SocialStatus.updateButton(origin);
michael@0 1129 },
michael@0 1130 onWidgetRemoved: function(aWidgetId, aPrevArea) {
michael@0 1131 let origin = this._getNodeOrigin(aWidgetId);
michael@0 1132 if (!origin)
michael@0 1133 return;
michael@0 1134 // When a widget is demoted to the palette ('removed'), it's visual
michael@0 1135 // style should change.
michael@0 1136 SocialStatus.updateButton(origin);
michael@0 1137 SocialStatus._removeFrame(origin);
michael@0 1138 }
michael@0 1139 }
michael@0 1140
michael@0 1141 SocialStatus = {
michael@0 1142 populateToolbarPalette: function() {
michael@0 1143 this._toolbarHelper.populatePalette();
michael@0 1144
michael@0 1145 for (let provider of Social.providers)
michael@0 1146 this.updateButton(provider.origin);
michael@0 1147 },
michael@0 1148
michael@0 1149 removeProvider: function(origin) {
michael@0 1150 this._removeFrame(origin);
michael@0 1151 this._toolbarHelper.removeProviderButton(origin);
michael@0 1152 },
michael@0 1153
michael@0 1154 reloadProvider: function(origin) {
michael@0 1155 let button = document.getElementById(this._toolbarHelper.idFromOrigin(origin));
michael@0 1156 if (button && button.getAttribute("open") == "true")
michael@0 1157 document.getElementById("social-notification-panel").hidePopup();
michael@0 1158 this._removeFrame(origin);
michael@0 1159 },
michael@0 1160
michael@0 1161 _removeFrame: function(origin) {
michael@0 1162 let notificationFrameId = "social-status-" + origin;
michael@0 1163 let frame = document.getElementById(notificationFrameId);
michael@0 1164 if (frame) {
michael@0 1165 SharedFrame.forgetGroup(frame.id);
michael@0 1166 frame.parentNode.removeChild(frame);
michael@0 1167 }
michael@0 1168 },
michael@0 1169
michael@0 1170 get _toolbarHelper() {
michael@0 1171 delete this._toolbarHelper;
michael@0 1172 this._toolbarHelper = new ToolbarHelper("social-status-button",
michael@0 1173 CreateSocialStatusWidget,
michael@0 1174 SocialStatusWidgetListener);
michael@0 1175 return this._toolbarHelper;
michael@0 1176 },
michael@0 1177
michael@0 1178 get _dynamicResizer() {
michael@0 1179 delete this._dynamicResizer;
michael@0 1180 this._dynamicResizer = new DynamicResizeWatcher();
michael@0 1181 return this._dynamicResizer;
michael@0 1182 },
michael@0 1183
michael@0 1184 // status panels are one-per button per-process, we swap the docshells between
michael@0 1185 // windows when necessary
michael@0 1186 _attachNotificatonPanel: function(aParent, aButton, provider) {
michael@0 1187 aParent.hidden = !SocialUI.enabled;
michael@0 1188 let notificationFrameId = "social-status-" + provider.origin;
michael@0 1189 let frame = document.getElementById(notificationFrameId);
michael@0 1190
michael@0 1191 // If the button was customized to a new location, we we'll destroy the
michael@0 1192 // iframe and start fresh.
michael@0 1193 if (frame && frame.parentNode != aParent) {
michael@0 1194 SharedFrame.forgetGroup(frame.id);
michael@0 1195 frame.parentNode.removeChild(frame);
michael@0 1196 frame = null;
michael@0 1197 }
michael@0 1198
michael@0 1199 if (!frame) {
michael@0 1200 frame = SharedFrame.createFrame(
michael@0 1201 notificationFrameId, /* frame name */
michael@0 1202 aParent, /* parent */
michael@0 1203 {
michael@0 1204 "type": "content",
michael@0 1205 "mozbrowser": "true",
michael@0 1206 "class": "social-panel-frame",
michael@0 1207 "id": notificationFrameId,
michael@0 1208 "tooltip": "aHTMLTooltip",
michael@0 1209 "context": "contentAreaContextMenu",
michael@0 1210 "flex": "1",
michael@0 1211
michael@0 1212 // work around bug 793057 - by making the panel roughly the final size
michael@0 1213 // we are more likely to have the anchor in the correct position.
michael@0 1214 "style": "width: " + PANEL_MIN_WIDTH + "px;",
michael@0 1215
michael@0 1216 "origin": provider.origin,
michael@0 1217 "src": provider.statusURL
michael@0 1218 }
michael@0 1219 );
michael@0 1220
michael@0 1221 if (frame.socialErrorListener)
michael@0 1222 frame.socialErrorListener.remove();
michael@0 1223 if (frame.docShell) {
michael@0 1224 frame.docShell.isActive = false;
michael@0 1225 Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
michael@0 1226 }
michael@0 1227 } else {
michael@0 1228 frame.setAttribute("origin", provider.origin);
michael@0 1229 SharedFrame.updateURL(notificationFrameId, provider.statusURL);
michael@0 1230 }
michael@0 1231 aButton.setAttribute("notificationFrameId", notificationFrameId);
michael@0 1232 },
michael@0 1233
michael@0 1234 updateButton: function(origin) {
michael@0 1235 let id = this._toolbarHelper.idFromOrigin(origin);
michael@0 1236 let widget = CustomizableUI.getWidget(id);
michael@0 1237 if (!widget)
michael@0 1238 return;
michael@0 1239 let button = widget.forWindow(window).node;
michael@0 1240 if (button) {
michael@0 1241 // we only grab the first notification, ignore all others
michael@0 1242 let place = CustomizableUI.getPlaceForItem(button);
michael@0 1243 let provider = Social._getProviderFromOrigin(origin);
michael@0 1244 let icons = provider.ambientNotificationIcons;
michael@0 1245 let iconNames = Object.keys(icons);
michael@0 1246 let notif = icons[iconNames[0]];
michael@0 1247
michael@0 1248 // The image and tooltip need to be updated for both
michael@0 1249 // ambient notification and profile changes.
michael@0 1250 let iconURL = provider.icon32URL || provider.iconURL;
michael@0 1251 let tooltiptext;
michael@0 1252 if (!notif || place == "palette") {
michael@0 1253 button.style.listStyleImage = "url(" + iconURL + ")";
michael@0 1254 button.setAttribute("badge", "");
michael@0 1255 button.setAttribute("aria-label", "");
michael@0 1256 button.setAttribute("tooltiptext", provider.name);
michael@0 1257 return;
michael@0 1258 }
michael@0 1259 button.style.listStyleImage = "url(" + (notif.iconURL || iconURL) + ")";
michael@0 1260 button.setAttribute("tooltiptext", notif.label || provider.name);
michael@0 1261
michael@0 1262 let badge = notif.counter || "";
michael@0 1263 button.setAttribute("badge", badge);
michael@0 1264 let ariaLabel = notif.label;
michael@0 1265 // if there is a badge value, we must use a localizable string to insert it.
michael@0 1266 if (badge)
michael@0 1267 ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
michael@0 1268 [ariaLabel, badge]);
michael@0 1269 button.setAttribute("aria-label", ariaLabel);
michael@0 1270 }
michael@0 1271 },
michael@0 1272
michael@0 1273 showPopup: function(aToolbarButton) {
michael@0 1274 // attach our notification panel if necessary
michael@0 1275 let origin = aToolbarButton.getAttribute("origin");
michael@0 1276 let provider = Social._getProviderFromOrigin(origin);
michael@0 1277
michael@0 1278 // if we're a slice in the hamburger, use that panel instead
michael@0 1279 let widgetGroup = CustomizableUI.getWidget(aToolbarButton.getAttribute("id"));
michael@0 1280 let widget = widgetGroup.forWindow(window);
michael@0 1281 let panel, showingEvent, hidingEvent;
michael@0 1282 let inMenuPanel = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
michael@0 1283 if (inMenuPanel) {
michael@0 1284 panel = document.getElementById("PanelUI-socialapi");
michael@0 1285 this._attachNotificatonPanel(panel, aToolbarButton, provider);
michael@0 1286 widget.node.setAttribute("closemenu", "none");
michael@0 1287 showingEvent = "ViewShowing";
michael@0 1288 hidingEvent = "ViewHiding";
michael@0 1289 } else {
michael@0 1290 panel = document.getElementById("social-notification-panel");
michael@0 1291 this._attachNotificatonPanel(panel, aToolbarButton, provider);
michael@0 1292 showingEvent = "popupshown";
michael@0 1293 hidingEvent = "popuphidden";
michael@0 1294 }
michael@0 1295 let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
michael@0 1296 let notificationFrame = document.getElementById(notificationFrameId);
michael@0 1297
michael@0 1298 let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
michael@0 1299 SharedFrame.setOwner(notificationFrameId, notificationFrame);
michael@0 1300
michael@0 1301 // Clear dimensions on all browsers so the panel size will
michael@0 1302 // only use the selected browser.
michael@0 1303 let frameIter = panel.firstElementChild;
michael@0 1304 while (frameIter) {
michael@0 1305 frameIter.collapsed = (frameIter != notificationFrame);
michael@0 1306 frameIter = frameIter.nextElementSibling;
michael@0 1307 }
michael@0 1308
michael@0 1309 function dispatchPanelEvent(name) {
michael@0 1310 let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
michael@0 1311 evt.initCustomEvent(name, true, true, {});
michael@0 1312 notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
michael@0 1313 }
michael@0 1314
michael@0 1315 // we only use a dynamic resizer when we're located the toolbar.
michael@0 1316 let dynamicResizer = inMenuPanel ? null : this._dynamicResizer;
michael@0 1317 panel.addEventListener(hidingEvent, function onpopuphiding() {
michael@0 1318 panel.removeEventListener(hidingEvent, onpopuphiding);
michael@0 1319 aToolbarButton.removeAttribute("open");
michael@0 1320 if (dynamicResizer)
michael@0 1321 dynamicResizer.stop();
michael@0 1322 notificationFrame.docShell.isActive = false;
michael@0 1323 dispatchPanelEvent("socialFrameHide");
michael@0 1324 });
michael@0 1325
michael@0 1326 panel.addEventListener(showingEvent, function onpopupshown() {
michael@0 1327 panel.removeEventListener(showingEvent, onpopupshown);
michael@0 1328 // This attribute is needed on both the button and the
michael@0 1329 // containing toolbaritem since the buttons on OS X have
michael@0 1330 // moz-appearance:none, while their container gets
michael@0 1331 // moz-appearance:toolbarbutton due to the way that toolbar buttons
michael@0 1332 // get combined on OS X.
michael@0 1333 let initFrameShow = () => {
michael@0 1334 notificationFrame.docShell.isActive = true;
michael@0 1335 notificationFrame.docShell.isAppTab = true;
michael@0 1336 if (dynamicResizer)
michael@0 1337 dynamicResizer.start(panel, notificationFrame);
michael@0 1338 dispatchPanelEvent("socialFrameShow");
michael@0 1339 };
michael@0 1340 if (!inMenuPanel)
michael@0 1341 aToolbarButton.setAttribute("open", "true");
michael@0 1342 if (notificationFrame.contentDocument &&
michael@0 1343 notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
michael@0 1344 initFrameShow();
michael@0 1345 } else {
michael@0 1346 // first time load, wait for load and dispatch after load
michael@0 1347 notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
michael@0 1348 notificationFrame.removeEventListener("load", panelBrowserOnload, true);
michael@0 1349 initFrameShow();
michael@0 1350 }, true);
michael@0 1351 }
michael@0 1352 });
michael@0 1353
michael@0 1354 if (inMenuPanel) {
michael@0 1355 PanelUI.showSubView("PanelUI-socialapi", widget.node,
michael@0 1356 CustomizableUI.AREA_PANEL);
michael@0 1357 } else {
michael@0 1358 let anchor = document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
michael@0 1359 // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
michael@0 1360 // handling from preventing it being opened in some cases.
michael@0 1361 setTimeout(function() {
michael@0 1362 panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
michael@0 1363 }, 0);
michael@0 1364 }
michael@0 1365 },
michael@0 1366
michael@0 1367 setPanelErrorMessage: function(aNotificationFrame) {
michael@0 1368 if (!aNotificationFrame)
michael@0 1369 return;
michael@0 1370
michael@0 1371 let src = aNotificationFrame.getAttribute("src");
michael@0 1372 aNotificationFrame.removeAttribute("src");
michael@0 1373 let origin = aNotificationFrame.getAttribute("origin");
michael@0 1374 aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
michael@0 1375 encodeURIComponent(src) + "&origin=" +
michael@0 1376 encodeURIComponent(origin),
michael@0 1377 null, null, null, null);
michael@0 1378 let panel = aNotificationFrame.parentNode;
michael@0 1379 sizeSocialPanelToContent(panel, aNotificationFrame);
michael@0 1380 },
michael@0 1381
michael@0 1382 };
michael@0 1383
michael@0 1384
michael@0 1385 /**
michael@0 1386 * SocialMarks
michael@0 1387 *
michael@0 1388 * Handles updates to toolbox and signals all buttons to update when necessary.
michael@0 1389 */
michael@0 1390 SocialMarks = {
michael@0 1391 update: function() {
michael@0 1392 // signal each button to update itself
michael@0 1393 let currentButtons = document.querySelectorAll('toolbarbutton[type="socialmark"]');
michael@0 1394 for (let elt of currentButtons)
michael@0 1395 elt.update();
michael@0 1396 },
michael@0 1397
michael@0 1398 updatePanelButtons: function() {
michael@0 1399 // querySelectorAll does not work on the menu panel the panel, so we have to
michael@0 1400 // do this the hard way.
michael@0 1401 let providers = SocialMarks.getProviders();
michael@0 1402 let panel = document.getElementById("PanelUI-popup");
michael@0 1403 for (let p of providers) {
michael@0 1404 let widgetId = SocialMarks._toolbarHelper.idFromOrigin(p.origin);
michael@0 1405 let widget = CustomizableUI.getWidget(widgetId);
michael@0 1406 if (!widget)
michael@0 1407 continue;
michael@0 1408 let node = widget.forWindow(window).node;
michael@0 1409 if (node)
michael@0 1410 node.update();
michael@0 1411 }
michael@0 1412 },
michael@0 1413
michael@0 1414 getProviders: function() {
michael@0 1415 // only rely on providers that the user has placed in the UI somewhere. This
michael@0 1416 // also means that populateToolbarPalette must be called prior to using this
michael@0 1417 // method, otherwise you get a big fat zero. For our use case with context
michael@0 1418 // menu's, this is ok.
michael@0 1419 let tbh = this._toolbarHelper;
michael@0 1420 return [p for (p of Social.providers) if (p.markURL &&
michael@0 1421 document.getElementById(tbh.idFromOrigin(p.origin)))];
michael@0 1422 },
michael@0 1423
michael@0 1424 populateContextMenu: function() {
michael@0 1425 // only show a selection if enabled and there is more than one
michael@0 1426 let providers = this.getProviders();
michael@0 1427
michael@0 1428 // remove all previous entries by class
michael@0 1429 let menus = [m for (m of document.getElementsByClassName("context-socialmarks"))];
michael@0 1430 [m.parentNode.removeChild(m) for (m of menus)];
michael@0 1431
michael@0 1432 let contextMenus = [
michael@0 1433 {
michael@0 1434 type: "link",
michael@0 1435 id: "context-marklinkMenu",
michael@0 1436 label: "social.marklinkMenu.label"
michael@0 1437 },
michael@0 1438 {
michael@0 1439 type: "page",
michael@0 1440 id: "context-markpageMenu",
michael@0 1441 label: "social.markpageMenu.label"
michael@0 1442 }
michael@0 1443 ];
michael@0 1444 for (let cfg of contextMenus) {
michael@0 1445 this._populateContextPopup(cfg, providers);
michael@0 1446 }
michael@0 1447 this.updatePanelButtons();
michael@0 1448 },
michael@0 1449
michael@0 1450 MENU_LIMIT: 3, // adjustable for testing
michael@0 1451 _populateContextPopup: function(menuInfo, providers) {
michael@0 1452 let menu = document.getElementById(menuInfo.id);
michael@0 1453 let popup = menu.firstChild;
michael@0 1454 for (let provider of providers) {
michael@0 1455 // We show up to MENU_LIMIT providers as single menuitems's at the top
michael@0 1456 // level of the context menu, if we have more than that, dump them *all*
michael@0 1457 // into the menu popup.
michael@0 1458 let mi = document.createElement("menuitem");
michael@0 1459 mi.setAttribute("oncommand", "gContextMenu.markLink(this.getAttribute('origin'));");
michael@0 1460 mi.setAttribute("origin", provider.origin);
michael@0 1461 mi.setAttribute("image", provider.iconURL);
michael@0 1462 if (providers.length <= this.MENU_LIMIT) {
michael@0 1463 // an extra class to make enable/disable easy
michael@0 1464 mi.setAttribute("class", "menuitem-iconic context-socialmarks context-mark"+menuInfo.type);
michael@0 1465 let menuLabel = gNavigatorBundle.getFormattedString(menuInfo.label, [provider.name]);
michael@0 1466 mi.setAttribute("label", menuLabel);
michael@0 1467 menu.parentNode.insertBefore(mi, menu);
michael@0 1468 } else {
michael@0 1469 mi.setAttribute("class", "menuitem-iconic context-socialmarks");
michael@0 1470 mi.setAttribute("label", provider.name);
michael@0 1471 popup.appendChild(mi);
michael@0 1472 }
michael@0 1473 }
michael@0 1474 },
michael@0 1475
michael@0 1476 populateToolbarPalette: function() {
michael@0 1477 this._toolbarHelper.populatePalette();
michael@0 1478 this.populateContextMenu();
michael@0 1479 },
michael@0 1480
michael@0 1481 removeProvider: function(origin) {
michael@0 1482 this._toolbarHelper.removeProviderButton(origin);
michael@0 1483 },
michael@0 1484
michael@0 1485 get _toolbarHelper() {
michael@0 1486 delete this._toolbarHelper;
michael@0 1487 this._toolbarHelper = new ToolbarHelper("social-mark-button", CreateSocialMarkWidget);
michael@0 1488 return this._toolbarHelper;
michael@0 1489 },
michael@0 1490
michael@0 1491 markLink: function(aOrigin, aUrl) {
michael@0 1492 // find the button for this provider, and open it
michael@0 1493 let id = this._toolbarHelper.idFromOrigin(aOrigin);
michael@0 1494 document.getElementById(id).markLink(aUrl);
michael@0 1495 }
michael@0 1496 };
michael@0 1497
michael@0 1498 })();

mercurial