Bug 765874 - Implement recommend/share button, r=gavin
authorJared Wein <jwein@mozilla.com>
Wed, 11 Jul 2012 18:31:19 -0700
changeset 104008 0e5b85c4fdde335c73928ac2b4f6d54906adb72b
parent 104007 30a4cd976842d78fffe99d201fd701fe516a3887
child 104009 567d5343e36328449b3c0dcb9182bd19480719d1
push idunknown
push userunknown
push dateunknown
reviewersgavin
bugs765874
milestone16.0a1
Bug 765874 - Implement recommend/share button, r=gavin
browser/base/content/browser-sets.inc
browser/base/content/browser-social.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/test/Makefile.in
browser/base/content/test/browser_shareButton.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/browser.properties
browser/modules/Makefile.in
browser/modules/Social.jsm
browser/themes/gnomestripe/browser.css
browser/themes/gnomestripe/jar.mn
browser/themes/gnomestripe/share-button-active.png
browser/themes/gnomestripe/share-button-shared.png
browser/themes/gnomestripe/share-button.png
browser/themes/pinstripe/browser.css
browser/themes/pinstripe/jar.mn
browser/themes/pinstripe/share-button-active.png
browser/themes/pinstripe/share-button-shared.png
browser/themes/pinstripe/share-button.png
browser/themes/winstripe/browser.css
browser/themes/winstripe/jar.mn
browser/themes/winstripe/share-button-active.png
browser/themes/winstripe/share-button-shared.png
browser/themes/winstripe/share-button.png
toolkit/components/social/SocialService.jsm
toolkit/components/social/test/xpcshell/test_SocialService.js
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -100,16 +100,18 @@
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
+    <command id="Social:SharePage" oncommand="SocialShareButton.sharePage();"/>
+    <command id="Social:UnsharePage" oncommand="SocialShareButton.unsharePage();"/>
   </commandset>
 
   <commandset id="placesCommands">
     <command id="Browser:ShowAllBookmarks"
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
   </commandset>
@@ -332,16 +334,17 @@
     <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
 #endif
     <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
 #ifdef XP_WIN
 # Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
 # overridden for other purposes there.
     <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
 #endif
+    <key id="sharePage" key="&sharePageCmd.commandkey;" command="Social:SharePage" modifiers="accel,shift"/>
 
 # don't use |command="Browser:Stop"|, ESC is being used to freeze animated gifs,
 # even if the stop button and menuitem are disabled (see Bug 284140)
     <key id="key_stop" keycode="VK_ESCAPE" oncommand="BrowserStop();"/>
 
 #ifdef XP_MACOSX
     <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" oncommand="BrowserStop();"/>
 #endif
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-social.js
@@ -0,0 +1,108 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+let SocialUI = {
+  // Called on delayed startup to initialize UI
+  init: function SocialUI_init() {
+    Services.obs.addObserver(this, "social:pref-changed", false);
+    Social.init(this._providerReady.bind(this));
+  },
+
+  // Called on window unload
+  uninit: function SocialUI_uninit() {
+    Services.obs.removeObserver(this, "social:pref-changed");
+  },
+
+  // Called when the social.enabled pref is changed
+  observe: function SocialUI_observe(aSubject, aTopic, aData) {
+    SocialShareButton.updateButtonEnabledState();
+  },
+
+  // Called once Social.jsm's provider has been set
+  _providerReady: function SocialUI_providerReady() {
+    SocialShareButton.init();
+  }
+}
+
+let SocialShareButton = {
+  init: function SSB_init() {
+    this.sharePopup.hidden = false;
+    this.updateButtonEnabledState();
+  },
+
+  get shareButton() {
+    return document.getElementById("share-button");
+  },
+  get sharePopup() {
+    return document.getElementById("editSharePopup");
+  },
+
+  dismissSharePopup: function SSB_dismissSharePopup() {
+    this.sharePopup.hidePopup();
+  },
+
+  updateButtonEnabledState: function SSB_updateButtonEnabledState() {
+    let shareButton = this.shareButton;
+    if (shareButton)
+      shareButton.hidden = !Social.provider || !Social.provider.enabled ||
+                           !Social.provider.port;
+  },
+
+  onClick: function SSB_onClick(aEvent) {
+    if (aEvent.button != 0)
+      return;
+
+    // Don't bubble to the textbox, to avoid unwanted selection of the address.
+    aEvent.stopPropagation();
+
+    this.sharePage();
+  },
+
+  panelShown: function SSB_panelShown(aEvent) {
+    let sharePopupOkButton = document.getElementById("editSharePopupOkButton");
+    if (sharePopupOkButton)
+      sharePopupOkButton.focus();
+  },
+
+  sharePage: function SSB_sharePage() {
+    let uri = gBrowser.currentURI;
+    if (!Social.isPageShared(uri)) {
+      Social.sharePage(uri);
+      this.updateShareState();
+    } else {
+      this.sharePopup.openPopup(this.shareButton, "bottomcenter topright");
+    }
+  },
+
+  unsharePage: function SSB_unsharePage() {
+    Social.unsharePage(gBrowser.currentURI);
+    this.updateShareState();
+    this.dismissSharePopup();
+  },
+
+  updateShareState: function SSB_updateShareState() {
+    let currentPageShared = Social.isPageShared(gBrowser.currentURI);
+
+    // Provide a11y-friendly notification of share.
+    let status = document.getElementById("share-button-status");
+    if (status) {
+      let statusString = currentPageShared ?
+                           gNavigatorBundle.getString("social.pageShared.label") : "";
+      status.setAttribute("value", statusString);
+    }
+
+    // Update the share button, if present
+    let shareButton = this.shareButton;
+    if (!shareButton)
+      return;
+
+    if (currentPageShared) {
+      shareButton.setAttribute("shared", "true");
+      shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.sharedtooltip"));
+    } else {
+      shareButton.removeAttribute("shared");
+      shareButton.setAttribute("tooltiptext", gNavigatorBundle.getString("social.shareButton.tooltip"));
+    }
+  }
+};
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -129,30 +129,37 @@ XPCOMUtils.defineLazyGetter(this, "Debug
 });
 
 XPCOMUtils.defineLazyGetter(this, "Tilt", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/Tilt.jsm", tmp);
   return new tmp.Tilt(window);
 });
 
+XPCOMUtils.defineLazyGetter(this, "Social", function() {
+  let tmp = {};
+  Cu.import("resource:///modules/Social.jsm", tmp);
+  return tmp.Social;
+});
+
 let gInitialPages = [
   "about:blank",
   "about:newtab",
   "about:home",
   "about:privatebrowsing",
   "about:sessionrestore"
 ];
 
 #include browser-addons.js
 #include browser-feeds.js
 #include browser-fullScreen.js
 #include browser-fullZoom.js
 #include browser-places.js
 #include browser-plugins.js
+#include browser-social.js
 #include browser-tabPreviews.js
 #include browser-tabview.js
 #include browser-thumbnails.js
 
 #ifdef MOZ_SERVICES_SYNC
 #include browser-syncui.js
 #endif
 
@@ -1241,16 +1248,17 @@ var gBrowserInit = {
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gFormSubmitObserver.init();
+    SocialUI.init();
     AddonManager.addAddonListener(AddonsMgrListener);
 
     gBrowser.addEventListener("pageshow", function(evt) { setTimeout(pageShowEventHandlers, 0, evt); }, true);
 
     // Ensure login manager is up and running.
     Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
 
     if (mustLoadSidebar) {
@@ -1612,16 +1620,17 @@ var gBrowserInit = {
       } catch (ex) {
         Cu.reportError(ex);
       }
 
       BrowserOffline.uninit();
       OfflineApps.uninit();
       IndexedDBPromptHelper.uninit();
       AddonManager.removeAddonListener(AddonsMgrListener);
+      SocialUI.uninit();
     }
 
     // Final window teardown, do this last.
     window.XULBrowserWindow.destroy();
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
           .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
@@ -3557,16 +3566,17 @@ function BrowserToolboxCustomizeDone(aTo
   UpdateUrlbarSearchSplitterState();
   setUrlAndSearchBarWidthForConditionalForwardButton();
 
   // Update the urlbar
   if (gURLBar) {
     URLBarSetURI();
     XULBrowserWindow.asyncUpdateUI();
     PlacesStarButton.updateState();
+    SocialShareButton.updateShareState();
   }
 
   TabsInTitlebar.allowedBy("customizing-toolbars", true);
 
   // Re-enable parts of the UI we disabled during the dialog
   var menubar = document.getElementById("main-menubar");
   for (var i = 0; i < menubar.childNodes.length; ++i)
     menubar.childNodes[i].setAttribute("disabled", false);
@@ -4035,16 +4045,17 @@ var XULBrowserWindow = {
         let uri = aLocationURI;
         try {
           uri = this._uriFixup.createExposableURI(uri);
         } catch (e) {}
         URLBarSetURI(uri);
 
         // Update starring UI
         PlacesStarButton.updateState();
+        SocialShareButton.updateShareState();
       }
 
       // Show or hide browser chrome based on the whitelist
       if (this.hideChromeForLocation(location)) {
         document.documentElement.setAttribute("disablechrome", "true");
       } else {
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
         if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -177,16 +177,55 @@
                 class="editBookmarkPanelBottomButton"
                 label="&editBookmark.done.label;"
                 default="true"
                 oncommand="StarUI.panel.hidePopup();"/>
 #endif
       </hbox>
     </panel>
 
+    <panel id="editSharePopup"
+           type="arrow"
+           orient="vertical"
+           ignorekeys="true"
+           hidden="true"
+           onpopupshown="SocialShareButton.panelShown(event);"
+           consumeoutsideclicks="true"
+           level="top">
+      <hbox id="editSharePopupBottomButtons" pack="end">
+#ifdef XP_UNIX
+        <button id="editSharePopupUndoButton"
+                class="editSharePopupBottomButton"
+                label="&social.sharePopup.undo.label;"
+                accesskey="&social.sharePopup.undo.accesskey;"
+                command="Social:UnsharePage"/>
+        <button id="editSharePopupOkButton"
+                class="editSharePopupBottomButton"
+                default="true"
+                autofocus="autofocus"
+                label="&social.sharePopup.ok.label;"
+                accesskey="&social.sharePopup.ok.accesskey;"
+                oncommand="SocialShareButton.dismissSharePopup();"/>
+#else
+        <button id="editSharePopupOkButton"
+                class="editSharePopupBottomButton"
+                default="true"
+                autofocus="autofocus"
+                label="&social.sharePopup.ok.label;"
+                accesskey="&social.sharePopup.ok.accesskey;"
+                oncommand="SocialShareButton.dismissSharePopup();"/>
+        <button id="editSharePopupUndoButton"
+                class="editSharePopupBottomButton"
+                label="&social.sharePopup.undo.label;"
+                accesskey="&social.sharePopup.undo.accesskey;"
+                command="Social:UnsharePage"/>
+#endif
+      </hbox>
+    </panel>
+
     <menupopup id="inspector-node-popup">
       <menuitem id="inspectorHTMLCopyInner"
                 label="&inspectorHTMLCopyInner.label;"
                 accesskey="&inspectorHTMLCopyInner.accesskey;"
                 command="Inspector:CopyInner"/>
       <menuitem id="inspectorHTMLCopyOuter"
                 label="&inspectorHTMLCopyOuter.label;"
                 accesskey="&inspectorHTMLCopyOuter.accesskey;"
@@ -502,16 +541,22 @@
           </box>
           <hbox id="urlbar-icons">
             <image id="page-report-button"
                    class="urlbar-icon"
                    hidden="true"
                    tooltiptext="&pageReportIcon.tooltip;"
                    onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
 
+            <label id="share-button-status" collapsed="true" role="status"/>
+            <image id="share-button"
+                   class="urlbar-icon"
+                   hidden="true"
+                   onclick="SocialShareButton.onClick(event);"/>
+
             <image id="star-button"
                    class="urlbar-icon"
                    onclick="PlacesStarButton.onClick(event);"/>
             <image id="go-button"
                    class="urlbar-icon"
                    tooltiptext="&goEndCap.tooltip;"
                    onclick="gURLBar.handleCommand(event);"/>
           </hbox>
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -149,16 +149,17 @@ endif
                  browser_bug710878.js \
                  browser_bug719271.js \
                  browser_bug724239.js \
                  browser_bug735471.js \
                  browser_bug743421.js \
                  browser_bug749738.js \
                  browser_bug763468.js \
                  browser_bug767836.js \
+                 browser_shareButton.js \
                  browser_canonizeURL.js \
                  browser_customize.js \
                  browser_findbarClose.js \
                  browser_homeDrop.js \
                  browser_keywordBookmarklets.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_customize_popupNotification.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_shareButton.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let prefName = "social.enabled",
+    shareButton,
+    sharePopup,
+    okButton,
+    undoButton;
+
+function test() {
+  waitForExplicitFinish();
+
+  // Need to load a non-empty page for the social share button to appear
+  let tab = gBrowser.selectedTab = gBrowser.addTab("about:", {skipAnimation: true});
+  tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+    tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+    executeSoon(tabLoaded);
+  }, true);
+
+  // Enable the service to start
+  Services.prefs.setBoolPref(prefName, true);
+
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref(prefName);
+    gBrowser.removeTab(tab);
+  });
+}
+
+function tabLoaded() {
+  ok(Social, "Social module loaded");
+
+  // If the UI is already active, run the test immediately, otherwise wait
+  // for initialization.
+  if (Social.provider) {
+    executeSoon(testInitial);
+  } else {
+    Services.obs.addObserver(function obs() {
+      Services.obs.removeObserver(obs, "test-social-ui-ready");
+      executeSoon(testInitial);
+    }, "test-social-ui-ready", false);
+  }
+}
+
+function testInitial() {
+  ok(Social.provider, "Social provider is active");
+  ok(Social.provider.enabled, "Social provider is enabled");
+  ok(Social.provider.port, "Social provider has a port to its FrameWorker");
+
+  shareButton = SocialShareButton.shareButton;
+  sharePopup = SocialShareButton.sharePopup;
+  ok(shareButton, "share button exists");
+  ok(sharePopup, "share popup exists");
+  ok(!sharePopup.hidden, "share popup is not hidden");
+
+  okButton = document.getElementById("editSharePopupOkButton");
+  undoButton = document.getElementById("editSharePopupUndoButton");
+
+  is(shareButton.hidden, false, "share button should be visible");
+
+  // Test clicking the share button
+  shareButton.addEventListener("click", function listener() {
+    shareButton.removeEventListener("click", listener);
+    is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked");
+    executeSoon(testSecondClick.bind(window, testPopupOKButton));
+  });
+  EventUtils.synthesizeMouseAtCenter(shareButton, {});
+}
+
+function testSecondClick(nextTest) {
+  sharePopup.addEventListener("popupshown", function listener() {
+    sharePopup.removeEventListener("popupshown", listener);
+    ok(true, "popup was shown after second click");
+    executeSoon(nextTest);
+  });
+  EventUtils.synthesizeMouseAtCenter(shareButton, {});
+}
+
+function testPopupOKButton() {
+  sharePopup.addEventListener("popuphidden", function listener() {
+    sharePopup.removeEventListener("popuphidden", listener);
+    is(shareButton.hasAttribute("shared"), true, "Share button should still have 'shared' attribute after OK button is clicked");
+    executeSoon(testSecondClick.bind(window, testPopupUndoButton));
+  });
+  EventUtils.synthesizeMouseAtCenter(okButton, {});
+}
+
+function testPopupUndoButton() {
+  sharePopup.addEventListener("popuphidden", function listener() {
+    sharePopup.removeEventListener("popuphidden", listener);
+    is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute after Undo button is clicked");
+    executeSoon(testShortcut);
+  });
+  EventUtils.synthesizeMouseAtCenter(undoButton, {});
+}
+
+function testShortcut() {
+  let keyTarget = window;
+  keyTarget.addEventListener("keyup", function listener() {
+    keyTarget.removeEventListener("keyup", listener);
+    executeSoon(checkShortcutWorked.bind(window, keyTarget));
+  });
+  EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
+}
+
+function checkShortcutWorked(keyTarget) {
+  is(shareButton.hasAttribute("shared"), true, "Share button should be in the 'shared' state after keyboard shortcut is used");
+
+  // Test a second invocation of the shortcut
+  sharePopup.addEventListener("popupshown", function listener() {
+    sharePopup.removeEventListener("popupshown", listener);
+    ok(true, "popup was shown after second use of keyboard shortcut");
+    executeSoon(checkOKButton);
+  });
+  EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
+}
+
+function checkOKButton() {
+  is(document.activeElement, okButton, "ok button should be focused by default");
+  checkNextInTabOrder(undoButton, function () {
+    checkNextInTabOrder(okButton, testCloseBySpace);
+  });
+}
+
+function checkNextInTabOrder(element, next) {
+  // This particular test doesn't really apply on Mac, since buttons aren't
+  // focusable by default.
+  if (navigator.platform.indexOf("Mac") != -1) {
+    executeSoon(next);
+    return;
+  }
+
+  function listener() {
+    element.removeEventListener("focus", listener);
+    is(document.activeElement, element, element.id + " should be next in tab order");
+    executeSoon(next);
+  }
+  element.addEventListener("focus", listener);
+  // Register a cleanup function to remove the listener in case this test fails
+  registerCleanupFunction(function () {
+    element.removeEventListener("focus", listener);
+  });
+  EventUtils.synthesizeKey("VK_TAB", {});
+}
+
+function testCloseBySpace() {
+  is(document.activeElement.id, okButton.id, "testCloseBySpace, the ok button should be focused");
+  sharePopup.addEventListener("popuphidden", function listener() {
+    sharePopup.removeEventListener("popuphidden", listener);
+    ok(true, "space closed the share popup");
+    executeSoon(testDisable);
+  });
+  EventUtils.synthesizeKey("VK_SPACE", {});
+}
+
+function testDisable() {
+  Services.prefs.setBoolPref(prefName, false);
+  is(shareButton.hidden, true, "Share button should be hidden when pref is disabled");
+  finish();
+}
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -110,16 +110,17 @@ These should match what Safari and other
 
 <!ENTITY closeWindow.label "Close Window">
 <!ENTITY closeWindow.accesskey "d">
 
 <!ENTITY bookmarksMenu.label "Bookmarks">
 <!ENTITY bookmarksMenu.accesskey "B">
 <!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
 <!ENTITY bookmarkThisPageCmd.commandkey "d">
+<!ENTITY sharePageCmd.commandkey "l">
 <!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
 <!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
 <!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
 <!ENTITY showAllBookmarks2.label "Show All Bookmarks">
 <!ENTITY unsortedBookmarksCmd.label "Unsorted Bookmarks">
 <!ENTITY bookmarksToolbarChevron.tooltip "Show more bookmarks">
 
 <!ENTITY backCmd.label                "Back">
@@ -646,8 +647,13 @@ just addresses the organization to follo
 <!-- LOCALIZATION NOTE (markupButton.arialabel): The markup button is the button
 located in front of the breadcrumbs display in the inspector toolbar. The button
 doesn't display any label, but exposes a label to screen-readers with "aria-label".
 -->
 <!ENTITY markupButton.arialabel          "Markup">
 <!-- LOCALIZATION NOTE (markupButton.accesskey): The key bound to the Markup panel's
 toolbar button -->
 <!ENTITY markupButton.accesskey          "M">
+
+<!ENTITY social.sharePopup.undo.label     "Unshare">
+<!ENTITY social.sharePopup.undo.accesskey "U">
+<!ENTITY social.sharePopup.ok.label       "OK">
+<!ENTITY social.sharePopup.ok.accesskey   "O">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -362,8 +362,12 @@ webapps.requestInstall = Do you want to 
 # LOCALIZATION NOTE (telemetryOptOutPrompt): %1$S and %3$S will be replaced by
 # brandFullName, and %2$S by the value of the toolkit.telemetry.server_owner preference.
 telemetryOptOutPrompt = %1$S sends information about performance, hardware, usage and customizations back to %2$S to help improve %3$S.
 
 # LOCALIZATION NOTE (fullscreen.entered): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).
 fullscreen.entered=%S is now fullscreen.
 # LOCALIZATION NOTE (fullscreen.rememberDecision): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).
 fullscreen.rememberDecision=Remember decision for %S
+
+social.shareButton.tooltip=Share this
+social.shareButton.sharedtooltip=You shared this
+social.pageShared.label=Page shared
--- a/browser/modules/Makefile.in
+++ b/browser/modules/Makefile.in
@@ -14,16 +14,17 @@ include $(topsrcdir)/config/config.mk
 TEST_DIRS += test
 
 EXTRA_JS_MODULES = \
 	openLocationLastURL.jsm \
 	NetworkPrioritizer.jsm \
 	NewTabUtils.jsm \
 	offlineAppCache.jsm \
 	TelemetryTimestamps.jsm \
+	Social.jsm \
 	webappsUI.jsm \
 	$(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows) 
 EXTRA_JS_MODULES += \
 	WindowsPreviewPerTab.jsm \
 	WindowsJumpLists.jsm \
 	$(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/modules/Social.jsm
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let EXPORTED_SYMBOLS = ["Social"];
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
+  "resource://gre/modules/SocialService.jsm");
+
+let Social = {
+  provider: null,
+  init: function Social_init(callback) {
+    if (this.provider) {
+      schedule(callback);
+      return;
+    }
+
+    // Eventually this might want to retrieve a specific provider, but for now
+    // just use the first available.
+    SocialService.getProviderList(function (providers) {
+      if (providers.length)
+        this.provider = providers[0];
+      callback();
+      Services.obs.notifyObservers(null, "test-social-ui-ready", "");
+    }.bind(this));
+  },
+
+  get enabled() {
+    return SocialService.enabled;
+  },
+
+  sendWorkerMessage: function Social_sendWorkerMessage(message) {
+    // Responses aren't handled yet because there is no actions to perform
+    // based on the response from the provider at this point.
+    if (this.provider && this.provider.port)
+      this.provider.port.postMessage(message);
+  },
+
+  // Sharing functionality
+  _getShareablePageUrl: function Social_getShareablePageUrl(aURI) {
+    let uri = aURI.clone();
+    try {
+      // Setting userPass on about:config throws.
+      uri.userPass = "";
+    } catch (e) {}
+    return uri.spec;
+  },
+
+  isPageShared: function Social_isPageShared(aURI) {
+    let url = this._getShareablePageUrl(aURI);
+    return this._sharedUrls.hasOwnProperty(url);
+  },
+
+  sharePage: function Social_sharePage(aURI) {
+    let url = this._getShareablePageUrl(aURI);
+    this._sharedUrls[url] = true;
+    this.sendWorkerMessage({
+      topic: "social.user-recommend",
+      data: { url: url }
+    });
+  },
+
+  unsharePage: function Social_unsharePage(aURI) {
+    let url = this._getShareablePageUrl(aURI);
+    delete this._sharedUrls[url];
+    this.sendWorkerMessage({
+      topic: "social.user-unrecommend",
+      data: { url: url }
+    });
+  },
+
+  _sharedUrls: {}
+};
+
+function schedule(callback) {
+  Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+}
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -1360,16 +1360,30 @@ richlistitem[type~="action"][actiontype=
   list-style-image: url("moz-icon://stock/gtk-stop?size=menu");
 }
 
 /* Popup blocker button */
 #page-report-button {
   list-style-image: url("chrome://browser/skin/Info.png");
 }
 
+/* social recommending panel */
+
+#share-button {
+  list-style-image: url(chrome://browser/skin/share-button.png);
+}
+
+#share-button:not([shared]):not([disabled]):hover {
+  list-style-image: url(chrome://browser/skin/share-button-active.png);
+}
+
+#share-button[shared] {
+  list-style-image: url(chrome://browser/skin/share-button-shared.png);
+}
+
 /* Star button */
 #star-button {
   padding: 1px;
   list-style-image: url("chrome://browser/skin/places/starPage.png");
 }
 
 #star-button[starred="true"] {
   list-style-image: url("chrome://browser/skin/places/pageStarred.png");
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -34,16 +34,19 @@ browser.jar:
   skin/classic/browser/pageInfo.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/Privacy-16.png
   skin/classic/browser/Privacy-48.png
   skin/classic/browser/searchbar.css                  (searchbar.css)
   skin/classic/browser/Secure.png
   skin/classic/browser/Security-broken.png
   skin/classic/browser/setDesktopBackground.css
+  skin/classic/browser/share-button.png
+  skin/classic/browser/share-button-active.png
+  skin/classic/browser/share-button-shared.png
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar-small.png
   skin/classic/browser/urlbar-arrow.png
   skin/classic/browser/downloads/buttons.png          (downloads/buttons.png)
   skin/classic/browser/downloads/download-glow.png    (downloads/download-glow.png)
   skin/classic/browser/downloads/download-notification.png (downloads/download-notification.png)
   skin/classic/browser/downloads/downloads.css        (downloads/downloads.css)
   skin/classic/browser/feeds/feedIcon.png             (feeds/feedIcon.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a87501e89b3112b507b9385d43d021e7700fb150
GIT binary patch
literal 1249
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFsjIP*rJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzNs7nBqrx>JiX%TiO^it=+6z+Se>#O)Si+-`yBO~LIJ6P$YWfsWBf
zi)fe-F!g|#@MH_*z!QFI9x$~R0h9HY+tH~E42+?kE{-7;w~~JR|8LK{G9vZAx|zZ}
z&Lm^Q2^=BcYbPcD`2OEOV0q&Yoh2DzH$NZb^jPa4rhJR_l=c0o|Nj5Z7Z<qCl9Z;9
zrf`bY>vr5{hLx@kG8{*iH~df*;6C`|qtt>OKbKlfxW#cq;TNBPKEtET6IlgP3pV_l
zDL)~K>xhE*9=1moD-wAscqhbvo+0UB%2a3?^1@o+H$$POK=&h4hq9y0iWwTaom&4f
z6|xANW-R0|n|_FQLS)f(36HhST5XM93U+))%nK?-a~i%hMF%=)33uo-eB@hkc0tP~
z&JN2)3+|5Mh8t{m8QcPjT-bw(*Eb(}XmDkNa=`S)m}1_J=ma)f?`tVXW=&`is&C{}
z<?di^1iJk*^G6c}F{V>DnKIcG?3ymK8Yme44bq>)y=~u#E1w<1o6R+ub62t_l`7P+
tA9;C0RPe;EMZ)J-F&B#HRW>j%Gt6|q@oRF+pXs1N$J5o%Wt~$(697@%rp*8V
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb8a11e81d899117cc02c924525c99182b7b8166
GIT binary patch
literal 1460
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm8pr7p`o#xv#YU_rJ<p#fs>hqvxOOuWoBsZX5``q)9aF-
zT$-DjR|3<Ug3#-NQ!gkfKy;@TWtOF;xE1B+DuBIgm5JLej=0?d(VK$XElxP~>H{64
zj~3A|Az<nOG2zJ;$bl#P)I4BnF9Ig(_8`;E3=B*fo-U3d6}P5LKA3&QLF8Dv+Q~Cz
zt9M+mjWAHQ75VtUQ~T)bu8tL=Ap$A=T@?ZRO()6(Uxvl*`N3Zi!gVc7C(gwsWE;Ef
z%d^cIlS|g_b}GM^GdX9{N8cMci*>fK$xWVFx!?SLP36JXrA)UDH1Br2a^u&fl(p;E
zR@eOh*8RjjG56=+PaVO<ahYvperqT49x&ussK0kB|00Jl<Jrd5C9(bAPg*7LZ;R_%
z&1NcgL*caWfkg^k!e2{OCO=y~bxyMNcXl&gnd_2En6DRFYeewAJFxG{f7bfy;M<zc
zCft+UAD*+l@%LrenZq?)MM{_WLpHsAeyyb>b!t#@$YF+dhV6`bYY%oZ&Cz>sEj;sa
z>|?fzC$@69ivFM8uOt;4zPzF@+_3*+5z}(pj)%XJZ0|>Yln*$@$5OX^)7ur5-l|dD
zN`7YQKT4RQ{9RAl96a60lw!ZRy!ni~iMOxm^iAbmECpI`Cs!zWNjwzV<A0p%$Q%o8
z=SoK5Ka48-4@j{Udjy{0KI#AIcKw0kMKdJNOgW^`^L$mqljT30CDMh2_sY+^SaJ7;
zE3cKN9QP6b<E~3)h`PRL{A@JA!}S*POe42VHj^WchbJsOB{6%;^W3WzLLqXedY_h=
z*qeQCi>nAW-0|D^Vcpy8vk~Qv@1FkP-m>@oEwSADA0{SRTY9j1YI?K>38%VmO5OkV
zI+OW_g$vJ@Jk*f=8x>i0JR_rKg{ob*cfyUZxCh<!v8Lz#>K<TVTJfhVjODO5s2ufl
L^>bP0l+XkK)?pmz
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..86bd5ec70add448cfa5647e9448d9bb2ea5349a9
GIT binary patch
literal 1245
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFp{uczrJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzKr7nBqrx>JiX%TiO^it=+6z+Se>#OW4iH{5Q4=uN@x76Y7m^?{Dj
zM~i5f5HR(CnDAr^<iHbtY927P7Xg#C@buT085kG?JzX3_DsD}=Z0~(IK&1WQl`aJi
zr-NIw?HX1$apbvbSa`6rvbS>Oxn6N}5p>c08ZX>D<(gE;ktFS=9cBs^mYq%a9R#c_
zRX^^X<k5ELf=q?Q+r8%P@sq*|W5YYW&7*5AMCVsenXz>C_sn(K$#FIBdy6-gU6ffK
z%X)p6U%<Niq8)!)_*Gp0tP_&CF2%g1IOSs#U(mn0Ck=~Vt@X-w`r;qrI^%)l%ZBDt
zXI0L&czkG;%%34-xuB$M?vmVuv(djC*;sj8`81@aYrfa?ukbcoHt|?U#HIg?)w?Wx
zvMbvEMjbT1m%9Decd7ENRp(0Q3dJQlH1~@An%K^&>Jf6Ne*VE*mMc4-UpS_$qd(K*
zbDM5YNAc<I&7BwIg`~GVwx~?Gcu?V#$3}B6`DRt$2iLzPrrJ+<kXV|jC;W>i<!J3X
r=cDFgD(#E@@0#uE?CQ7ezxDwJ`%cC;j^?6EKn0GctDnm{r-UW|0obvI
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -1237,16 +1237,30 @@ window[tabsontop="false"] richlistitem[t
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #page-report-button:hover:active,
 #page-report-button[open="true"] {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
+/* social recommending panel */
+
+#share-button {
+  list-style-image: url(chrome://browser/skin/share-button.png);
+}
+
+#share-button:not([shared]):not([disabled]):hover {
+  list-style-image: url(chrome://browser/skin/share-button-active.png);
+}
+
+#share-button[shared] {
+  list-style-image: url(chrome://browser/skin/share-button-shared.png);
+}
+
 /* STAR BUTTON */
 #star-button {
   list-style-image: url("chrome://browser/skin/places/star-icons.png");
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #star-button:hover:active,
 #star-button[starred="true"] {
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -63,16 +63,19 @@ browser.jar:
   skin/classic/browser/feeds/videoFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png            (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png            (feeds/feedIcon16.png)
   skin/classic/browser/newtab/newTab.css                    (newtab/newTab.css)
   skin/classic/browser/newtab/controls.png                  (newtab/controls.png)
   skin/classic/browser/newtab/noise.png                     (newtab/noise.png)
   skin/classic/browser/setDesktopBackground.css
+  skin/classic/browser/share-button.png
+  skin/classic/browser/share-button-active.png
+  skin/classic/browser/share-button-shared.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/places.css                    (places/places.css)
 * skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/query.png                     (places/query.png)
   skin/classic/browser/places/bookmarksMenu.png             (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png          (places/bookmarksToolbar.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a87501e89b3112b507b9385d43d021e7700fb150
GIT binary patch
literal 1249
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFsjIP*rJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzNs7nBqrx>JiX%TiO^it=+6z+Se>#O)Si+-`yBO~LIJ6P$YWfsWBf
zi)fe-F!g|#@MH_*z!QFI9x$~R0h9HY+tH~E42+?kE{-7;w~~JR|8LK{G9vZAx|zZ}
z&Lm^Q2^=BcYbPcD`2OEOV0q&Yoh2DzH$NZb^jPa4rhJR_l=c0o|Nj5Z7Z<qCl9Z;9
zrf`bY>vr5{hLx@kG8{*iH~df*;6C`|qtt>OKbKlfxW#cq;TNBPKEtET6IlgP3pV_l
zDL)~K>xhE*9=1moD-wAscqhbvo+0UB%2a3?^1@o+H$$POK=&h4hq9y0iWwTaom&4f
z6|xANW-R0|n|_FQLS)f(36HhST5XM93U+))%nK?-a~i%hMF%=)33uo-eB@hkc0tP~
z&JN2)3+|5Mh8t{m8QcPjT-bw(*Eb(}XmDkNa=`S)m}1_J=ma)f?`tVXW=&`is&C{}
z<?di^1iJk*^G6c}F{V>DnKIcG?3ymK8Yme44bq>)y=~u#E1w<1o6R+ub62t_l`7P+
tA9;C0RPe;EMZ)J-F&B#HRW>j%Gt6|q@oRF+pXs1N$J5o%Wt~$(697@%rp*8V
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb8a11e81d899117cc02c924525c99182b7b8166
GIT binary patch
literal 1460
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm8pr7p`o#xv#YU_rJ<p#fs>hqvxOOuWoBsZX5``q)9aF-
zT$-DjR|3<Ug3#-NQ!gkfKy;@TWtOF;xE1B+DuBIgm5JLej=0?d(VK$XElxP~>H{64
zj~3A|Az<nOG2zJ;$bl#P)I4BnF9Ig(_8`;E3=B*fo-U3d6}P5LKA3&QLF8Dv+Q~Cz
zt9M+mjWAHQ75VtUQ~T)bu8tL=Ap$A=T@?ZRO()6(Uxvl*`N3Zi!gVc7C(gwsWE;Ef
z%d^cIlS|g_b}GM^GdX9{N8cMci*>fK$xWVFx!?SLP36JXrA)UDH1Br2a^u&fl(p;E
zR@eOh*8RjjG56=+PaVO<ahYvperqT49x&ussK0kB|00Jl<Jrd5C9(bAPg*7LZ;R_%
z&1NcgL*caWfkg^k!e2{OCO=y~bxyMNcXl&gnd_2En6DRFYeewAJFxG{f7bfy;M<zc
zCft+UAD*+l@%LrenZq?)MM{_WLpHsAeyyb>b!t#@$YF+dhV6`bYY%oZ&Cz>sEj;sa
z>|?fzC$@69ivFM8uOt;4zPzF@+_3*+5z}(pj)%XJZ0|>Yln*$@$5OX^)7ur5-l|dD
zN`7YQKT4RQ{9RAl96a60lw!ZRy!ni~iMOxm^iAbmECpI`Cs!zWNjwzV<A0p%$Q%o8
z=SoK5Ka48-4@j{Udjy{0KI#AIcKw0kMKdJNOgW^`^L$mqljT30CDMh2_sY+^SaJ7;
zE3cKN9QP6b<E~3)h`PRL{A@JA!}S*POe42VHj^WchbJsOB{6%;^W3WzLLqXedY_h=
z*qeQCi>nAW-0|D^Vcpy8vk~Qv@1FkP-m>@oEwSADA0{SRTY9j1YI?K>38%VmO5OkV
zI+OW_g$vJ@Jk*f=8x>i0JR_rKg{ob*cfyUZxCh<!v8Lz#>K<TVTJfhVjODO5s2ufl
L^>bP0l+XkK)?pmz
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..86bd5ec70add448cfa5647e9448d9bb2ea5349a9
GIT binary patch
literal 1245
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFp{uczrJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzKr7nBqrx>JiX%TiO^it=+6z+Se>#OW4iH{5Q4=uN@x76Y7m^?{Dj
zM~i5f5HR(CnDAr^<iHbtY927P7Xg#C@buT085kG?JzX3_DsD}=Z0~(IK&1WQl`aJi
zr-NIw?HX1$apbvbSa`6rvbS>Oxn6N}5p>c08ZX>D<(gE;ktFS=9cBs^mYq%a9R#c_
zRX^^X<k5ELf=q?Q+r8%P@sq*|W5YYW&7*5AMCVsenXz>C_sn(K$#FIBdy6-gU6ffK
z%X)p6U%<Niq8)!)_*Gp0tP_&CF2%g1IOSs#U(mn0Ck=~Vt@X-w`r;qrI^%)l%ZBDt
zXI0L&czkG;%%34-xuB$M?vmVuv(djC*;sj8`81@aYrfa?ukbcoHt|?U#HIg?)w?Wx
zvMbvEMjbT1m%9Decd7ENRp(0Q3dJQlH1~@An%K^&>Jf6Ne*VE*mMc4-UpS_$qd(K*
zbDM5YNAc<I&7BwIg`~GVwx~?Gcu?V#$3}B6`DRt$2iLzPrrJ+<kXV|jC;W>i<!J3X
r=cDFgD(#E@@0#uE?CQ7ezxDwJ`%cC;j^?6EKn0GctDnm{r-UW|0obvI
--- a/browser/themes/winstripe/browser.css
+++ b/browser/themes/winstripe/browser.css
@@ -1621,16 +1621,30 @@ richlistitem[type~="action"][actiontype=
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #page-report-button:hover:active,
 #page-report-button[open="true"] {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
+/* social recommending panel */
+
+#share-button {
+  list-style-image: url(chrome://browser/skin/share-button.png);
+}
+
+#share-button:not([shared]):not([disabled]):hover {
+  list-style-image: url(chrome://browser/skin/share-button-active.png);
+}
+
+#share-button[shared] {
+  list-style-image: url(chrome://browser/skin/share-button-shared.png);
+}
+
 /* star button */
 
 #star-button {
   list-style-image: url("chrome://browser/skin/places/bookmark.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 #star-button:hover {
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -45,16 +45,19 @@ browser.jar:
         skin/classic/browser/Secure24.png                            (Secure24.png)
         skin/classic/browser/Toolbar.png                             (Toolbar.png)
         skin/classic/browser/Toolbar-inverted.png
         skin/classic/browser/toolbarbutton-dropdown-arrow.png
         skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png
 *       skin/classic/browser/searchbar.css                           (searchbar.css)
         skin/classic/browser/searchbar-dropdown-arrow.png
         skin/classic/browser/setDesktopBackground.css
+        skin/classic/browser/share-button.png
+        skin/classic/browser/share-button-active.png
+        skin/classic/browser/share-button-shared.png
         skin/classic/browser/menu-back.png                           (menu-back.png)
         skin/classic/browser/menu-forward.png                        (menu-forward.png)
         skin/classic/browser/monitor.png
         skin/classic/browser/monitor_16-10.png
         skin/classic/browser/urlbar-arrow.png
         skin/classic/browser/urlbar-popup-blocked.png
         skin/classic/browser/urlbar-history-dropmarker.png
         skin/classic/browser/downloads/buttons.png                   (downloads/buttons.png)
@@ -243,16 +246,19 @@ browser.jar:
         skin/classic/aero/browser/Secure24.png                       (Secure24-aero.png)
         skin/classic/aero/browser/Toolbar.png
         skin/classic/aero/browser/Toolbar-inverted.png
         skin/classic/aero/browser/toolbarbutton-dropdown-arrow.png
         skin/classic/aero/browser/toolbarbutton-dropdown-arrow-inverted.png
 *       skin/classic/aero/browser/searchbar.css                      (searchbar.css)
         skin/classic/aero/browser/searchbar-dropdown-arrow.png       (searchbar-dropdown-arrow-aero.png)
         skin/classic/aero/browser/setDesktopBackground.css
+        skin/classic/aero/browser/share-button.png
+        skin/classic/aero/browser/share-button-active.png
+        skin/classic/aero/browser/share-button-shared.png
         skin/classic/aero/browser/menu-back.png                      (menu-back-aero.png)
         skin/classic/aero/browser/menu-forward.png                   (menu-forward-aero.png)
         skin/classic/aero/browser/monitor.png
         skin/classic/aero/browser/monitor_16-10.png
         skin/classic/aero/browser/urlbar-arrow.png
         skin/classic/aero/browser/urlbar-popup-blocked.png
         skin/classic/aero/browser/urlbar-history-dropmarker.png
         skin/classic/aero/browser/downloads/buttons.png              (downloads/buttons-aero.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a87501e89b3112b507b9385d43d021e7700fb150
GIT binary patch
literal 1249
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFsjIP*rJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzNs7nBqrx>JiX%TiO^it=+6z+Se>#O)Si+-`yBO~LIJ6P$YWfsWBf
zi)fe-F!g|#@MH_*z!QFI9x$~R0h9HY+tH~E42+?kE{-7;w~~JR|8LK{G9vZAx|zZ}
z&Lm^Q2^=BcYbPcD`2OEOV0q&Yoh2DzH$NZb^jPa4rhJR_l=c0o|Nj5Z7Z<qCl9Z;9
zrf`bY>vr5{hLx@kG8{*iH~df*;6C`|qtt>OKbKlfxW#cq;TNBPKEtET6IlgP3pV_l
zDL)~K>xhE*9=1moD-wAscqhbvo+0UB%2a3?^1@o+H$$POK=&h4hq9y0iWwTaom&4f
z6|xANW-R0|n|_FQLS)f(36HhST5XM93U+))%nK?-a~i%hMF%=)33uo-eB@hkc0tP~
z&JN2)3+|5Mh8t{m8QcPjT-bw(*Eb(}XmDkNa=`S)m}1_J=ma)f?`tVXW=&`is&C{}
z<?di^1iJk*^G6c}F{V>DnKIcG?3ymK8Yme44bq>)y=~u#E1w<1o6R+ub62t_l`7P+
tA9;C0RPe;EMZ)J-F&B#HRW>j%Gt6|q@oRF+pXs1N$J5o%Wt~$(697@%rp*8V
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cb8a11e81d899117cc02c924525c99182b7b8166
GIT binary patch
literal 1460
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm8pr7p`o#xv#YU_rJ<p#fs>hqvxOOuWoBsZX5``q)9aF-
zT$-DjR|3<Ug3#-NQ!gkfKy;@TWtOF;xE1B+DuBIgm5JLej=0?d(VK$XElxP~>H{64
zj~3A|Az<nOG2zJ;$bl#P)I4BnF9Ig(_8`;E3=B*fo-U3d6}P5LKA3&QLF8Dv+Q~Cz
zt9M+mjWAHQ75VtUQ~T)bu8tL=Ap$A=T@?ZRO()6(Uxvl*`N3Zi!gVc7C(gwsWE;Ef
z%d^cIlS|g_b}GM^GdX9{N8cMci*>fK$xWVFx!?SLP36JXrA)UDH1Br2a^u&fl(p;E
zR@eOh*8RjjG56=+PaVO<ahYvperqT49x&ussK0kB|00Jl<Jrd5C9(bAPg*7LZ;R_%
z&1NcgL*caWfkg^k!e2{OCO=y~bxyMNcXl&gnd_2En6DRFYeewAJFxG{f7bfy;M<zc
zCft+UAD*+l@%LrenZq?)MM{_WLpHsAeyyb>b!t#@$YF+dhV6`bYY%oZ&Cz>sEj;sa
z>|?fzC$@69ivFM8uOt;4zPzF@+_3*+5z}(pj)%XJZ0|>Yln*$@$5OX^)7ur5-l|dD
zN`7YQKT4RQ{9RAl96a60lw!ZRy!ni~iMOxm^iAbmECpI`Cs!zWNjwzV<A0p%$Q%o8
z=SoK5Ka48-4@j{Udjy{0KI#AIcKw0kMKdJNOgW^`^L$mqljT30CDMh2_sY+^SaJ7;
zE3cKN9QP6b<E~3)h`PRL{A@JA!}S*POe42VHj^WchbJsOB{6%;^W3WzLLqXedY_h=
z*qeQCi>nAW-0|D^Vcpy8vk~Qv@1FkP-m>@oEwSADA0{SRTY9j1YI?K>38%VmO5OkV
zI+OW_g$vJ@Jk*f=8x>i0JR_rKg{ob*cfyUZxCh<!v8Lz#>K<TVTJfhVjODO5s2ufl
L^>bP0l+XkK)?pmz
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..86bd5ec70add448cfa5647e9448d9bb2ea5349a9
GIT binary patch
literal 1245
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk?
zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3m8Da#=fE;F*!T6L?J0P
zJu}Z%>HY5gN(z}Nwo2iqz6QPp&Z!xh9#uuD!Bu`C$yM3OmMKd1b_zBXRzL%CQ%e#R
zDspr3imfVamB8j&0ofp7eI*63l9Fs&C5WRUd;=7m^NUgyO!W+OlMT!a6wD0u42@09
z&CPWbj0_A7^bL&k4UKdS&8>`$tPBhkpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$
zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`<jAm(=3qqRfJl%=|nB5I->~zqG_w
zNeSkK+yc0<dBxCR0tT3VMPh-zp`L+0l0si!{7Q3k;i`*Ef>P7)t1b?yEJ)Q4N-fSW
zElLJPT$(aSbAgp}QEFmIeo;t%ehw@Y12XbU@{2R_3lyA#O%;3-lQZ)`e6V_7Un|eN
z;*!L?<Wx@=TP2`~US?*Bm9e3@o2iqFp{uczrJ<p#fs>hqvxOOuWd@Wma&d#{b;(aI
z%}vcKf$2>_=rzKr7nBqrx>JiX%TiO^it=+6z+Se>#OW4iH{5Q4=uN@x76Y7m^?{Dj
zM~i5f5HR(CnDAr^<iHbtY927P7Xg#C@buT085kG?JzX3_DsD}=Z0~(IK&1WQl`aJi
zr-NIw?HX1$apbvbSa`6rvbS>Oxn6N}5p>c08ZX>D<(gE;ktFS=9cBs^mYq%a9R#c_
zRX^^X<k5ELf=q?Q+r8%P@sq*|W5YYW&7*5AMCVsenXz>C_sn(K$#FIBdy6-gU6ffK
z%X)p6U%<Niq8)!)_*Gp0tP_&CF2%g1IOSs#U(mn0Ck=~Vt@X-w`r;qrI^%)l%ZBDt
zXI0L&czkG;%%34-xuB$M?vmVuv(djC*;sj8`81@aYrfa?ukbcoHt|?U#HIg?)w?Wx
zvMbvEMjbT1m%9Decd7ENRp(0Q3dJQlH1~@An%K^&>Jf6Ne*VE*mMc4-UpS_$qd(K*
zbDM5YNAc<I&7BwIg`~GVwx~?Gcu?V#$3}B6`DRt$2iLzPrrJ+<kXV|jC;W>i<!J3X
r=cDFgD(#E@@0#uE?CQ7ezxDwJ`%cC;j^?6EKn0GctDnm{r-UW|0obvI
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -64,16 +64,17 @@ const SocialService = {
       return;
 
     Services.prefs.setBoolPref("social.enabled", enable);
     this._setEnabled(enable);
   },
   _setEnabled: function _setEnabled(enable) {
     SocialServiceInternal.providerArray.forEach(function (p) p.enabled = enable);
     SocialServiceInternal.enabled = enable;
+    Services.obs.notifyObservers(null, "social:pref-changed", enable ? "enabled" : "disabled");
   },
 
   // Returns a single provider object with the specified origin.
   getProvider: function getProvider(origin, onDone) {
     schedule((function () {
       onDone(SocialServiceInternal.providers[origin] || null);
     }).bind(this));
   },
--- a/toolkit/components/social/test/xpcshell/test_SocialService.js
+++ b/toolkit/components/social/test/xpcshell/test_SocialService.js
@@ -61,22 +61,37 @@ function testGetProviderList(manifests, 
 function testEnabled(manifests, next) {
   let providers = yield SocialService.getProviderList(next);
   do_check_true(providers.length >= manifests.length);
   do_check_true(SocialService.enabled);
   providers.forEach(function (provider) {
     do_check_true(provider.enabled);
   });
 
+  let notificationDisabledCorrect = false;
+  Services.obs.addObserver(function obs1(subj, topic, data) {
+    Services.obs.removeObserver(obs1, "social:pref-changed");
+    notificationDisabledCorrect = data == "disabled";
+  }, "social:pref-changed", false);
+
   SocialService.enabled = false;
+  do_check_true(notificationDisabledCorrect);
   do_check_true(!Services.prefs.getBoolPref("social.enabled"));
   do_check_true(!SocialService.enabled);
   providers.forEach(function (provider) {
     do_check_true(!provider.enabled);
   });
 
   // Check that setting the pref directly updates things accordingly
+  let notificationEnabledCorrect = false;
+  Services.obs.addObserver(function obs2(subj, topic, data) {
+    Services.obs.removeObserver(obs2, "social:pref-changed");
+    notificationEnabledCorrect = data == "enabled";
+  }, "social:pref-changed", false);
+
   Services.prefs.setBoolPref("social.enabled", true);
+
+  do_check_true(notificationEnabledCorrect);
   do_check_true(SocialService.enabled);
   providers.forEach(function (provider) {
     do_check_true(provider.enabled);
   });
 }