Backed out 7 changesets (bug 1289549) for functional UI failures a=backout

Backed out changeset 32ff5490a900 (bug 1289549)
Backed out changeset 0a62241f9774 (bug 1289549)
Backed out changeset b6e3d77671f7 (bug 1289549)
Backed out changeset 506846cb7c35 (bug 1289549)
Backed out changeset efdb25f69c2c (bug 1289549)
Backed out changeset 6f8b50b7a92a (bug 1289549)
Backed out changeset 1c61346368e5 (bug 1289549)

--HG--
rename : browser/modules/SocialService.jsm => toolkit/components/social/SocialService.jsm
rename : browser/modules/test/unit/social/test_SocialService.js => toolkit/components/social/test/xpcshell/test_SocialService.js
rename : browser/modules/test/unit/social/test_SocialServiceMigration21.js => toolkit/components/social/test/xpcshell/test_SocialServiceMigration21.js
rename : browser/modules/test/unit/social/test_SocialServiceMigration22.js => toolkit/components/social/test/xpcshell/test_SocialServiceMigration22.js
rename : browser/modules/test/unit/social/test_SocialServiceMigration29.js => toolkit/components/social/test/xpcshell/test_SocialServiceMigration29.js
This commit is contained in:
Wes Kocher 2016-08-17 09:18:14 -07:00
Родитель f90f3d11fd
Коммит 8738cfff5d
82 изменённых файлов: 7045 добавлений и 177 удалений

Просмотреть файл

@ -93,6 +93,10 @@
label="&saveLinkCmd.label;"
accesskey="&saveLinkCmd.accesskey;"
oncommand="gContextMenu.saveLink();"/>
<menu id="context-marklinkMenu" label="&social.marklinkMenu.label;"
accesskey="&social.marklinkMenu.accesskey;">
<menupopup/>
</menu>
<menuitem id="context-copyemail"
label="&copyEmailCmd.label;"
accesskey="&copyEmailCmd.accesskey;"
@ -287,6 +291,10 @@
<menupopup id="context-sendpagetodevice-popup"
onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
</menu>
<menu id="context-markpageMenu" label="&social.markpageMenu.label;"
accesskey="&social.markpageMenu.accesskey;">
<menupopup/>
</menu>
<menuseparator id="context-sep-viewbgimage"/>
<menuitem id="context-viewbgimage"
label="&viewBGImageCmd.label;"

Просмотреть файл

@ -220,6 +220,9 @@
<menuitem id="menu_tabsSidebar"
observes="viewTabsSidebar"
label="&syncedTabs.sidebar.label;"/>
<!-- Service providers with sidebars are inserted between these two menuseperators -->
<menuseparator hidden="true"/>
<menuseparator class="social-provider-menu" hidden="true"/>
</menupopup>
</menu>
<menuseparator/>

Просмотреть файл

@ -107,7 +107,10 @@
<command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
<command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
<command id="Social:SharePage" oncommand="SocialShare.sharePage();"/>
<command id="Social:ToggleSidebar" oncommand="SocialSidebar.toggleSidebar();" hidden="true"/>
<command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
<command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
<command id="Chat:Focus" oncommand="Cu.import('resource:///modules/Chat.jsm', {}).Chat.focus(window);"/>
</commandset>
<commandset id="placesCommands">
@ -118,7 +121,7 @@
</commandset>
<broadcasterset id="mainBroadcasterSet">
<broadcaster id="Social:PageShareable" disabled="true"/>
<broadcaster id="Social:PageShareOrMark" disabled="true"/>
<broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
oncommand="SidebarUI.toggle('viewBookmarksSidebar');"/>
@ -178,6 +181,7 @@
sidebarurl="chrome://browser/content/syncedtabs/sidebar.xhtml"
oncommand="SidebarUI.toggle('viewTabsSidebar');"/>
<broadcaster id="workOfflineMenuitemState"/>
<broadcaster id="socialSidebarBroadcaster" hidden="true"/>
<broadcaster id="devtoolsMenuBroadcaster_PageSource"
label="&pageSourceCmd.label;"
@ -312,6 +316,17 @@
<key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
#endif
<!--<key id="markPage" key="&markPageCmd.commandkey;" command="Social:TogglePageMark" modifiers="accel,shift"/>-->
<key id="focusChatBar" key="&social.chatBar.commandkey;" command="Chat:Focus"
#ifdef XP_MACOSX
# Sadly the devtools uses shift-accel-c on non-mac and alt-accel-c everywhere else
# So we just use the other
modifiers="accel,shift"
#else
modifiers="accel,alt"
#endif
/>
<key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
#ifdef XP_MACOSX

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -19,6 +19,30 @@
%endif
}
/* These values are chosen to keep the Loop detached chat window from
* getting too small. When it's too small, three bad things happen:
*
* - It looks terrible
* - It's not really usable
* - It's possible for the user to be transmitting video that's cropped by the
* the edge of the window, so that they're not aware of it, which is a
* privacy problem
*
* Note that if the chat window grows more users than Loop who want this
* ability, we'll need to generalize. A partial patch for this is in
* bug 1112264.
*/
#chat-window {
/*
* In some ideal world, we'd have a simple way to express "block resizing
* along any dimension beyond the point at which an overflow event would
* occur". But none of -moz-{fit,max,min}-content do what we want here. So..
*/
min-width: 260px;
min-height: 315px;
}
#main-window[customize-entered] {
min-width: -moz-fit-content;
}
@ -888,6 +912,10 @@ html|*#gcli-output-frame,
transition: none;
}
toolbarbutton[type="socialmark"] {
-moz-binding: url("chrome://browser/content/socialmarks.xml#toolbarbutton-marks");
}
panelview > .social-panel-frame {
width: auto;
height: auto;
@ -898,6 +926,77 @@ notification[value="translation"] {
-moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
}
/* Social */
/* Note the chatbox 'width' values are duplicated in socialchat.xml */
chatbox {
-moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
transition: height 150ms ease-out, width 150ms ease-out;
height: 290px;
width: 300px; /* CHAT_WIDTH_OPEN in socialchat.xml */
}
chatbox[customSize] {
width: 350px; /* CHAT_WIDTH_OPEN_ALT in socialchat.xml */
}
#chat-window[customSize] {
min-width: 350px;
}
chatbox[customSize="loopChatEnabled"] {
/* 430px as defined per UX */
height: 430px;
}
#chat-window[customSize="loopChatEnabled"] {
/* 325px + 30px top bar height. */
min-height: calc(325px + 30px);
}
chatbox[customSize="loopChatMessageAppended"] {
/* 430px as defined per UX */
height: 430px;
}
chatbox[customSize="loopChatDisabledMessageAppended"] {
/* 388px as defined per UX */
height: 388px;
}
#chat-window[customSize="loopChatMessageAppended"] {
/* 445px + 30px top bar height. */
min-height: calc(400px + 30px);
}
chatbox[minimized="true"] {
width: 160px;
height: 20px; /* CHAT_WIDTH_MINIMIZED in socialchat.xml */
}
chatbar {
-moz-binding: url("chrome://browser/content/socialchat.xml#chatbar");
height: 0;
max-height: 0;
}
.chatbar-innerbox {
margin: -285px 0 0;
}
chatbar[customSize] > .chatbar-innerbox {
/* 450px to make room for the maximum custom-size chatbox; currently 'loopChatMessageAppended'. */
margin-top: -450px;
}
/* Apply crisp rendering for favicons at exactly 2dppx resolution */
@media (resolution: 2dppx) {
#social-sidebar-favico,
.social-status-button,
.chat-status-icon {
image-rendering: -moz-crisp-edges;
}
}
/** See bug 872317 for why the following rule is necessary. */
#downloads-button {
@ -929,6 +1028,18 @@ toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-
visibility: hidden;
}
/* hide chat chrome when chat is fullscreen */
#chat-window[sizemode="fullscreen"] chatbox > .chat-titlebar {
display: none;
}
/* hide chatbar and sidebar if browser tab is fullscreen */
#main-window[inFullscreen][inDOMFullscreen] chatbar,
#main-window[inFullscreen][inDOMFullscreen] #social-sidebar-box,
#main-window[inFullscreen][inDOMFullscreen] #social-sidebar-splitter {
display: none;
}
/* Combobox dropdown renderer */
#ContentSelectDropdown > menupopup {
/* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with

Просмотреть файл

@ -2245,6 +2245,9 @@ function BrowserViewSourceOfDocument(aArgsOrDocument) {
!E10SUtils.canLoadURIInProcess(args.URL, contentProcess)
}
// In the case of sidebars and chat windows, gBrowser is defined but null,
// because no #content element exists. For these cases, we need to find
// the most recent browser window.
// In the case of popups, we need to find a non-popup browser window.
if (!tabBrowser || !window.toolbar.visible) {
// This returns only non-popup browser windows by default.

Просмотреть файл

@ -271,6 +271,23 @@
<hbox id="share-container" flex="1"/>
</panel>
<panel id="social-notification-panel"
class="social-panel"
type="arrow"
hidden="true"
noautofocus="true"/>
<panel id="social-flyout-panel"
class="social-panel"
onpopupshown="SocialFlyout.onShown()"
onpopuphidden="SocialFlyout.onHidden()"
side="right"
type="arrow"
hidden="true"
flip="slide"
rolluponmousewheel="true"
noautofocus="true"
position="topcenter topright"/>
<menupopup id="toolbar-context-menu"
onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('viewToolbarsMenuSeparator'));">
<menuitem oncommand="gCustomizeMode.addToPanel(document.popupNode)"
@ -1063,6 +1080,56 @@
contentcontextmenu="contentAreaContextMenu"
autocompletepopup="PopupAutoComplete"
selectmenulist="ContentSelectDropdown"/>
<chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
</vbox>
<splitter id="social-sidebar-splitter"
class="chromeclass-extrachrome sidebar-splitter"
observes="socialSidebarBroadcaster"/>
<vbox id="social-sidebar-box"
class="chromeclass-extrachrome"
observes="socialSidebarBroadcaster"
persist="width">
<sidebarheader id="social-sidebar-header" class="sidebar-header" align="center">
<image id="social-sidebar-favico"/>
<label id="social-sidebar-title" class="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
<toolbarbutton id="social-sidebar-button"
class="toolbarbutton-1"
type="menu">
<menupopup id="social-statusarea-popup" position="after_end">
<menuitem class="social-toggle-sidebar-menuitem"
type="checkbox"
autocheck="false"
command="Social:ToggleSidebar"
label="&social.toggleSidebar.label;"
accesskey="&social.toggleSidebar.accesskey;"/>
<menuitem class="social-toggle-notifications-menuitem"
type="checkbox"
autocheck="false"
command="Social:ToggleNotifications"
label="&social.toggleNotifications.label;"
accesskey="&social.toggleNotifications.accesskey;"/>
<menuseparator/>
<menuseparator class="social-provider-menu" hidden="true"/>
<menuitem class="social-addons-menuitem" command="Social:Addons"
label="&social.addons.label;"/>
<menuitem label="&social.learnMore.label;"
accesskey="&social.learnMore.accesskey;"
oncommand="SocialUI.showLearnMore();"/>
</menupopup>
</toolbarbutton>
</sidebarheader>
<browser id="social-sidebar-browser"
type="content"
context="contentAreaContextMenu"
message="true"
messagemanagergroup="social"
disableglobalhistory="true"
tooltip="aHTMLTooltip"
popupnotificationanchor="social-sidebar-favico"
flex="1"
style="min-width: 14em; width: 18em; max-width: 36em;"/>
</vbox>
<vbox id="browser-border-end" hidden="true" layer="true"/>
</hbox>

Просмотреть файл

@ -0,0 +1,170 @@
#filter substitution
<?xml version="1.0"?>
# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# 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/.
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
#include browser-doctype.inc
<window id="chat-window"
windowtype="Social:Chat"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&mainWindow.title;"
onload="gChatWindow.onLoad();"
onunload="gChatWindow.onUnload();"
macanimationtype="document"
fullscreenbutton="true"
# width and height are also used in socialchat.xml: chatbar dragend handler
width="400px"
height="420px"
persist="screenX screenY width height sizemode">
<script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
#include global-scripts.inc
<script type="application/javascript">
var gChatWindow = {
// cargo-culted from browser.js for nonBrowserStartup, but we're slightly
// different what what we need to leave enabled
onLoad: function() {
// Disable inappropriate commands / submenus
var disabledItems = ['Browser:SavePage', 'Browser:OpenFile',
'Browser:SendLink', 'cmd_pageSetup', 'cmd_print',
'cmd_find', 'cmd_findAgain', 'cmd_findPrevious',
'cmd_fullZoomReduce', 'cmd_fullZoomEnlarge', 'cmd_fullZoomReset',
#ifdef XP_MACOSX
'viewToolbarsMenu', 'viewSidebarMenuMenu',
'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu',
#else
'Browser:OpenLocation', 'Tools:Search',
#endif
'Tools:Sanitize', 'Tools:DevToolbox',
'key_selectTab1', 'key_selectTab2', 'key_selectTab3',
'key_selectTab4', 'key_selectTab5', 'key_selectTab6',
'key_selectTab7', 'key_selectTab8', 'key_selectLastTab',
'viewHistorySidebar', 'viewBookmarksSidebar',
'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs'];
for (let disabledItem of disabledItems) {
document.getElementById(disabledItem).setAttribute("disabled", "true");
}
window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
new chatBrowserAccess();
// initialise the offline listener
BrowserOffline.init();
},
onUnload: function() {
BrowserOffline.uninit();
}
}
// define a popupnotifications handler for this window. we don't use
// an iconbox here, and only support the browser frame for chat.
XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
let tmp = {};
Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
try {
return new tmp.PopupNotifications(document.getElementById("chatter").content,
document.getElementById("notification-popup"),
null);
} catch (ex) {
console.error(ex);
return null;
}
});
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
function chatBrowserAccess() { }
chatBrowserAccess.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
_openURIInNewTab: function(aURI, aWhere) {
if (aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB)
return null;
let win = RecentWindow.getMostRecentBrowserWindow();
if (!win) {
// We couldn't find a suitable window, a new one needs to be opened.
return null;
}
let loadInBackground =
Services.prefs.getBoolPref("browser.tabs.loadDivertedInBackground");
let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank",
{inBackground: loadInBackground});
let browser = win.gBrowser.getBrowserForTab(tab);
win.focus();
return browser;
},
openURI: function (aURI, aOpener, aWhere, aContext) {
let browser = this._openURIInNewTab(aURI, aWhere);
return browser ? browser.contentWindow : null;
},
openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
let browser = this._openURIInNewTab(aURI, aWhere);
return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
},
isTabContentWindow: function (aWindow) {
return this.contentWindow == aWindow;
},
canClose() {
let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {});
return BrowserUtils.canCloseWindow(window);
},
};
</script>
#include browser-sets.inc
#ifdef XP_MACOSX
#include browser-menubar.inc
#endif
<popupset id="mainPopupSet">
<tooltip id="aHTMLTooltip" page="true"/>
<menupopup id="contentAreaContextMenu" pagemenu="start"
onpopupshowing="if (event.target != this)
return true;
gContextMenu = new nsContextMenu(this, event.shiftKey);
if (gContextMenu.shouldDisplay)
document.popupNode = this.triggerNode;
return gContextMenu.shouldDisplay;"
onpopuphiding="if (event.target != this)
return;
gContextMenu.hiding();
gContextMenu = null;">
#include browser-context.inc
</menupopup>
#include popup-notifications.inc
</popupset>
<commandset id="editMenuCommands"/>
<chatbox id="chatter" flex="1"/>
</window>

Просмотреть файл

@ -713,6 +713,10 @@ PageMetadataMessenger.init();
addEventListener("ActivateSocialFeature", function (aEvent) {
let document = content.document;
if (PrivateBrowsingUtils.isContentWindowPrivate(content)) {
Cu.reportError("cannot use social providers in private windows");
return;
}
let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!dwu.isHandlingUserInput) {

Просмотреть файл

@ -350,6 +350,28 @@ nsContextMenu.prototype = {
this.showItem("context-bidi-page-direction-toggle",
!this.onTextInput && top.gBidiUI);
// SocialMarks. Marks does not work with text selections, only links. If
// there is more than MENU_LIMIT providers, we show a submenu for them,
// otherwise we have a menuitem per provider (added in SocialMarks class).
let markProviders = SocialMarks.getProviders();
let enablePageMarks = markProviders.length > 0 && !(this.onLink || this.onImage
|| this.onVideo || this.onAudio);
this.showItem("context-markpageMenu", enablePageMarks && markProviders.length > SocialMarks.MENU_LIMIT);
let enablePageMarkItems = enablePageMarks && markProviders.length <= SocialMarks.MENU_LIMIT;
let linkmenus = document.getElementsByClassName("context-markpage");
for (let m of linkmenus) {
m.hidden = !enablePageMarkItems;
}
let enableLinkMarks = markProviders.length > 0 &&
((this.onLink && !this.onMailtoLink) || this.onPlainTextLink);
this.showItem("context-marklinkMenu", enableLinkMarks && markProviders.length > SocialMarks.MENU_LIMIT);
let enableLinkMarkItems = enableLinkMarks && markProviders.length <= SocialMarks.MENU_LIMIT;
linkmenus = document.getElementsByClassName("context-marklink");
for (let m of linkmenus) {
m.hidden = !enableLinkMarkItems;
}
// SocialShare
let shareButton = SocialShare.shareButton;
let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
@ -1051,7 +1073,12 @@ nsContextMenu.prototype = {
},
reload: function(event) {
BrowserReloadOrDuplicate(event);
if (this.onSocial) {
// full reload of social provider
Social._getProviderFromOrigin(this.browser.getAttribute("origin")).reload();
} else {
BrowserReloadOrDuplicate(event);
}
},
// View Partial Source
@ -1059,6 +1086,9 @@ nsContextMenu.prototype = {
let inWindow = !Services.prefs.getBoolPref("view_source.tab");
let openSelectionFn = inWindow ? null : function() {
let tabBrowser = gBrowser;
// In the case of sidebars and chat windows, gBrowser is defined but null,
// because no #content element exists. For these cases, we need to find
// the most recent browser window.
// In the case of popups, we need to find a non-popup browser window.
if (!tabBrowser || !window.toolbar.visible) {
// This returns only non-popup browser windows by default.
@ -1707,6 +1737,10 @@ nsContextMenu.prototype = {
mm.sendAsyncMessage("ContextMenu:BookmarkFrame", null, { target: this.target });
},
markLink: function CM_markLink(origin) {
// send link to social, if it is the page url linkURI will be null
SocialMarks.markLink(origin, this.linkURI ? this.linkURI.spec : null, this.target);
},
shareLink: function CM_shareLink() {
SocialShare.sharePage(null, { url: this.linkURI.spec }, this.target);
},

Просмотреть файл

@ -44,6 +44,15 @@ addEventListener("DOMTitleChanged", function(e) {
gDOMTitleChangedByUs = false;
});
addEventListener("Social:Notification", function(event) {
let frame = docShell.chromeEventHandler;
let origin = frame.getAttribute("origin");
sendAsyncMessage("Social:Notification", {
"origin": origin,
"detail": JSON.parse(event.detail)
});
});
addMessageListener("Social:OpenGraphData", (message) => {
let ev = new content.CustomEvent("OpenGraphData", { detail: JSON.stringify(message.data) });
content.dispatchEvent(ev);

Просмотреть файл

@ -0,0 +1,913 @@
<?xml version="1.0"?>
<bindings id="socialChatBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="chatbox">
<content orient="vertical" mousethrough="never">
<xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected,activity" align="baseline">
<xul:hbox flex="1" onclick="document.getBindingParent(this).onTitlebarClick(event);">
<xul:image class="chat-status-icon" xbl:inherits="src=image"/>
<xul:label class="chat-title" flex="1" xbl:inherits="crop=titlecrop,value=label" crop="end"/>
</xul:hbox>
<xul:toolbarbutton anonid="webRTC-shareScreen-icon"
class="notification-anchor-icon chat-toolbarbutton screen-icon"
oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
<xul:toolbarbutton anonid="webRTC-sharingScreen-icon"
class="notification-anchor-icon chat-toolbarbutton screen-icon in-use"
oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
<xul:toolbarbutton anonid="notification-icon" class="notification-anchor-icon chat-toolbarbutton"
oncommand="document.getBindingParent(this).showNotifications(this); event.stopPropagation();"/>
<xul:toolbarbutton anonid="minimize" class="chat-minimize-button chat-toolbarbutton"
oncommand="document.getBindingParent(this).toggle();"/>
<xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
oncommand="document.getBindingParent(this).swapWindows();"/>
<xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
oncommand="document.getBindingParent(this).close();"/>
</xul:hbox>
<xul:browser anonid="remote-content" class="chat-frame" flex="1"
context="contentAreaContextMenu"
disableglobalhistory="true"
frameType="social"
message="true"
messagemanagergroup="social"
tooltip="aHTMLTooltip"
remote="true"
xbl:inherits="src,origin"
type="content"/>
<xul:browser anonid="content" class="chat-frame" flex="1"
context="contentAreaContextMenu"
disableglobalhistory="true"
message="true"
messagemanagergroup="social"
tooltip="aHTMLTooltip"
xbl:inherits="src,origin"
type="content"/>
</content>
<implementation implements="nsIDOMEventListener, nsIMessageListener">
<constructor><![CDATA[
const kAnchorMap = new Map([
["", "notification-"],
["webRTC-shareScreen-", ""],
["webRTC-sharingScreen-", ""]
]);
const kBrowsers = [
document.getAnonymousElementByAttribute(this, "anonid", "content"),
document.getAnonymousElementByAttribute(this, "anonid", "remote-content")
];
for (let content of kBrowsers) {
for (let [getterPrefix, idPrefix] of kAnchorMap) {
let getter = getterPrefix + "popupnotificationanchor";
let anonid = (idPrefix || getterPrefix) + "icon";
content.__defineGetter__(getter, () => {
delete content[getter];
return content[getter] = document.getAnonymousElementByAttribute(
this, "anonid", anonid);
});
}
}
let mm = this.content.messageManager;
// process this._callbacks, then set to null so the chatbox creator
// knows to make new callbacks immediately.
if (this._callbacks) {
for (let callback of this._callbacks) {
callback(this);
}
this._callbacks = null;
}
mm.addMessageListener("Social:DOMTitleChanged", this);
mm.sendAsyncMessage("WaitForDOMContentLoaded");
mm.addMessageListener("DOMContentLoaded", function DOMContentLoaded(event) {
mm.removeMessageListener("DOMContentLoaded", DOMContentLoaded);
this.isActive = !this.minimized;
this._chat.loadButtonSet(this, this.getAttribute("buttonSet"));
this._deferredChatLoaded.resolve(this);
}.bind(this));
this.setActiveBrowser();
]]></constructor>
<field name="_deferredChatLoaded" readonly="true">
Promise.defer();
</field>
<property name="promiseChatLoaded">
<getter>
return this._deferredChatLoaded.promise;
</getter>
</property>
<property name="content">
<getter>
return document.getAnonymousElementByAttribute(this, "anonid",
(this.remote ? "remote-" : "") + "content");
</getter>
</property>
<field name="_chat" readonly="true">
Cu.import("resource:///modules/Chat.jsm", {}).Chat;
</field>
<property name="minimized">
<getter>
return this.getAttribute("minimized") == "true";
</getter>
<setter><![CDATA[
// Note that this.isActive is set via our transitionend handler so
// the content doesn't see intermediate values.
let parent = this.chatbar;
if (val) {
this.setAttribute("minimized", "true");
// If this chat is the selected one a new one needs to be selected.
if (parent && parent.selectedChat == this)
parent._selectAnotherChat();
} else {
this.removeAttribute("minimized");
// this chat gets selected.
if (parent)
parent.selectedChat = this;
}
]]></setter>
</property>
<property name="chatbar">
<getter>
if (this.parentNode.nodeName == "chatbar")
return this.parentNode;
return null;
</getter>
</property>
<property name="isActive">
<getter>
return this.content.docShellIsActive;
</getter>
<setter>
this.content.docShellIsActive = !!val;
// Bug 1256431 to remove socialFrameShow/Hide from hello, keep this
// until that is complete.
// let the chat frame know if it is being shown or hidden
this.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
name: val ? "socialFrameShow" : "socialFrameHide"
});
</setter>
</property>
<field name="_remote">false</field>
<property name="remote" onget="return this._remote;">
<setter><![CDATA[
this._remote = !!val;
this.setActiveBrowser();
]]></setter>
</property>
<method name="setActiveBrowser">
<body><![CDATA[
// Make sure we only show one browser element at a time.
let content = document.getAnonymousElementByAttribute(this, "anonid", "content");
let remoteContent = document.getAnonymousElementByAttribute(this, "anonid", "remote-content");
remoteContent.setAttribute("hidden", !this.remote);
content.setAttribute("hidden", this.remote);
remoteContent.removeAttribute("src");
content.removeAttribute("src");
if (this.src) {
this.setAttribute("src", this.src);
// Stop loading of the document - that is set before this method was
// called - in the now hidden browser.
(this.remote ? content : remoteContent).setAttribute("src", "about:blank");
}
]]></body>
</method>
<method name="showNotifications">
<parameter name="aAnchor"/>
<body><![CDATA[
PopupNotifications._reshowNotifications(aAnchor,
this.content);
]]></body>
</method>
<method name="swapDocShells">
<parameter name="aTarget"/>
<body><![CDATA[
aTarget.setAttribute("label", this.content.contentTitle);
aTarget.remote = this.remote;
aTarget.src = this.src;
let content = aTarget.content;
content.setAttribute("origin", this.content.getAttribute("origin"));
content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
content.swapDocShells(this.content);
// When a chat window is attached or detached, the docShell hosting
// the chat document is swapped to the newly created chat window.
// (Be it inside a popup or back inside a chatbox element attached to
// the chatbar.)
// Since a swapDocShells call does not swap the messageManager instances
// attached to a browser, we'll need to add the message listeners to
// the new messageManager. This is not a bug in swapDocShells, merely
// a design decision.
content.messageManager.addMessageListener("Social:DOMTitleChanged", content);
]]></body>
</method>
<method name="setDecorationAttributes">
<parameter name="aTarget"/>
<body><![CDATA[
if (this.hasAttribute("customSize"))
aTarget.setAttribute("customSize", this.getAttribute("customSize"));
this._chat.loadButtonSet(aTarget, this.getAttribute("buttonSet"));
]]></body>
</method>
<method name="onTitlebarClick">
<parameter name="aEvent"/>
<body><![CDATA[
if (!this.chatbar)
return;
if (aEvent.button == 0) { // left-click: toggle minimized.
this.toggle();
// if we restored it, we want to focus it.
if (!this.minimized)
this.chatbar.focus();
} else if (aEvent.button == 1) // middle-click: close chat
this.close();
]]></body>
</method>
<method name="close">
<body><![CDATA[
if (this.chatbar)
this.chatbar.remove(this);
else
window.close();
if (!this.swappingWindows)
this.dispatchEvent(new CustomEvent("ChatboxClosed"));
]]></body>
</method>
<method name="swapWindows">
<body><![CDATA[
let deferred = Promise.defer();
let title = this.getAttribute("label");
if (this.chatbar) {
this.chatbar.detachChatbox(this, { "centerscreen": "yes" }).then(
chatbox => {
chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
title: title
});
deferred.resolve(chatbox);
}
);
} else {
// attach this chatbox to the topmost browser window
let Chat = Cu.import("resource:///modules/Chat.jsm").Chat;
let win = Chat.findChromeWindowForChats();
let chatbar = win.document.getElementById("pinnedchats");
let origin = this.content.getAttribute("origin");
let cb = chatbar.openChat({
origin: origin,
title: title,
url: "about:blank"
});
cb.promiseChatLoaded.then(
() => {
this.setDecorationAttributes(cb);
this.swapDocShells(cb);
chatbar.focus();
this.swappingWindows = true;
this.close();
// chatboxForURL is a map of URL -> chatbox used to avoid opening
// duplicate chat windows. Ensure reattached chat windows aren't
// registered with about:blank as their URL, otherwise reattaching
// more than one chat window isn't possible.
chatbar.chatboxForURL.delete("about:blank");
chatbar.chatboxForURL.set(this.src, Cu.getWeakReference(cb));
cb.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
name: "socialFrameAttached"
});
deferred.resolve(cb);
}
);
}
return deferred.promise;
]]></body>
</method>
<method name="toggle">
<body><![CDATA[
this.minimized = !this.minimized;
]]></body>
</method>
<method name="setTitle">
<body><![CDATA[
try {
this.setAttribute("label", this.content.contentTitle);
} catch (ex) {}
if (this.chatbar)
this.chatbar.updateTitlebar(this);
]]></body>
</method>
<method name="receiveMessage">
<parameter name="aMessage" />
<body><![CDATA[
switch (aMessage.name) {
case "Social:DOMTitleChanged":
this.setTitle();
break;
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="focus" phase="capturing">
if (this.chatbar)
this.chatbar.selectedChat = this;
</handler>
<handler event="DOMTitleChanged">
this.setTitle();
</handler>
<handler event="DOMLinkAdded"><![CDATA[
// Much of this logic is from DOMLinkHandler in browser.js.
// This sets the presence icon for a chat user, we simply use favicon
// style updating.
let link = event.originalTarget;
let rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href)
return;
if (link.rel.indexOf("icon") < 0)
return;
let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {})
.ContentLinkHandler;
let uri = ContentLinkHandler.getLinkIconURI(link);
if (!uri)
return;
// We made it this far, use it.
this.setAttribute("image", uri.spec);
if (this.chatbar)
this.chatbar.updateTitlebar(this);
]]></handler>
<handler event="transitionend">
if (this.isActive == this.minimized)
this.isActive = !this.minimized;
</handler>
</handlers>
</binding>
<binding id="chatbar">
<content>
<xul:hbox align="end" pack="end" anonid="innerbox" class="chatbar-innerbox" mousethrough="always" flex="1">
<xul:spacer flex="1" anonid="spacer" class="chatbar-overflow-spacer"/>
<xul:toolbarbutton anonid="nub" class="chatbar-button" type="menu" collapsed="true" mousethrough="never">
<xul:menupopup anonid="nubMenu" oncommand="document.getBindingParent(this).showChat(event.target.chat)"/>
</xul:toolbarbutton>
<children/>
</xul:hbox>
</content>
<implementation implements="nsIDOMEventListener">
<constructor>
// to avoid reflows we cache the width of the nub.
this.cachedWidthNub = 0;
this._selectedChat = null;
</constructor>
<field name="innerbox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "innerbox");
</field>
<field name="menupopup" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "nubMenu");
</field>
<field name="nub" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "nub");
</field>
<method name="focus">
<body><![CDATA[
if (!this.selectedChat)
return;
this.selectedChat.content.messageManager.sendAsyncMessage("Social:EnsureFocus");
]]></body>
</method>
<method name="_isChatFocused">
<parameter name="aChatbox"/>
<body><![CDATA[
// If there are no XBL bindings for the chat it can't be focused.
if (!aChatbox.content)
return false;
let fw = Services.focus.focusedWindow;
if (!fw)
return false;
// We want to see if the focused window is in the subtree below our browser...
let containingBrowser = fw.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
return containingBrowser == aChatbox.content;
]]></body>
</method>
<property name="selectedChat">
<getter><![CDATA[
return this._selectedChat;
]]></getter>
<setter><![CDATA[
// this is pretty horrible, but we:
// * want to avoid doing touching 'selected' attribute when the
// specified chat is already selected.
// * remove 'activity' attribute on newly selected tab *even if*
// newly selected is already selected.
// * need to handle either current or new being null.
if (this._selectedChat != val) {
if (this._selectedChat) {
this._selectedChat.removeAttribute("selected");
}
this._selectedChat = val;
if (val) {
this._selectedChat.setAttribute("selected", "true");
}
}
if (val) {
this._selectedChat.removeAttribute("activity");
}
]]></setter>
</property>
<field name="menuitemMap">new WeakMap()</field>
<field name="chatboxForURL">new Map();</field>
<property name="hasCollapsedChildren">
<getter><![CDATA[
return !!this.querySelector("[collapsed]");
]]></getter>
</property>
<property name="collapsedChildren">
<getter><![CDATA[
// A generator yielding all collapsed chatboxes, in the order in
// which they should be restored.
return function*() {
let child = this.lastElementChild;
while (child) {
if (child.collapsed)
yield child;
child = child.previousElementSibling;
}
}
]]></getter>
</property>
<property name="visibleChildren">
<getter><![CDATA[
// A generator yielding all non-collapsed chatboxes.
return function*() {
let child = this.firstElementChild;
while (child) {
if (!child.collapsed)
yield child;
child = child.nextElementSibling;
}
}
]]></getter>
</property>
<property name="collapsibleChildren">
<getter><![CDATA[
// A generator yielding all children which are able to be collapsed
// in the order in which they should be collapsed.
// (currently this is all visible ones other than the selected one.)
return function*() {
for (let child of this.visibleChildren())
if (child != this.selectedChat)
yield child;
}
]]></getter>
</property>
<method name="_selectAnotherChat">
<body><![CDATA[
// Select a different chat (as the currently selected one is no
// longer suitable as the selection - maybe it is being minimized or
// closed.) We only select non-minimized and non-collapsed chats,
// and if none are found, set the selectedChat to null.
// It's possible in the future we will track most-recently-selected
// chats or similar to find the "best" candidate - for now though
// the choice is somewhat arbitrary.
let moveFocus = this.selectedChat && this._isChatFocused(this.selectedChat);
for (let other of this.children) {
if (other != this.selectedChat && !other.minimized && !other.collapsed) {
this.selectedChat = other;
if (moveFocus)
this.focus();
return;
}
}
// can't find another - so set no chat as selected.
this.selectedChat = null;
]]></body>
</method>
<method name="updateTitlebar">
<parameter name="aChatbox"/>
<body><![CDATA[
if (aChatbox.collapsed) {
let menuitem = this.menuitemMap.get(aChatbox);
if (aChatbox.getAttribute("activity")) {
menuitem.setAttribute("activity", true);
this.nub.setAttribute("activity", true);
}
menuitem.setAttribute("label", aChatbox.getAttribute("label"));
menuitem.setAttribute("image", aChatbox.getAttribute("image"));
}
]]></body>
</method>
<method name="calcTotalWidthOf">
<parameter name="aElement"/>
<body><![CDATA[
let cs = document.defaultView.getComputedStyle(aElement);
let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
return aElement.getBoundingClientRect().width + margins;
]]></body>
</method>
<method name="getTotalChildWidth">
<parameter name="aChatbox"/>
<body><![CDATA[
// These are from the CSS for the chatbox and must be kept in sync.
// We can't use calcTotalWidthOf due to the transitions...
const CHAT_WIDTH_OPEN = 300;
const CHAT_WIDTH_OPEN_ALT = 350;
const CHAT_WIDTH_MINIMIZED = 160;
let openWidth = aChatbox.hasAttribute("customSize") ?
CHAT_WIDTH_OPEN_ALT : CHAT_WIDTH_OPEN;
return aChatbox.minimized ? CHAT_WIDTH_MINIMIZED : openWidth;
]]></body>
</method>
<method name="collapseChat">
<parameter name="aChatbox"/>
<body><![CDATA[
// we ensure that the cached width for a child of this type is
// up-to-date so we can use it when resizing.
this.getTotalChildWidth(aChatbox);
aChatbox.collapsed = true;
aChatbox.isActive = false;
let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
menu.setAttribute("class", "menuitem-iconic");
menu.setAttribute("label", aChatbox.content.contentTitle);
menu.setAttribute("image", aChatbox.getAttribute("image"));
menu.chat = aChatbox;
this.menuitemMap.set(aChatbox, menu);
this.menupopup.appendChild(menu);
this.nub.collapsed = false;
]]></body>
</method>
<method name="showChat">
<parameter name="aChatbox"/>
<parameter name="aMode"/>
<body><![CDATA[
if ((aMode != "minimized") && aChatbox.minimized)
aChatbox.minimized = false;
if (this.selectedChat != aChatbox)
this.selectedChat = aChatbox;
if (!aChatbox.collapsed)
return; // already showing - no more to do.
this._showChat(aChatbox);
// showing a collapsed chat might mean another needs to be collapsed
// to make room...
this.resize();
]]></body>
</method>
<method name="_showChat">
<parameter name="aChatbox"/>
<body><![CDATA[
// the actual implementation - doesn't check for overflow, assumes
// collapsed, etc.
let menuitem = this.menuitemMap.get(aChatbox);
this.menuitemMap.delete(aChatbox);
this.menupopup.removeChild(menuitem);
aChatbox.collapsed = false;
aChatbox.isActive = !aChatbox.minimized;
]]></body>
</method>
<method name="remove">
<parameter name="aChatbox"/>
<body><![CDATA[
this._remove(aChatbox);
// The removal of a chat may mean a collapsed one can spring up,
// or that the popup should be hidden. We also defer the selection
// of another chat until after a resize, as a new candidate may
// become uncollapsed after the resize.
this.resize();
if (this.selectedChat == aChatbox) {
this._selectAnotherChat();
}
]]></body>
</method>
<method name="_remove">
<parameter name="aChatbox"/>
<body><![CDATA[
this.removeChild(aChatbox);
// child might have been collapsed.
let menuitem = this.menuitemMap.get(aChatbox);
if (menuitem) {
this.menuitemMap.delete(aChatbox);
this.menupopup.removeChild(menuitem);
}
this.chatboxForURL.delete(aChatbox.src);
]]></body>
</method>
<method name="openChat">
<parameter name="aOptions"/>
<parameter name="aCallback"/>
<body><![CDATA[
let {origin, title, url, mode} = aOptions;
let cb = this.chatboxForURL.get(url);
if (cb && (cb = cb.get())) {
// A chatbox is still alive to us when it's parented and still has
// content.
if (cb.parentNode) {
this.showChat(cb, mode);
if (aCallback) {
if (cb._callbacks == null) {
// Chatbox has already been created, so callback now.
aCallback(cb);
} else {
// Chatbox is yet to have bindings created...
cb._callbacks.push(aCallback);
}
}
return cb;
}
this.chatboxForURL.delete(url);
}
cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
cb._callbacks = [];
if (aCallback) {
// _callbacks is a javascript property instead of a <field> as it
// must exist before the (possibly delayed) bindings are created.
cb._callbacks.push(aCallback);
}
cb.remote = !!aOptions.remote;
// src also a javascript property; the src attribute is set in the ctor.
cb.src = url;
if (mode == "minimized")
cb.setAttribute("minimized", "true");
cb.setAttribute("origin", origin);
cb.setAttribute("label", title);
this.insertBefore(cb, this.firstChild);
this.selectedChat = cb;
this.chatboxForURL.set(url, Cu.getWeakReference(cb));
this.resize();
return cb;
]]></body>
</method>
<method name="resize">
<body><![CDATA[
// Checks the current size against the collapsed state of children
// and collapses or expands as necessary such that as many as possible
// are shown.
// So 2 basic strategies:
// * Collapse/Expand one at a time until we can't collapse/expand any
// more - but this is one reflow per change.
// * Calculate the dimensions ourself and choose how many to collapse
// or expand based on this, then do them all in one go. This is one
// reflow regardless of how many we change.
// So we go the more complicated but more efficient second option...
let availWidth = this.getBoundingClientRect().width;
let currentWidth = 0;
if (!this.nub.collapsed) { // the nub is visible.
if (!this.cachedWidthNub)
this.cachedWidthNub = this.calcTotalWidthOf(this.nub);
currentWidth += this.cachedWidthNub;
}
for (let child of this.visibleChildren()) {
currentWidth += this.getTotalChildWidth(child);
}
if (currentWidth > availWidth) {
// we need to collapse some.
let toCollapse = [];
for (let child of this.collapsibleChildren()) {
if (currentWidth <= availWidth)
break;
toCollapse.push(child);
currentWidth -= this.getTotalChildWidth(child);
}
if (toCollapse.length) {
for (let child of toCollapse)
this.collapseChat(child);
}
} else if (currentWidth < availWidth) {
// we *might* be able to expand some - see how many.
// XXX - if this was clever, it could know when removing the nub
// leaves enough space to show all collapsed
let toShow = [];
for (let child of this.collapsedChildren()) {
currentWidth += this.getTotalChildWidth(child);
if (currentWidth > availWidth)
break;
toShow.push(child);
}
for (let child of toShow)
this._showChat(child);
// If none remain collapsed remove the nub.
if (!this.hasCollapsedChildren) {
this.nub.collapsed = true;
}
}
// else: achievement unlocked - we are pixel-perfect!
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "resize") {
this.resize();
}
]]></body>
</method>
<method name="_getDragTarget">
<parameter name="event"/>
<body><![CDATA[
return event.target.localName == "chatbox" ? event.target : null;
]]></body>
</method>
<!-- Moves a chatbox to a new window. Returns a promise that is resolved
once the move to the other window is complete.
-->
<method name="detachChatbox">
<parameter name="aChatbox"/>
<parameter name="aOptions"/>
<body><![CDATA[
let deferred = Promise.defer();
let chatbar = this;
let options = "";
for (let name in aOptions)
options += "," + name + "=" + aOptions[name];
let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul",
"_blank", "chrome,all,dialog=no" + options);
otherWin.addEventListener("load", function _chatLoad(event) {
if (event.target != otherWin.document)
return;
if (aChatbox.hasAttribute("customSize")) {
otherWin.document.getElementById("chat-window").
setAttribute("customSize", aChatbox.getAttribute("customSize"));
}
otherWin.removeEventListener("load", _chatLoad, true);
let otherChatbox = otherWin.document.getElementById("chatter");
aChatbox.setDecorationAttributes(otherChatbox);
aChatbox.swapDocShells(otherChatbox);
aChatbox.swappingWindows = true;
aChatbox.close();
let url = aChatbox.src;
chatbar.chatboxForURL.set(url, Cu.getWeakReference(otherChatbox));
// All processing is done, now we can fire the event.
otherChatbox.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
name: "socialFrameDetached"
});
Services.obs.addObserver(function onDOMWindowClosed(subject) {
if (subject !== otherWin)
return;
Services.obs.removeObserver(onDOMWindowClosed, "domwindowclosed");
chatbar.chatboxForURL.delete(url);
if (!otherChatbox.swappingWindows)
otherChatbox.dispatchEvent(new CustomEvent("ChatboxClosed"));
}, "domwindowclosed", false);
deferred.resolve(otherChatbox);
}, true);
return deferred.promise;
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshown"><![CDATA[
this.nub.removeAttribute("activity");
]]></handler>
<handler event="load"><![CDATA[
window.addEventListener("resize", this, true);
]]></handler>
<handler event="unload"><![CDATA[
window.removeEventListener("resize", this, true);
]]></handler>
<handler event="dragstart"><![CDATA[
// chat window dragging is essentially duplicated from tabbrowser.xml
// to acheive the same visual experience
let chatbox = this._getDragTarget(event);
if (!chatbox) {
return;
}
let dt = event.dataTransfer;
// we do not set a url in the drag data to prevent moving to tabbrowser
// or otherwise having unexpected drop handlers do something with our
// chatbox
dt.mozSetDataAt("application/x-moz-chatbox", chatbox, 0);
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
// Create a canvas to which we capture the current tab.
// Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
// canvas size (in CSS pixels) to the window's backing resolution in order
// to get a full-resolution drag image for use on HiDPI displays.
let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.mozOpaque = true;
canvas.width = 160 * scale;
canvas.height = 90 * scale;
PageThumbs.captureToCanvas(chatbox, canvas);
dt.setDragImage(canvas, -16 * scale, -16 * scale);
event.stopPropagation();
]]></handler>
<handler event="dragend"><![CDATA[
let dt = event.dataTransfer;
let draggedChat = dt.mozGetDataAt("application/x-moz-chatbox", 0);
if (dt.mozUserCancelled || dt.dropEffect != "none") {
return;
}
let eX = event.screenX;
let eY = event.screenY;
// screen.availLeft et. al. only check the screen that this window is on,
// but we want to look at the screen the tab is being dropped onto.
let sX = {}, sY = {}, sWidth = {}, sHeight = {};
Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager)
.screenForRect(eX, eY, 1, 1)
.GetAvailRect(sX, sY, sWidth, sHeight);
// default size for the chat window as used in chatWindow.xul, use them
// here to attempt to keep the window fully within the screen when
// opening at the drop point. If the user has resized the window to
// something larger (which gets persisted), at least a good portion of
// the window should still be within the screen.
let winWidth = 400;
let winHeight = 420;
// ensure new window entirely within screen
let left = Math.min(Math.max(eX, sX.value),
sX.value + sWidth.value - winWidth);
let top = Math.min(Math.max(eY, sY.value),
sY.value + sHeight.value - winHeight);
this.detachChatbox(draggedChat, { screenX: left, screenY: top });
event.stopPropagation();
]]></handler>
</handlers>
</binding>
</bindings>

Просмотреть файл

@ -0,0 +1,366 @@
<?xml version="1.0"?>
<bindings id="socialMarkBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="toolbarbutton-marks" display="xul:button"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
<content>
<xul:panel anonid="panel" hidden="true" type="arrow" class="social-panel"/>
<xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
<xul:label class="toolbarbutton-text" crop="right" flex="1"
xbl:inherits="value=label,accesskey,crop,wrap"/>
<xul:label class="toolbarbutton-multiline-text" flex="1"
xbl:inherits="xbl:text=label,accesskey,wrap"/>
</content>
<implementation implements="nsIDOMEventListener, nsIObserver">
<constructor>
// if we overflow, we have to reset the button. unfortunately we cannot
// use a widget listener because we need to do this *after* the node is
// moved, and the event happens before the node is moved.
this.update();
</constructor>
<property name="_anchor">
<getter>
let widgetGroup = CustomizableUI.getWidget(this.getAttribute("id"));
return widgetGroup.forWindow(window).anchor;
</getter>
</property>
<property name="_useDynamicResizer">
<getter>
let provider = Social._getProviderFromOrigin(this.getAttribute("origin"));
return !provider.getPageSize("marks");
</getter>
</property>
<property name="panel">
<getter>
return document.getAnonymousElementByAttribute(this, "anonid", "panel");
</getter>
</property>
<property name="content">
<getter><![CDATA[
if (this._frame)
return this._frame;
let provider = Social._getProviderFromOrigin(this.getAttribute("origin"));
let size = provider.getPageSize("marks");
let {width, height} = size ? size : {width: 330, height: 100};
let iframe = this._frame = document.createElement("iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("class", "social-panel-frame");
iframe.setAttribute("flex", "1");
iframe.setAttribute("message", "true");
iframe.setAttribute("messagemanagergroup", "social");
iframe.setAttribute("tooltip", "aHTMLTooltip");
iframe.setAttribute("context", "contentAreaContextMenu");
iframe.setAttribute("origin", provider.origin);
iframe.setAttribute("style", "width: " + width + "px; height: " + height + "px;");
this.panel.appendChild(iframe);
this._frame.addEventListener("DOMLinkAdded", this);
return this._frame;
]]></getter>
</property>
<property name="messageManager">
<getter>
return this.content.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
</getter>
</property>
<property name="contentWindow">
<getter>
return this.content.contentWindow;
</getter>
</property>
<property name="contentDocument">
<getter>
return this.content.contentDocument;
</getter>
</property>
<property name="provider">
<getter>
return Social._getProviderFromOrigin(this.getAttribute("origin"));
</getter>
</property>
<property name="isMarked">
<setter><![CDATA[
this._isMarked = val;
let provider = this.provider;
// we cannot size the image when we apply it via listStyleImage, so
// use the toolbar image
let widgetGroup = CustomizableUI.getWidget(this.getAttribute("id"));
val = val && !!widgetGroup.areaType;
let icon = val ? provider.markedIcon : provider.unmarkedIcon;
let iconURL = icon || provider.icon32URL || provider.iconURL;
this.setAttribute("image", iconURL);
]]></setter>
<getter>
return this._isMarked;
</getter>
</property>
<method name="update">
<body><![CDATA[
// update the button for use with the current tab
let provider = this.provider;
if (this._dynamicResizer) {
this._dynamicResizer.stop();
this._dynamicResizer = null;
}
this.content.setAttribute("src", "about:blank");
// called during onhidden, make sure the docshell is updated
if (this._frame.docShell)
this._frame.docShell.createAboutBlankContentViewer(null);
// disabled attr is set by Social:PageShareOrMark command
if (this.hasAttribute("disabled")) {
this.isMarked = false;
} else {
Social.isURIMarked(provider.origin, gBrowser.currentURI, (isMarked) => {
this.isMarked = isMarked;
});
}
this.content.setAttribute("origin", provider.origin);
let panel = this.panel;
// if customization is currently happening, we may not have a panel
// that we can hide
if (panel.hidePopup) {
panel.hidePopup();
}
this.pageData = null;
]]></body>
</method>
<method name="receiveMessage">
<parameter name="message"/>
<body><![CDATA[
if (message.name != "Social:ErrorPageNotify" || message.target != this.content)
return;
this.openPanel();
]]></body>
</method>
<method name="loadPanel">
<parameter name="pageData"/>
<parameter name="target"/>
<body><![CDATA[
let provider = this.provider;
let panel = this.panel;
panel.hidden = false;
// reparent the iframe if we've been customized to a new location
if (this.content.parentNode != panel)
panel.appendChild(this.content);
let URLTemplate = provider.markURL;
let _dataFn;
if (!pageData) {
messageManager.addMessageListener("PageMetadata:PageDataResult", _dataFn = (msg) => {
messageManager.removeMessageListener("PageMetadata:PageDataResult", _dataFn);
this.loadPanel(msg.json, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetPageData", null, { target });
return;
}
// if this is a share of a selected item, get any microformats
if (!pageData.microformats && target) {
messageManager.addMessageListener("PageMetadata:MicroformatsResult", _dataFn = (msg) => {
messageManager.removeMessageListener("PageMetadata:MicroformatsResult", _dataFn);
pageData.microformats = msg.data;
this.loadPanel(pageData, target);
});
gBrowser.selectedBrowser.messageManager.sendAsyncMessage("PageMetadata:GetMicroformats", null, { target });
return;
}
this.pageData = pageData;
let endpoint = OpenGraphBuilder.generateEndpointURL(URLTemplate, this.pageData);
// setup listeners
let DOMContentLoaded = (event) => {
this._loading = false;
this.messageManager.removeMessageListener("DOMContentLoaded", DOMContentLoaded);
// add our resizer after the dom is ready
if (this._useDynamicResizer) {
let DynamicResizeWatcher = Cu.import("resource:///modules/Social.jsm", {}).DynamicResizeWatcher;
this._dynamicResizer = new DynamicResizeWatcher();
this._dynamicResizer.start(this.panel, this.content);
} else if (this._dynamicResizer) {
this._dynamicResizer.stop();
this._dynamicResizer = null;
}
let contentWindow = this.contentWindow;
let markUpdate = function(event) {
// update the annotation based on this event, then update the
// icon as well
this.isMarked = JSON.parse(event.detail).marked;
if (this.isMarked) {
Social.markURI(provider.origin, gBrowser.currentURI);
} else {
Social.unmarkURI(provider.origin, gBrowser.currentURI, () => {
this.update();
});
}
}.bind(this);
let unload = () => {
contentWindow.removeEventListener("unload", unload);
contentWindow.removeEventListener("socialMarkUpdate", markUpdate);
}
contentWindow.addEventListener("socialMarkUpdate", markUpdate);
contentWindow.addEventListener("unload", unload);
// send the opengraph data
this.messageManager.sendAsyncMessage("Social:OpenGraphData", pageData);
}
this.messageManager.addMessageListener("DOMContentLoaded", DOMContentLoaded);
this._loading = true;
this.content.setAttribute("src", endpoint);
]]></body>
</method>
<method name="openPanel">
<parameter name="aResetOnClose"/>
<body><![CDATA[
let panel = this.panel;
let anchor = document.getAnonymousElementByAttribute(this._anchor, "class", "toolbarbutton-icon");
// Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
// handling from preventing it being opened in some cases.
setTimeout(() => {
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
}, 0);
Services.telemetry.getHistogramById("SOCIAL_TOOLBAR_BUTTONS").add(2);
]]></body>
</method>
<method name="markCurrentPage">
<parameter name="aOpenPanel"/>
<body><![CDATA[
// we always set the src on click if it has not been set for this tab,
// but we only want to open the panel if it was previously annotated.
let openPanel = this.isMarked || aOpenPanel;
let src = this.content.getAttribute("src");
if (!src || src == "about:blank") {
this.loadPanel();
}
if (openPanel)
this.openPanel();
]]></body>
</method>
<method name="markLink">
<parameter name="aUrl"/>
<parameter name="aTarget"/>
<body><![CDATA[
if (!aUrl) {
this.markCurrentPage(true);
return;
}
// initiated form an external source, such as gContextMenu, where
// pageData is passed into us. In this case, we always load the iframe
// and show it since the url may not be the browser tab, but an image,
// link, etc. inside the page. We also "update" the iframe to the
// previous url when it is closed.
this.content.setAttribute("src", "about:blank");
this.loadPanel({ url: aUrl }, aTarget);
this.openPanel(true);
]]></body>
</method>
<method name="dispatchPanelEvent">
<parameter name="name"/>
<body><![CDATA[
let evt = this.contentDocument.createEvent("CustomEvent");
evt.initCustomEvent(name, true, true, {});
this.contentDocument.documentElement.dispatchEvent(evt);
]]></body>
</method>
<method name="onShown">
<body><![CDATA[
// because the panel may be preloaded, we need to size the panel when
// showing as well as after load
if (!this._useDynamicResizer) {
return;
}
let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent;
if (!this._loading && this.contentDocument &&
this.contentDocument.readyState == "complete") {
sizeSocialPanelToContent(this.panel, this.content);
} else {
let panelBrowserOnload = (message) => {
if (message.target != this.content)
return;
this.messageManager.removeMessageListener("PageVisibility:Show", panelBrowserOnload, true);
sizeSocialPanelToContent(this.panel, this.content);
};
this.messageManager.addMessageListener("PageVisibility:Show", panelBrowserOnload);
}
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.eventPhase != aEvent.BUBBLING_PHASE)
return;
switch (aEvent.type) {
case "DOMLinkAdded": {
// much of this logic is from DOMLinkHandler in browser.js, this sets
// the presence icon for a chat user, we simply use favicon style
// updating
let link = aEvent.originalTarget;
let rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href)
return;
if (link.rel.indexOf("icon") < 0)
return;
let ContentLinkHandler = Cu.import("resource:///modules/ContentLinkHandler.jsm", {}).ContentLinkHandler;
let uri = ContentLinkHandler.getLinkIconURI(link);
if (!uri)
return;
// we cannot size the image when we apply it via listStyleImage, so
// use the toolbar image
this.setAttribute("image", uri.spec);
}
break;
case "click":
Services.telemetry.getHistogramById("SOCIAL_PANEL_CLICKS").add(2);
break;
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing"><![CDATA[
this._anchor.setAttribute("open", "true");
this.content.addEventListener("click", this);
]]></handler>
<handler event="popupshown"><![CDATA[
this.onShown();
]]></handler>
<handler event="popuphidden"><![CDATA[
this._anchor.removeAttribute("open");
this.update();
this.content.removeEventListener("click", this);
]]></handler>
<handler event="command"><![CDATA[
this.markCurrentPage();
]]></handler>
</handlers>
</binding>
</bindings>

Просмотреть файл

@ -0,0 +1,5 @@
{
"extends": [
"../../../../../testing/mochitest/browser.eslintrc"
]
}

Просмотреть файл

@ -0,0 +1,9 @@
[DEFAULT]
skip-if = buildapp == 'mulet'
support-files =
head.js
chat.html
[browser_chatwindow.js]
[browser_focus.js]
[browser_tearoff.js]

Просмотреть файл

@ -0,0 +1,197 @@
/* 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/. */
requestLongerTimeout(2);
var chatbar = document.getElementById("pinnedchats");
add_chat_task(function* testOpenCloseChat() {
let chatbox = yield promiseOpenChat("http://example.com");
Assert.strictEqual(chatbox, chatbar.selectedChat);
// we requested a "normal" chat, so shouldn't be minimized
Assert.ok(!chatbox.minimized, "chat is not minimized");
Assert.equal(chatbar.childNodes.length, 1, "should be 1 chat open");
// now request the same URL again - we should get the same chat.
let chatbox2 = yield promiseOpenChat("http://example.com");
Assert.strictEqual(chatbox2, chatbox, "got the same chat");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
chatbox.toggle();
is(chatbox.minimized, true, "chat is now minimized");
// was no other chat to select, so selected becomes null.
is(chatbar.selectedChat, null);
// We check the content gets an unload event as we close it.
chatbox.close();
});
// In this case we open a chat minimized, then request the same chat again
// without specifying minimized. On that second call the chat should open,
// selected, and no longer minimized.
add_chat_task(function* testMinimized() {
let chatbox = yield promiseOpenChat("http://example.com", "minimized");
Assert.strictEqual(chatbox, chatbar.selectedChat);
Assert.ok(chatbox.minimized, "chat is minimized");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
yield promiseOpenChat("http://example.com");
Assert.ok(!chatbox.minimized, false, "chat is no longer minimized");
});
// open enough chats to overflow the window, then check
// if the menupopup is visible
add_chat_task(function* testManyChats() {
Assert.ok(chatbar.menupopup.parentNode.collapsed, "popup nub collapsed at start");
// we should *never* find a test box that needs more than this to cause
// an overflow!
let maxToOpen = 20;
let numOpened = 0;
for (let i = 0; i < maxToOpen; i++) {
yield promiseOpenChat("http://example.com#" + i);
if (!chatbar.menupopup.parentNode.collapsed) {
info("the menu popup appeared");
return;
}
}
Assert.ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
});
// Check that closeAll works as expected.
add_chat_task(function* testOpenTwiceCallbacks() {
yield promiseOpenChat("http://example.com#1");
yield promiseOpenChat("http://example.com#2");
yield promiseOpenChat("http://test2.example.com");
Assert.equal(numChatsInWindow(window), 3, "should be 3 chats open");
Chat.closeAll("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "should have closed 2 chats");
Chat.closeAll("http://test2.example.com");
Assert.equal(numChatsInWindow(window), 0, "should have closed last chat");
});
// Check that when we open the same chat twice, the callbacks are called back
// twice.
add_chat_task(function* testOpenTwiceCallbacks() {
yield promiseOpenChatCallback("http://example.com");
yield promiseOpenChatCallback("http://example.com");
});
// Bug 817782 - check chats work in new top-level windows.
add_chat_task(function* testSecondTopLevelWindow() {
const chatUrl = "http://example.com";
let winPromise = BrowserTestUtils.waitForNewWindow();
OpenBrowserWindow();
let secondWindow = yield winPromise;
yield promiseOpenChat(chatUrl);
// the chat was created - let's make sure it was created in the second window.
Assert.equal(numChatsInWindow(window), 0, "main window has no chats");
Assert.equal(numChatsInWindow(secondWindow), 1, "second window has 1 chat");
secondWindow.close();
});
// Test that findChromeWindowForChats() returns the expected window.
add_chat_task(function* testChatWindowChooser() {
let chat = yield promiseOpenChat("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "first window has the chat");
// create a second window - this will be the "most recent" and will
// therefore be the window that hosts the new chat (see bug 835111)
let secondWindow = OpenBrowserWindow();
yield promiseOneEvent(secondWindow, "load");
Assert.equal(secondWindow, Chat.findChromeWindowForChats(null), "Second window is the preferred chat window");
// focus the first window, and check it will be selected for future chats.
// Bug 1090633 - there are lots of issues around focus, especially when the
// browser itself doesn't have focus. We can end up with
// Services.wm.getMostRecentWindow("navigator:browser") returning a different
// window than, say, Services.focus.activeWindow. But the focus manager isn't
// the point of this test.
// So we simply keep focusing the first window until it is reported as the
// most recent.
do {
dump("trying to force window to become the most recent.\n");
secondWindow.focus();
window.focus();
yield promiseWaitForFocus();
} while (Services.wm.getMostRecentWindow("navigator:browser") != window)
Assert.equal(window, Chat.findChromeWindowForChats(null), "First window now the preferred chat window");
let privateWindow = OpenBrowserWindow({private: true});
yield promiseOneEvent(privateWindow, "load")
// The focused window can't accept chats (it's a private window), so the
// chat should open in the window that was selected before. This will be
// either window or secondWindow (linux may choose a different one) but the
// point is that the window is *not* the private one.
Assert.ok(Chat.findChromeWindowForChats(null) == window ||
Chat.findChromeWindowForChats(null) == secondWindow,
"Private window isn't selected for new chats.");
privateWindow.close();
secondWindow.close();
});
add_chat_task(function* testButtonSet() {
let chatbox = yield promiseOpenChat("http://example.com#1");
let document = chatbox.ownerDocument;
// Expect all default buttons to be visible.
for (let buttonId of kDefaultButtonSet) {
let button = document.getAnonymousElementByAttribute(chatbox, "anonid", buttonId);
Assert.ok(!button.hidden, "Button '" + buttonId + "' should be visible");
}
let visible = new Set(["minimize", "close"]);
chatbox = yield promiseOpenChat("http://example.com#2", null, null, [...visible].join(","));
for (let buttonId of kDefaultButtonSet) {
let button = document.getAnonymousElementByAttribute(chatbox, "anonid", buttonId);
if (visible.has(buttonId)) {
Assert.ok(!button.hidden, "Button '" + buttonId + "' should be visible");
} else {
Assert.ok(button.hidden, "Button '" + buttonId + "' should NOT be visible");
}
}
});
add_chat_task(function* testCustomButton() {
let commanded = 0;
let customButton = {
id: "custom",
onCommand: function() {
++commanded;
}
};
Chat.registerButton(customButton);
let chatbox = yield promiseOpenChat("http://example.com#1");
let document = chatbox.ownerDocument;
let titlebarNode = document.getAnonymousElementByAttribute(chatbox, "class",
"chat-titlebar");
Assert.equal(titlebarNode.getElementsByClassName("chat-custom")[0], null,
"Custom chat button should not be in the toolbar yet.");
let visible = new Set(["minimize", "close", "custom"]);
Chat.loadButtonSet(chatbox, [...visible].join(","));
for (let buttonId of kDefaultButtonSet) {
let button = document.getAnonymousElementByAttribute(chatbox, "anonid", buttonId);
if (visible.has(buttonId)) {
Assert.ok(!button.hidden, "Button '" + buttonId + "' should be visible");
} else {
Assert.ok(button.hidden, "Button '" + buttonId + "' should NOT be visible");
}
}
let customButtonNode = titlebarNode.getElementsByClassName("chat-custom")[0];
Assert.ok(!customButtonNode.hidden, "Custom button should be visible");
let ev = document.createEvent("XULCommandEvent");
ev.initCommandEvent("command", true, true, document.defaultView, 0, false,
false, false, false, null);
customButtonNode.dispatchEvent(ev);
Assert.equal(commanded, 1, "Button should have been commanded once");
});

Просмотреть файл

@ -0,0 +1,262 @@
/* 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/. */
// Tests the focus functionality.
Cu.import("resource://testing-common/ContentTask.jsm", this);
const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
requestLongerTimeout(2);
// Is the currently opened tab focused?
function isTabFocused() {
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
// focus sucks in tests - our window may have lost focus.
let elt = Services.focus.getFocusedElementForWindow(window, false, {});
return elt == tabb;
}
// Is the specified chat focused?
function isChatFocused(chat) {
// focus sucks in tests - our window may have lost focus.
let elt = Services.focus.getFocusedElementForWindow(window, false, {});
return elt == chat.content;
}
var chatbar = document.getElementById("pinnedchats");
function* setUp() {
// Note that (probably) due to bug 604289, if a tab is focused but the
// focused element is null, our chat windows can "steal" focus. This is
// avoided if we explicitly focus an element in the tab.
// So we load a page with an <input> field and focus that before testing.
let html = '<input id="theinput"><button id="chat-opener"></button>';
let url = "data:text/html;charset=utf-8," + encodeURI(html);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
let browser = tab.linkedBrowser;
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("theinput").focus();
});
registerCleanupFunction(function() {
gBrowser.removeTab(tab);
});
}
// Test default focus - not user input.
add_chat_task(function* testDefaultFocus() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com");
// we used the default focus behaviour, which means that because this was
// not the direct result of user action the chat should not be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
});
// Test default focus via user input.
add_chat_task(function* testDefaultFocusUserInput() {
todo(false, "BrowserTestUtils.synthesizeMouseAtCenter doesn't move the user " +
"focus to the chat window, even though we're recording a click correctly.");
return;
yield setUp();
let browser = gBrowser.selectedTab.linkedBrowser;
let mm = browser.messageManager;
let promise = new Promise(resolve => {
mm.addMessageListener("ChatOpenerClicked", function handler() {
mm.removeMessageListener("ChatOpenerClicked", handler);
promiseOpenChat("http://example.com").then(resolve);
});
});
yield ContentTask.spawn(browser, null, function* () {
let button = content.document.getElementById("chat-opener");
button.addEventListener("click", function onclick() {
button.removeEventListener("click", onclick);
sendAsyncMessage("ChatOpenerClicked");
});
});
// Note we must use synthesizeMouseAtCenter() rather than calling
// .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
// to be true.
yield BrowserTestUtils.synthesizeMouseAtCenter("#chat-opener", {}, browser);
let chat = yield promise;
// we use the default focus behaviour but the chat was opened via user input,
// so the chat should be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
yield promiseWaitForCondition(() => !isTabFocused());
Assert.ok(!isTabFocused(), "the tab should have lost focus.");
Assert.ok(isChatFocused(chat), "the chat should have got focus.");
});
// We explicitly ask for the chat to be focused.
add_chat_task(function* testExplicitFocus() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com", undefined, true);
// we use the default focus behaviour, which means that because this was
// not the direct result of user action the chat should not be focused.
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
yield promiseWaitForCondition(() => !isTabFocused());
Assert.ok(!isTabFocused(), "the tab should have lost focus.");
Assert.ok(isChatFocused(chat), "the chat should have got focus.");
});
// Open a minimized chat via default focus behaviour - it will open and not
// have focus. Then open the same chat without 'minimized' - it will be
// restored but should still not have grabbed focus.
add_chat_task(function* testNoFocusOnAutoRestore() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com", "minimized");
Assert.ok(chat.minimized, "chat is minimized");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
yield promiseOpenChat("http://example.com");
Assert.ok(!chat.minimized, "chat should be restored");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
});
// Here we open a chat, which will not be focused. Then we minimize it and
// restore it via a titlebar clock - it should get focus at that point.
add_chat_task(function* testFocusOnExplicitRestore() {
yield setUp();
let chat = yield promiseOpenChat("http://example.com");
Assert.ok(!chat.minimized, "chat should have been opened restored");
Assert.ok(isTabFocused(), "the tab should remain focused.");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
chat.minimized = true;
Assert.ok(isTabFocused(), "tab should still be focused");
Assert.ok(!isChatFocused(chat), "the chat should not be focused.");
let promise = promiseOneMessage(chat.content, "Social:FocusEnsured");
// pretend we clicked on the titlebar
chat.onTitlebarClick({button: 0});
yield promise; // wait for focus event.
Assert.ok(!chat.minimized, "chat should have been restored");
Assert.ok(isChatFocused(chat), "chat should be focused");
Assert.strictEqual(chat, chatbar.selectedChat, "chat is marked selected");
});
// Open 2 chats and give 1 focus. Minimize the focused one - the second
// should get focus.
add_chat_task(function* testMinimizeFocused() {
yield setUp();
let chat1 = yield promiseOpenChat("http://example.com#1");
let chat2 = yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(window), 2, "2 chats open");
Assert.strictEqual(chatbar.selectedChat, chat2, "chat2 is selected");
let promise = promiseOneMessage(chat1.content, "Social:FocusEnsured");
chatbar.selectedChat = chat1;
chatbar.focus();
yield promise; // wait for chat1 to get focus.
Assert.strictEqual(chat1, chatbar.selectedChat, "chat1 is marked selected");
Assert.notStrictEqual(chat2, chatbar.selectedChat, "chat2 is not marked selected");
todo(false, "Bug 1245803 should re-enable the test below to have a chat window " +
"re-gain focus when another chat window is minimized.");
return;
promise = promiseOneMessage(chat2.content, "Social:FocusEnsured");
chat1.minimized = true;
yield promise; // wait for chat2 to get focus.
Assert.notStrictEqual(chat1, chatbar.selectedChat, "chat1 is not marked selected");
Assert.strictEqual(chat2, chatbar.selectedChat, "chat2 is marked selected");
});
// Open 2 chats, select and focus the second. Pressing the TAB key should
// cause focus to move between all elements in our chat window before moving
// to the next chat window.
add_chat_task(function* testTab() {
yield setUp();
function sendTabAndWaitForFocus(chat, eltid) {
EventUtils.sendKey("tab");
return ContentTask.spawn(chat.content, { eltid: eltid }, function* (args) {
let doc = content.document;
// ideally we would use the 'focus' event here, but that doesn't work
// as expected for the iframe - the iframe itself never gets the focus
// event (apparently the sub-document etc does.)
// So just poll for the correct element getting focus...
yield new Promise(function(resolve, reject) {
let tries = 0;
let interval = content.setInterval(function() {
if (tries >= 30) {
clearInterval(interval);
reject("never got focus");
return;
}
tries++;
let elt = args.eltid ? doc.getElementById(args.eltid) : doc.documentElement;
if (doc.activeElement == elt) {
content.clearInterval(interval);
resolve();
}
info("retrying wait for focus: " + tries);
info("(the active element is " + doc.activeElement + "/" +
doc.activeElement.getAttribute("id") + ")");
}, 100);
info("waiting for element " + args.eltid + " to get focus");
});
});
}
let chat1 = yield promiseOpenChat(CHAT_URL + "#1");
let chat2 = yield promiseOpenChat(CHAT_URL + "#2");
chatbar.selectedChat = chat2;
let promise = promiseOneMessage(chat2.content, "Social:FocusEnsured");
chatbar.focus();
info("waiting for second chat to get focus");
yield promise;
// Our chats have 3 focusable elements, so it takes 4 TABs to move
// to the new chat.
yield sendTabAndWaitForFocus(chat2, "input1");
Assert.ok(isChatFocused(chat2), "new chat still focused after first tab");
yield sendTabAndWaitForFocus(chat2, "input2");
Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
yield sendTabAndWaitForFocus(chat2, "iframe");
Assert.ok(isChatFocused(chat2), "new chat still focused after tab");
// this tab now should move to the next chat, but focus the
// document element itself (hence the null eltid)
yield sendTabAndWaitForFocus(chat1, null);
Assert.ok(isChatFocused(chat1), "first chat is focused");
});
// Open a chat and focus an element other than the first. Move focus to some
// other item (the tab itself in this case), then focus the chatbar - the
// same element that was previously focused should still have focus.
add_chat_task(function* testFocusedElement() {
yield setUp();
// open a chat with focus requested.
let chat = yield promiseOpenChat(CHAT_URL, undefined, true);
yield ContentTask.spawn(chat.content, null, function* () {
content.document.getElementById("input2").focus();
});
// set focus to the main window.
let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
let promise = promiseOneEvent(window, "focus");
Services.focus.moveFocus(window, null, Services.focus.MOVEFOCUS_ROOT, 0);
yield promise;
promise = promiseOneMessage(chat.content, "Social:FocusEnsured");
chatbar.focus();
yield promise;
yield ContentTask.spawn(chat.content, null, function* () {
Assert.equal(content.document.activeElement.getAttribute("id"), "input2",
"correct input field still has focus");
});
});

Просмотреть файл

@ -0,0 +1,135 @@
/* 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/. */
var chatbar = document.getElementById("pinnedchats");
function promiseNewWindowLoaded() {
return new Promise(resolve => {
Services.wm.addListener({
onWindowTitleChange: function() {},
onCloseWindow: function(xulwindow) {},
onOpenWindow: function(xulwindow) {
var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
Services.wm.removeListener(this);
// wait for load to ensure the window is ready for us to test
domwindow.addEventListener("load", function _load(event) {
let doc = domwindow.document;
if (event.target != doc)
return;
domwindow.removeEventListener("load", _load);
resolve(domwindow);
});
},
});
});
}
add_chat_task(function* testTearoffChat() {
let chatbox = yield promiseOpenChat("http://example.com");
Assert.equal(numChatsInWindow(window), 1, "should be 1 chat open");
let chatTitle = yield ContentTask.spawn(chatbox.content, null, function* () {
let chatDoc = content.document;
// Mutate the chat document a bit before we tear it off.
let div = chatDoc.createElement("div");
div.setAttribute("id", "testdiv");
div.setAttribute("test", "1");
chatDoc.body.appendChild(div);
return chatDoc.title;
});
Assert.equal(chatbox.getAttribute("label"), chatTitle,
"the new chatbox should show the title of the chat window");
// chatbox is open, lets detach. The new chat window will be caught in
// the window watcher below
let promise = promiseNewWindowLoaded();
let swap = document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
swap.click();
// and wait for the new window.
let domwindow = yield promise;
Assert.equal(domwindow.document.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
Assert.equal(numChatsInWindow(window), 0, "should be no chats in the chat bar");
// get the chatbox from the new window.
chatbox = domwindow.document.getElementById("chatter")
Assert.equal(chatbox.getAttribute("label"), chatTitle, "window should have same title as chat");
yield ContentTask.spawn(chatbox.content, null, function* () {
let div = content.document.getElementById("testdiv");
Assert.equal(div.getAttribute("test"), "1", "docshell should have been swapped");
div.setAttribute("test", "2");
});
// swap the window back to the chatbar
promise = promiseOneEvent(domwindow, "unload");
swap = domwindow.document.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
swap.click();
yield promise;
Assert.equal(numChatsInWindow(window), 1, "chat should be docked back in the window");
chatbox = chatbar.selectedChat;
Assert.equal(chatbox.getAttribute("label"), chatTitle,
"the new chatbox should show the title of the chat window again");
yield ContentTask.spawn(chatbox.content, null, function* () {
let div = content.document.getElementById("testdiv");
Assert.equal(div.getAttribute("test"), "2", "docshell should have been swapped");
});
});
// Similar test but with 2 chats.
add_chat_task(function* testReattachTwice() {
let chatbox1 = yield promiseOpenChat("http://example.com#1");
let chatbox2 = yield promiseOpenChat("http://example.com#2");
Assert.equal(numChatsInWindow(window), 2, "both chats should be docked in the window");
info("chatboxes are open, detach from window");
let promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
let domwindow1 = yield promise;
chatbox1 = domwindow1.document.getElementById("chatter");
Assert.equal(numChatsInWindow(window), 1, "only second chat should be docked in the window");
promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
let domwindow2 = yield promise;
chatbox2 = domwindow2.document.getElementById("chatter");
Assert.equal(numChatsInWindow(window), 0, "should be no docked chats");
promise = promiseOneEvent(domwindow2, "unload");
domwindow2.document.getAnonymousElementByAttribute(chatbox2, "anonid", "swap").click();
yield promise;
Assert.equal(numChatsInWindow(window), 1, "one chat should be docked back in the window");
promise = promiseOneEvent(domwindow1, "unload");
domwindow1.document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
yield promise;
Assert.equal(numChatsInWindow(window), 2, "both chats should be docked back in the window");
});
// Check that Chat.closeAll() also closes detached windows.
add_chat_task(function* testCloseAll() {
let chatbox1 = yield promiseOpenChat("http://example.com#1");
let chatbox2 = yield promiseOpenChat("http://example.com#2");
let promise = promiseNewWindowLoaded();
document.getAnonymousElementByAttribute(chatbox1, "anonid", "swap").click();
let domwindow = yield promise;
chatbox1 = domwindow.document.getElementById("chatter");
let promiseWindowUnload = promiseOneEvent(domwindow, "unload");
Assert.equal(numChatsInWindow(window), 1, "second chat should still be docked");
Chat.closeAll("http://example.com");
yield promiseWindowUnload;
Assert.equal(numChatsInWindow(window), 0, "should be no chats left");
});

Просмотреть файл

@ -0,0 +1,14 @@
<html>
<head>
<meta charset="utf-8">
<title>test chat window</title>
</head>
<body>
<p>This is a test chat window.</p>
<!-- a couple of input fields to help with focus testing -->
<input id="input1"/>
<input id="input2"/>
<!-- an iframe here so this one page generates multiple load events -->
<iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,130 @@
/* 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/. */
// Utility functions for Chat tests.
var Chat = Cu.import("resource:///modules/Chat.jsm", {}).Chat;
const kDefaultButtonSet = new Set(["minimize", "swap", "close"]);
function promiseOpenChat(url, mode, focus, buttonSet = null) {
let uri = Services.io.newURI(url, null, null);
let origin = uri.prePath;
let title = origin;
return new Promise(resolve => {
// we just through a few hoops to ensure the content document is fully
// loaded, otherwise tests that rely on that content may intermittently fail.
let callback = function(chatbox) {
let mm = chatbox.content.messageManager;
mm.sendAsyncMessage("WaitForDOMContentLoaded");
mm.addMessageListener("DOMContentLoaded", function cb() {
mm.removeMessageListener("DOMContentLoaded", cb);
resolve(chatbox);
});
}
let chatbox = Chat.open(null, {
origin: origin,
title: title,
url: url,
mode: mode,
focus: focus
}, callback);
if (buttonSet) {
chatbox.setAttribute("buttonSet", buttonSet);
}
});
}
// Opens a chat, returns a promise resolved when the chat callback fired.
function promiseOpenChatCallback(url, mode) {
let uri = Services.io.newURI(url, null, null);
let origin = uri.prePath;
let title = origin;
return new Promise(resolve => {
Chat.open(null, { origin, title, url, mode }, resolve);
});
}
// Opens a chat, returns the chat window's promise which fires when the chat
// starts loading.
function promiseOneEvent(target, eventName, capture) {
return new Promise(resolve => {
target.addEventListener(eventName, function handler(event) {
target.removeEventListener(eventName, handler, capture);
resolve();
}, capture);
});
}
function promiseOneMessage(target, messageName) {
return new Promise(resolve => {
let mm = target.messageManager;
mm.addMessageListener(messageName, function handler() {
mm.removeMessageListener(messageName, handler);
resolve();
});
});
}
// Return the number of chats in a browser window.
function numChatsInWindow(win) {
let chatbar = win.document.getElementById("pinnedchats");
return chatbar.childElementCount;
}
function promiseWaitForFocus() {
return new Promise(resolve => waitForFocus(resolve));
}
// A simple way to clean up after each test.
function add_chat_task(genFunction) {
add_task(function* () {
info("Starting chat test " + genFunction.name);
try {
yield genFunction();
} finally {
info("Finished chat test " + genFunction.name + " - cleaning up.");
// close all docked chats.
while (chatbar.childNodes.length) {
chatbar.childNodes[0].close();
}
// and non-docked chats.
let winEnum = Services.wm.getEnumerator("Social:Chat");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
if (win.closed) {
continue;
}
win.close();
}
}
});
}
function waitForCondition(condition, nextTest, errorMsg) {
var tries = 0;
var interval = setInterval(function() {
if (tries >= 100) {
ok(false, errorMsg);
moveOn();
}
var conditionPassed;
try {
conditionPassed = condition();
} catch (e) {
ok(false, e + "\n" + e.stack);
conditionPassed = false;
}
if (conditionPassed) {
moveOn();
}
tries++;
}, 100);
var moveOn = function() { clearInterval(interval); nextTest(); };
}
function promiseWaitForCondition(aConditionFn) {
return new Promise((resolve, reject) => {
waitForCondition(aConditionFn, resolve, "Condition didn't pass.");
});
}

Просмотреть файл

@ -2,6 +2,7 @@
skip-if = buildapp == "mulet"
support-files =
blocklist.xml
checked.jpg
head.js
opengraph/og_invalid_url.html
opengraph/opengraph.html
@ -14,7 +15,15 @@ support-files =
social_activate.html
social_activate_basic.html
social_activate_iframe.html
social_chat.html
social_crash_content_helper.js
social_flyout.html
social_mark.html
social_panel.html
social_postActivation.html
social_sidebar.html
social_sidebar_empty.html
unchecked.jpg
!/browser/base/content/test/plugins/blockNoPlugins.xml
[browser_aboutHome_activation.js]
@ -22,3 +31,18 @@ support-files =
[browser_blocklist.js]
[browser_share.js]
[browser_social_activation.js]
[browser_social_chatwindow.js]
[browser_social_chatwindow_resize.js]
[browser_social_chatwindowfocus.js]
skip-if = asan # Bug 1260177
[browser_social_contextmenu.js]
skip-if = (os == 'linux' && e10s) # Bug 1072669 context menu relies on target element
[browser_social_errorPage.js]
[browser_social_flyout.js]
[browser_social_isVisible.js]
[browser_social_marks.js]
[browser_social_marks_context.js]
[browser_social_multiprovider.js]
[browser_social_sidebar.js]
[browser_social_status.js]
[browser_social_window.js]

Просмотреть файл

@ -2,7 +2,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
@ -17,7 +17,7 @@ var snippet =
' "iconURL": "chrome://branding/content/icon16.png",' +
' "icon32URL": "chrome://branding/content/icon32.png",' +
' "icon64URL": "chrome://branding/content/icon64.png",' +
' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
' "sidebarURL": "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",' +
' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
' };' +
' function activateProvider(node) {' +
@ -39,7 +39,7 @@ var snippet2 =
' "iconURL": "chrome://branding/content/icon16.png",' +
' "icon32URL": "chrome://branding/content/icon32.png",' +
' "icon64URL": "chrome://branding/content/icon64.png",' +
' "shareURL": "https://example.com/browser/browser/base/content/test/social/social_share.html",' +
' "sidebarURL": "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",' +
' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' +
' "oneclick": true' +
' };' +
@ -101,8 +101,10 @@ function test()
yield new Promise(resolve => {
activateProvider(tab, test.panel).then(() => {
ok(SocialSidebar.provider, "provider activated");
checkSocialUI();
SocialService.uninstallProvider("https://example.com", function () {
is(gBrowser.currentURI.spec, SocialSidebar.provider.manifest.postActivationURL, "postActivationURL was loaded");
SocialService.uninstallProvider(SocialSidebar.provider.origin, function () {
info("provider uninstalled");
resolve();
});
@ -208,12 +210,14 @@ function sendActivationEvent(tab) {
function activateProvider(tab, expectPanel, aCallback) {
return new Promise(resolve => {
if (expectPanel) {
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
let panel = document.getElementById("servicesInstall-notification");
panel.button.click();
});
}
waitForProviderLoad().then(() => {
ok(SocialSidebar.provider, "new provider is active");
ok(SocialSidebar.opened, "sidebar is open");
checkSocialUI();
resolve();
});
@ -225,5 +229,6 @@ function waitForProviderLoad(cb) {
return Promise.all([
promiseObserverNotified("social:provider-enabled"),
ensureFrameLoaded(gBrowser, "https://example.com/browser/browser/base/content/test/social/social_postActivation.html"),
ensureFrameLoaded(SocialSidebar.browser)
]);
}

Просмотреть файл

@ -1,23 +1,23 @@
var AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifest = {
name: "provider 1",
origin: "https://example.com",
shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
var manifest2 = { // used for testing install
name: "provider 2",
origin: "https://test1.example.com",
shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
var manifestUpgrade = { // used for testing install
name: "provider 3",
origin: "https://test2.example.com",
shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};

Просмотреть файл

@ -4,7 +4,7 @@
// a place for miscellaneous social tests
var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
var blocklistURL = "http://example.com/browser/browser/base/content/test/social/blocklist.xml";
@ -12,13 +12,13 @@ var blocklistURL = "http://example.com/browser/browser/base/content/test/social/
var manifest = { // normal provider
name: "provider ok",
origin: "https://example.com",
shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
var manifest_bad = { // normal provider
name: "provider blocked",
origin: "https://test1.example.com",
shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png"
};

Просмотреть файл

@ -1,5 +1,5 @@
var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var baseURL = "https://example.com/browser/browser/base/content/test/social/";

Просмотреть файл

@ -10,7 +10,7 @@
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null");
var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var tabsToRemove = [];
@ -73,6 +73,7 @@ function activateIFrameProvider(domain, callback) {
function waitForProviderLoad(origin) {
return Promise.all([
ensureFrameLoaded(gBrowser, origin + "/browser/browser/base/content/test/social/social_postActivation.html"),
ensureFrameLoaded(SocialSidebar.browser)
]);
}
@ -112,7 +113,6 @@ function clickAddonRemoveButton(tab, aCallback) {
}
function activateOneProvider(manifest, finishActivation, aCallback) {
info("activating provider "+manifest.name);
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
ok(!panel.hidden, "servicesInstall-notification panel opened");
@ -128,6 +128,8 @@ function activateOneProvider(manifest, finishActivation, aCallback) {
executeSoon(aCallback);
} else {
waitForProviderLoad(manifest.origin).then(() => {
is(SocialSidebar.provider.origin, manifest.origin, "new provider is active");
ok(SocialSidebar.opened, "sidebar is open");
checkSocialUI();
executeSoon(aCallback);
});
@ -145,19 +147,19 @@ var gProviders = [
{
name: "provider 1",
origin: "https://example.com",
shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.html?provider1",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider1",
iconURL: "chrome://branding/content/icon48.png"
},
{
name: "provider 2",
origin: "https://test1.example.com",
shareURL: "https://test1.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider2",
iconURL: "chrome://branding/content/icon64.png"
},
{
name: "provider 3",
origin: "https://test2.example.com",
shareURL: "https://test2.example.com/browser/browser/base/content/test/social/social_share.html?provider2",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html?provider2",
iconURL: "chrome://branding/content/about-logo.png"
}
];
@ -189,6 +191,7 @@ var tests = {
testIFrameActivation: function(next) {
activateIFrameProvider(gTestDomains[0], function() {
is(SocialUI.enabled, false, "SocialUI is not enabled");
ok(!SocialSidebar.provider, "provider is not installed");
let panel = document.getElementById("servicesInstall-notification");
ok(panel.hidden, "activation panel still hidden");
checkSocialUI();
@ -200,6 +203,7 @@ var tests = {
// first up we add a manifest entry for a single provider.
activateOneProvider(gProviders[0], false, function() {
// we deactivated leaving no providers left, so Social is disabled.
ok(!SocialSidebar.provider, "should be no provider left after disabling");
checkSocialUI();
next();
});
@ -217,6 +221,7 @@ var tests = {
// activate the last provider.
activateOneProvider(gProviders[2], false, function() {
// we deactivated - the first provider should be enabled.
is(SocialSidebar.provider.origin, Social.providers[1].origin, "original provider should have been reactivated");
checkSocialUI();
next();
});

Просмотреть файл

@ -0,0 +1,141 @@
/* 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/. */
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifests = [
{
name: "provider@example.com",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?example.com",
iconURL: "chrome://branding/content/icon48.png"
},
{
name: "provider@test1",
origin: "https://test1.example.com",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?test1",
iconURL: "chrome://branding/content/icon48.png"
},
{
name: "provider@test2",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?test2",
iconURL: "chrome://branding/content/icon48.png"
}
];
var chatId = 0;
function openChat(provider) {
return new Promise(resolve => {
SocialSidebar.provider = provider;
let chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
let url = chatUrl + "?id=" + (chatId++);
makeChat("normal", "chat " + chatId, (cb) => { resolve(cb); });
});
}
function windowHasChats(win) {
return !!getChatBar().firstElementChild;
}
function test() {
requestLongerTimeout(2); // only debug builds seem to need more time...
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addMessageListener("socialTest-CloseSelf", function(e) {
content.close();
}, true);
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
let oldwidth = window.outerWidth; // we futz with these, so we restore them
let oldleft = window.screenX;
window.moveTo(0, window.screenY)
let postSubTest = function(cb) {
let chats = document.getElementById("pinnedchats");
ok(chats.children.length == 0, "no chatty children left behind");
cb();
};
runSocialTestWithProvider(manifests, function (finishcb) {
ok(Social.enabled, "Social is enabled");
SocialSidebar.show();
runSocialTests(tests, undefined, postSubTest, function() {
window.moveTo(oldleft, window.screenY)
window.resizeTo(oldwidth, window.outerHeight);
mm.removeDelayedFrameScript(frameScript);
finishcb();
});
});
}
var tests = {
testOpenCloseChat: function(next) {
openChat(SocialSidebar.provider).then((cb) => {
BrowserTestUtils.waitForCondition(() => { return cb.minimized; },
"chatbox is minimized").then(() => {
ok(cb.minimized, "chat is minimized after toggle");
BrowserTestUtils.waitForCondition(() => { return !cb.minimized; },
"chatbox is not minimized").then(() => {
ok(!cb.minimized, "chat is not minimized after toggle");
promiseNodeRemoved(cb).then(next);
let mm = cb.content.messageManager;
mm.sendAsyncMessage("socialTest-CloseSelf", {});
info("close chat window requested");
});
cb.toggle();
});
ok(!cb.minimized, "chat is not minimized on open");
// toggle to minimize chat
cb.toggle();
});
},
// Check what happens when you close the only visible chat.
testCloseOnlyVisible: function(next) {
let chatbar = getChatBar();
let chatWidth = undefined;
let num = 0;
is(chatbar.childNodes.length, 0, "chatbar starting empty");
is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
makeChat("normal", "first chat", function() {
// got the first one.
checkPopup();
ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
// we kinda cheat here and get the width of the first chat, assuming
// that all future chats will have the same width when open.
chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
let desired = chatWidth * 1.5;
resizeWindowToChatAreaWidth(desired, function(sizedOk) {
ok(sizedOk, "can't do any tests without this width");
checkPopup();
makeChat("normal", "second chat", function() {
is(chatbar.childNodes.length, 2, "now have 2 chats");
let first = chatbar.childNodes[0];
let second = chatbar.childNodes[1];
is(chatbar.selectedChat, first, "first chat is selected");
ok(second.collapsed, "second chat is currently collapsed");
// closing the first chat will leave enough room for the second
// chat to appear, and thus become selected.
chatbar.selectedChat.close();
is(chatbar.selectedChat, second, "second chat is selected");
Task.spawn(closeAllChats).then(next);
});
});
});
},
testShowWhenCollapsed: function(next) {
get3ChatsForCollapsing("normal", function(first, second, third) {
let chatbar = getChatBar();
chatbar.showChat(first);
ok(!first.collapsed, "first should no longer be collapsed");
is(second.collapsed || third.collapsed, true, "one of the others should be collapsed");
Task.spawn(closeAllChats).then(next);
});
}
}

Просмотреть файл

@ -0,0 +1,82 @@
/* 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/. */
function test() {
requestLongerTimeout(2); // only debug builds seem to need more time...
waitForExplicitFinish();
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
// added for test purposes
chatURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html"
};
let oldwidth = window.outerWidth; // we futz with these, so we restore them
let oldleft = window.screenX;
window.moveTo(0, window.screenY)
let postSubTest = function(cb) {
let chats = document.getElementById("pinnedchats");
ok(chats.children.length == 0, "no chatty children left behind");
cb();
};
runSocialTestWithProvider(manifest, function (finishcb) {
let sbrowser = document.getElementById("social-sidebar-browser");
ensureFrameLoaded(sbrowser).then(() => {
let provider = SocialSidebar.provider;
provider.chatURL = manifest.chatURL;
ok(provider, "provider is set");
ok(provider.chatURL, "provider has chatURL");
// executeSoon to let the browser UI observers run first
runSocialTests(tests, undefined, postSubTest, function() {
window.moveTo(oldleft, window.screenY)
window.resizeTo(oldwidth, window.outerHeight);
finishcb();
});
});
SocialSidebar.show();
});
}
var tests = {
// resize and collapse testing.
testBrowserResize: function(next, mode) {
let chats = document.getElementById("pinnedchats");
get3ChatsForCollapsing(mode || "normal", function(first, second, third) {
let chatWidth = chats.getTotalChildWidth(first);
ok(chatWidth, "have a chatwidth");
let popupWidth = getPopupWidth();
ok(popupWidth, "have a popupwidth");
info("starting resize tests - each chat's width is " + chatWidth +
" and the popup width is " + popupWidth);
// Note that due to a difference between "device", "app" and "css" pixels
// we allow use 2 pixels as the minimum size difference.
resizeAndCheckWidths(first, second, third, [
[chatWidth-2, 1, "to < 1 chat width - only last should be visible."],
[chatWidth+2, 1, "2 pixels more then one fully exposed (not counting popup) - still only 1."],
[chatWidth+popupWidth+2, 1, "2 pixels more than one fully exposed (including popup) - still only 1."],
[chatWidth*2-2, 1, "second not showing by 2 pixels (not counting popup) - only 1 exposed."],
[chatWidth*2+popupWidth-2, 1, "second not showing by 2 pixelx (including popup) - only 1 exposed."],
[chatWidth*2+popupWidth+2, 2, "big enough to fit 2 - nub remains visible as first is still hidden"],
[chatWidth*3+popupWidth-2, 2, "one smaller than the size necessary to display all three - first still hidden"],
[chatWidth*3+popupWidth+2, 3, "big enough to fit all - all exposed (which removes the nub)"],
[chatWidth*3+2, 3, "now the nub is hidden we can resize back down to chatWidth*3 before overflow."],
[chatWidth*3-2, 2, "2 pixels less and the first is again collapsed (and the nub re-appears)"],
[chatWidth*2+popupWidth+2, 2, "back down to just big enough to fit 2"],
[chatWidth*2+popupWidth-2, 1, "back down to just not enough to fit 2"],
[chatWidth*3+popupWidth+2, 3, "now a large jump to make all 3 visible (ie, affects 2)"],
[chatWidth*1.5, 1, "and a large jump back down to 1 visible (ie, affects 2)"],
], function() {
Task.spawn(closeAllChats).then(next);
});
});
},
testBrowserResizeMinimized: function(next) {
this.testBrowserResize(next);
}
}

Просмотреть файл

@ -0,0 +1,66 @@
/* 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/. */
function isChatFocused(chat) {
return getChatBar()._isChatFocused(chat);
}
var manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
function test() {
waitForExplicitFinish();
// Note that (probably) due to bug 604289, if a tab is focused but the
// focused element is null, our chat windows can "steal" focus. This is
// avoided if we explicitly focus an element in the tab.
// So we load a page with an <input> field and focus that before testing.
let url = "data:text/html;charset=utf-8," + encodeURI('<input id="theinput">');
let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
let browser = tab.linkedBrowser;
browser.addEventListener("load", function tabLoad(event) {
browser.removeEventListener("load", tabLoad, true);
// before every test we focus the input field.
let preSubTest = function(cb) {
ContentTask.spawn(browser, null, function* () {
content.focus();
content.document.getElementById("theinput").focus();
yield ContentTaskUtils.waitForCondition(
() => Services.focus.focusedWindow == content, "tab should have focus");
}).then(cb);
}
let postSubTest = function(cb) {
Task.spawn(closeAllChats).then(cb);
}
// and run the tests.
runSocialTestWithProvider(manifest, function (finishcb) {
SocialSidebar.show();
runSocialTests(tests, preSubTest, postSubTest, function () {
BrowserTestUtils.removeTab(tab).then(finishcb);
});
});
}, true);
}
var tests = {
// In this test we arrange for the sidebar to open the chat via a simulated
// click. This should cause the new chat to be opened and focused.
testFocusWhenViaUser: function(next) {
ensureFrameLoaded(document.getElementById("social-sidebar-browser")).then(() => {
let chatbar = getChatBar();
openChatViaUser();
ok(chatbar.firstElementChild, "chat opened");
BrowserTestUtils.waitForCondition(() => isChatFocused(chatbar.selectedChat),
"chat should be focused").then(() => {
is(chatbar.selectedChat, chatbar.firstElementChild, "chat is selected");
next();
});
});
},
};

Просмотреть файл

@ -0,0 +1,74 @@
/* 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/. */
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifest = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
markURL: "https://test1.example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
markedIcon: "https://test1.example.com/browser/browser/base/content/test/social/unchecked.jpg",
unmarkedIcon: "https://test1.example.com/browser/browser/base/content/test/social/checked.jpg",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
function test() {
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addEventListener("OpenGraphData", function (aEvent) {
sendAsyncMessage("sharedata", aEvent.detail);
}, true, true);
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, function () {
mm.removeDelayedFrameScript(frameScript);
finishcb();
});
});
}
var tests = {
testMarkMicroformats: function(next) {
// emulates context menu action using target element, calling SocialMarks.markLink
let provider = Social._getProviderFromOrigin(manifest.origin);
let target, testTab;
// browser_share tests microformats on the full page, this is testing a
// specific target element.
let expecting = JSON.stringify({
"url": "https://example.com/browser/browser/base/content/test/social/microformats.html",
"microformats": {
"items": [{
"type": ["h-review"],
"properties": {
"rating": ["4.5"]
}
}
],
"rels": {},
"rel-urls": {}
}
});
let mm = getGroupMessageManager("social");
mm.addMessageListener("sharedata", function handler(msg) {
is(msg.data, expecting, "microformats data ok");
mm.removeMessageListener("sharedata", handler);
BrowserTestUtils.removeTab(testTab).then(next);
});
let url = "https://example.com/browser/browser/base/content/test/social/microformats.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => {
testTab = tab;
let doc = tab.linkedBrowser.contentDocument;
target = doc.getElementById("test-review");
SocialMarks.markLink(manifest.origin, url, target);
});
}
}

Просмотреть файл

@ -0,0 +1,187 @@
/* 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/. */
function gc() {
Cu.forceGC();
let wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
wu.garbageCollect();
}
var openChatWindow = Cu.import("resource://gre/modules/MozSocialAPI.jsm", {}).openChatWindow;
function openPanel(url, panelCallback, loadCallback) {
// open a flyout
SocialFlyout.open(url, 0, panelCallback);
// wait for both open and loaded before callback. Since the test doesn't close
// the panel between opens, we cannot rely on events here. We need to ensure
// popupshown happens before we finish out the tests.
BrowserTestUtils.waitForCondition(function() {
return SocialFlyout.panel.state == "open" &&
SocialFlyout.iframe.contentDocument.readyState == "complete";
},"flyout is open and loaded").then(() => { executeSoon(loadCallback) });
}
function openChat(url, panelCallback, loadCallback) {
// open a chat window
let chatbar = getChatBar();
openChatWindow(null, SocialSidebar.provider, url, panelCallback);
chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
executeSoon(loadCallback);
}, true);
}
function onSidebarLoad(callback) {
let sbrowser = document.getElementById("social-sidebar-browser");
sbrowser.addEventListener("load", function load() {
sbrowser.removeEventListener("load", load, true);
executeSoon(callback);
}, true);
}
var manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
function test() {
waitForExplicitFinish();
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, function(next) { goOnline().then(next) }, finishcb);
});
}
var tests = {
testSidebar: function(next) {
let sbrowser = document.getElementById("social-sidebar-browser");
onSidebarLoad(function() {
ok(sbrowser.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0, "sidebar is on social error page");
gc();
// Add a new load listener, then find and click the "try again" button.
onSidebarLoad(function() {
// should still be on the error page.
ok(sbrowser.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0, "sidebar is still on social error page");
// go online and try again - this should work.
goOnline().then(function () {
onSidebarLoad(function() {
// should now be on the correct page.
is(sbrowser.contentDocument.documentURI, manifest.sidebarURL, "sidebar is now on social sidebar page");
next();
});
sbrowser.contentDocument.getElementById("btnTryAgain").click();
});
});
sbrowser.contentDocument.getElementById("btnTryAgain").click();
});
// go offline then attempt to load the sidebar - it should fail.
goOffline().then(function() {
SocialSidebar.show();
});
},
testFlyout: function(next) {
let panelCallbackCount = 0;
let panel = document.getElementById("social-flyout-panel");
goOffline().then(function() {
openPanel(
manifest.sidebarURL, /* empty html page */
function() { // the panel api callback
panelCallbackCount++;
},
function() { // the "load" callback.
todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
let href = panel.firstChild.contentDocument.documentURI;
ok(href.indexOf("about:socialerror?mode=compactInfo")==0, "flyout is on social error page");
// Bug 832943 - the listeners previously stopped working after a GC, so
// force a GC now and try again.
gc();
openPanel(
manifest.sidebarURL, /* empty html page */
function() { // the panel api callback
panelCallbackCount++;
},
function() { // the "load" callback.
todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
let href = panel.firstChild.contentDocument.documentURI;
ok(href.indexOf("about:socialerror?mode=compactInfo")==0, "flyout is on social error page");
gc();
SocialFlyout.unload();
next();
}
);
}
);
});
},
testChatWindow: function(next) {
todo(false, "Bug 1245799 is needed to make error pages work again for chat windows.");
next();
return;
let panelCallbackCount = 0;
// chatwindow tests throw errors, which muddy test output, if the worker
// doesn't get test-init
goOffline().then(function() {
openChat(
manifest.sidebarURL, /* empty html page */
function() { // the panel api callback
panelCallbackCount++;
},
function() { // the "load" callback.
todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
let chat = getChatBar().selectedChat;
BrowserTestUtils.waitForCondition(() => chat.content != null && chat.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0,
"error page didn't appear").then(() => {
chat.close();
next();
});
}
);
});
},
testChatWindowAfterTearOff: function(next) {
todo(false, "Bug 1245799 is needed to make error pages work again for chat windows.");
next();
return;
// Ensure that the error listener survives the chat window being detached.
let url = manifest.sidebarURL; /* empty html page */
let panelCallbackCount = 0;
// chatwindow tests throw errors, which muddy test output, if the worker
// doesn't get test-init
// open a chat while we are still online.
openChat(
url,
null,
function() { // the "load" callback.
let chat = getChatBar().selectedChat;
is(chat.contentDocument.documentURI, url, "correct url loaded");
// toggle to a detached window.
chat.swapWindows().then(chat => {
ok(!!chat.content, "we have chat content 1");
BrowserTestUtils.waitForCondition(() => chat.content != null && chat.contentDocument.readyState == "complete",
"swapped window loaded").then(() => {
// now go offline and reload the chat - about:socialerror should be loaded.
goOffline().then(() => {
ok(!!chat.content, "we have chat content 2");
chat.contentDocument.location.reload();
info("chat reload called");
BrowserTestUtils.waitForCondition(() => chat.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly")==0,
"error page didn't appear").then(() => {
chat.close();
next();
});
});
});
});
}
);
}
}

Просмотреть файл

@ -0,0 +1,102 @@
/* 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/. */
function test() {
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addMessageListener("socialTest-CloseSelf", function(e) {
content.close();
});
addMessageListener("socialTest-sendEvent", function(msg) {
let data = msg.data;
let evt = content.document.createEvent("CustomEvent");
evt.initCustomEvent(data.name, true, true, JSON.stringify(data.data));
content.document.documentElement.dispatchEvent(evt);
});
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
runSocialTestWithProvider(manifest, function (finishcb) {
SocialSidebar.show();
ensureFrameLoaded(SocialSidebar.browser, manifest.sidebarURL).then(() => {
// disable transitions for the test
registerCleanupFunction(function () {
SocialFlyout.panel.removeAttribute("animate");
});
SocialFlyout.panel.setAttribute("animate", "false");
runSocialTests(tests, undefined, undefined, finishcb);
});
});
}
var tests = {
testResizeFlyout: function(next) {
let panel = document.getElementById("social-flyout-panel");
BrowserTestUtils.waitForEvent(panel, "popupshown").then(() => {
is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
// The width of the flyout should be 400px initially
let iframe = panel.firstChild;
let body = iframe.contentDocument.body;
let cs = iframe.contentWindow.getComputedStyle(body);
is(cs.width, "400px", "should be 400px wide");
is(iframe.boxObject.width, 400, "iframe should now be 400px wide");
is(cs.height, "400px", "should be 400px high");
is(iframe.boxObject.height, 400, "iframe should now be 400px high");
BrowserTestUtils.waitForEvent(iframe.contentWindow, "resize").then(() => {
cs = iframe.contentWindow.getComputedStyle(body);
is(cs.width, "500px", "should now be 500px wide");
is(iframe.boxObject.width, 500, "iframe should now be 500px wide");
is(cs.height, "500px", "should now be 500px high");
is(iframe.boxObject.height, 500, "iframe should now be 500px high");
BrowserTestUtils.waitForEvent(panel, "popuphidden").then(next);
panel.hidePopup();
});
SocialFlyout.dispatchPanelEvent("socialTest-MakeWider");
});
SocialSidebar.browser.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
},
testCloseSelf: function(next) {
let panel = document.getElementById("social-flyout-panel");
BrowserTestUtils.waitForEvent(panel, "popupshown").then(() => {
is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
BrowserTestUtils.waitForEvent(panel, "popuphidden").then(next);
let mm = panel.firstChild.messageManager;
mm.sendAsyncMessage("socialTest-CloseSelf", {});
});
SocialSidebar.browser.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
},
testCloseOnLinkTraversal: function(next) {
BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen", true).then(event => {
BrowserTestUtils.waitForCondition(function() { return panel.state == "closed" },
"panel should close after tab open").then(() => {
BrowserTestUtils.removeTab(event.target).then(next);
});
});
let panel = document.getElementById("social-flyout-panel");
BrowserTestUtils.waitForEvent(panel, "popupshown").then(() => {
is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
is(panel.state, "open", "flyout should be open");
let iframe = panel.firstChild;
iframe.contentDocument.getElementById('traversal').click();
});
SocialSidebar.browser.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
}
}

Просмотреть файл

@ -0,0 +1,51 @@
/* 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/. */
function test() {
waitForExplicitFinish();
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
let frameScript = "data:,(" + function frame_script() {
addEventListener("visibilitychange", function() {
sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
});
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
registerCleanupFunction(function () {
mm.removeDelayedFrameScript(frameScript);
});
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, finishcb);
});
}
var tests = {
testIsVisible: function(next) {
let mm = getGroupMessageManager("social");
mm.addMessageListener("visibility", function handler(msg) {
mm.removeMessageListener("visibility", handler);
is(msg.data, "shown", "sidebar is visible");
next();
});
SocialSidebar.show();
},
testIsNotVisible: function(next) {
let mm = getGroupMessageManager("social");
mm.addMessageListener("visibility", function handler(msg) {
mm.removeMessageListener("visibility", handler);
is(msg.data, "hidden", "sidebar is hidden");
next();
});
SocialSidebar.hide();
}
}

Просмотреть файл

@ -0,0 +1,232 @@
/* 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/. */
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifest2 = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
markURL: "https://test1.example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
markedIcon: "https://test1.example.com/browser/browser/base/content/test/social/unchecked.jpg",
unmarkedIcon: "https://test1.example.com/browser/browser/base/content/test/social/checked.jpg",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
var manifest3 = { // used for testing install
name: "provider test2",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
function test() {
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addEventListener("visibilitychange", function() {
sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
});
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
PopupNotifications.panel.setAttribute("animate", "false");
registerCleanupFunction(function () {
PopupNotifications.panel.removeAttribute("animate");
mm.removeDelayedFrameScript(frameScript);
});
runSocialTests(tests, undefined, undefined, finish);
}
var tests = {
testButtonDisabledOnActivate: function(next) {
// starting on about:blank page, share should be visible but disabled when
// adding provider
is(gBrowser.selectedBrowser.currentURI.spec, "about:blank");
SocialService.addProvider(manifest2, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id).forWindow(window)
ok(widget.node, "button added to widget set");
// bypass widget go directly to dom, check attribute states
let button = document.getElementById(id);
is(button.disabled, true, "mark button is disabled");
// verify the attribute for proper css
is(button.getAttribute("disabled"), "true", "mark button attribute is disabled");
// button should be visible
is(button.hidden, false, "mark button is visible");
checkSocialUI(window);
SocialService.disableProvider(manifest2.origin, next);
});
},
testNoButtonOnEnable: function(next) {
// we expect the addon install dialog to appear, we need to accept the
// install from the dialog.
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
info("servicesInstall-notification panel opened");
panel.button.click();
});
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
let doc = tab.linkedBrowser.contentDocument;
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest3,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest3.origin, function(provider) {
is(provider.origin, manifest3.origin, "provider is installed");
let id = SocialMarks._toolbarHelper.idFromOrigin(provider.origin);
let widget = CustomizableUI.getWidget(id);
ok(!widget || !widget.forWindow(window).node, "no button added to widget set");
Social.uninstallProvider(manifest3.origin, function() {
BrowserTestUtils.removeTab(tab).then(next);
});
});
});
});
},
testButtonOnEnable: function(next) {
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
info("servicesInstall-notification panel opened");
panel.button.click();
});
// enable the provider now
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
let doc = tab.linkedBrowser.contentDocument;
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
SocialService.enableProvider(manifest2.origin, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id).forWindow(window)
ok(widget.node, "button added to widget set");
// bypass widget go directly to dom, check attribute states
let button = document.getElementById(id);
is(button.disabled, false, "mark button is disabled");
// verify the attribute for proper css
ok(!button.hasAttribute("disabled"), "mark button attribute is disabled");
// button should be visible
is(button.hidden, false, "mark button is visible");
checkSocialUI(window);
BrowserTestUtils.removeTab(tab).then(next);
});
});
});
},
testMarkPanel: function(next) {
// click on panel to open and wait for visibility
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider.enabled, "provider is enabled");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id);
let btn = widget.forWindow(window).node;
ok(btn, "got a mark button");
let ourTab;
BrowserTestUtils.waitForEvent(btn.panel, "popupshown").then(() => {
info("marks panel shown");
let doc = btn.contentDocument;
let unmarkBtn = doc.getElementById("unmark");
ok(unmarkBtn, "testMarkPanel - got the panel unmark button");
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
});
BrowserTestUtils.waitForEvent(btn.panel, "popuphidden").then(() => {
BrowserTestUtils.removeTab(ourTab).then(() => {
ok(btn.disabled, "button is disabled");
next();
});
});
// verify markbutton is disabled when there is no browser url
ok(btn.disabled, "button is disabled");
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
ourTab = tab;
ok(!btn.disabled, "button is enabled");
// first click marks the page, second click opens the page. We have to
// synthesize so the command event happens
EventUtils.synthesizeMouseAtCenter(btn, {});
// wait for the button to be marked, click to open panel
is(btn.panel.state, "closed", "panel should not be visible yet");
BrowserTestUtils.waitForCondition(() => btn.isMarked, "button is marked").then(() => {
EventUtils.synthesizeMouseAtCenter(btn, {});
});
});
},
testMarkPanelOffline: function(next) {
// click on panel to open and wait for visibility
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider.enabled, "provider is enabled");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id);
let btn = widget.forWindow(window).node;
ok(btn, "got a mark button");
// verify markbutton is disabled when there is no browser url
ok(btn.disabled, "button is disabled");
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html";
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
ok(!btn.disabled, "button is enabled");
goOffline().then(function() {
info("testing offline error page");
// wait for popupshown
BrowserTestUtils.waitForEvent(btn.panel, "popupshown").then(() => {
info("marks panel is open");
ensureFrameLoaded(btn.content).then(() => {
is(btn.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly"), 0, "social error page is showing "+btn.contentDocument.documentURI);
// cleanup after the page has been unmarked
BrowserTestUtils.removeTab(tab).then(() => {
ok(btn.disabled, "button is disabled");
goOnline().then(next);
});
});
});
btn.markCurrentPage();
});
});
},
testButtonOnDisable: function(next) {
// enable the provider now
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider, "provider is installed");
SocialService.disableProvider(manifest2.origin, function() {
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
BrowserTestUtils.waitForCondition(() => {
// getWidget now returns null since we've destroyed the widget
return !CustomizableUI.getWidget(id)
}, "button does not exist after disabling the provider").then(() => {
checkSocialUI(window);
Social.uninstallProvider(manifest2.origin, next);
});
});
}
}

Просмотреть файл

@ -0,0 +1,106 @@
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
function makeMarkProvider(origin) {
return { // used for testing install
name: "mark provider " + origin,
origin: "https://" + origin + ".example.com",
markURL: "https://" + origin + ".example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
markedIcon: "https://" + origin + ".example.com/browser/browser/base/content/test/social/unchecked.jpg",
unmarkedIcon: "https://" + origin + ".example.com/browser/browser/base/content/test/social/checked.jpg",
iconURL: "https://" + origin + ".example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
}
}
function test() {
waitForExplicitFinish();
PopupNotifications.panel.setAttribute("animate", "false");
registerCleanupFunction(function () {
PopupNotifications.panel.removeAttribute("animate");
});
runSocialTests(tests, undefined, undefined, finish);
}
var tests = {
testContextSubmenu: function(next) {
// install 4 providers to test that the menu's are added as submenus
let manifests = [
makeMarkProvider("sub1.test1"),
makeMarkProvider("sub2.test1"),
makeMarkProvider("sub1.test2"),
makeMarkProvider("sub2.test2")
];
let installed = [];
let markLinkMenu = document.getElementById("context-marklinkMenu").firstChild;
let markPageMenu = document.getElementById("context-markpageMenu").firstChild;
function addProviders(callback) {
let manifest = manifests.pop();
if (!manifest) {
info("INSTALLATION FINISHED");
executeSoon(callback);
return;
}
info("INSTALLING " + manifest.origin);
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
info("servicesInstall-notification panel opened");
panel.button.click();
});
let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest.origin);
let toolbar = document.getElementById("nav-bar");
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
let doc = tab.linkedBrowser.contentDocument;
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest.origin, function(provider) {
BrowserTestUtils.waitForCondition(() => { return CustomizableUI.getWidget(id) },
"button exists after enabling social").then(() => {
BrowserTestUtils.removeTab(tab).then(() => {
installed.push(manifest.origin);
// checkSocialUI will properly check where the menus are located
checkSocialUI(window);
executeSoon(function() {
addProviders(callback);
});
});
});
});
});
});
}
function removeProviders(callback) {
let origin = installed.pop();
if (!origin) {
executeSoon(callback);
return;
}
Social.uninstallProvider(origin, function(provider) {
executeSoon(function() {
removeProviders(callback);
});
});
}
addProviders(function() {
removeProviders(function() {
is(SocialMarks.getProviders().length, 0, "mark providers removed");
is(markLinkMenu.childNodes.length, 0, "marklink menu ok");
is(markPageMenu.childNodes.length, 0, "markpage menu ok");
checkSocialUI(window);
next();
});
});
}
}

Просмотреть файл

@ -0,0 +1,78 @@
/* 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/. */
function test() {
waitForExplicitFinish();
runSocialTestWithProvider(gProviders, function (finishcb) {
SocialSidebar.provider = Social.providers[0];
SocialSidebar.show();
is(Social.providers[0].origin, SocialSidebar.provider.origin, "selected provider in sidebar");
runSocialTests(tests, undefined, undefined, finishcb);
});
}
var gProviders = [
{
name: "provider 1",
origin: "https://test1.example.com",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
iconURL: "chrome://branding/content/icon48.png"
},
{
name: "provider 2",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
iconURL: "chrome://branding/content/icon48.png"
}
];
var tests = {
testProviderSwitch: function(next) {
let sbrowser = document.getElementById("social-sidebar-browser");
let menu = document.getElementById("social-statusarea-popup");
let button = document.getElementById("social-sidebar-button");
function checkProviderMenu(selectedProvider) {
let menuProviders = menu.querySelectorAll(".social-provider-menuitem");
is(menuProviders.length, gProviders.length, "correct number of providers listed in the menu");
// Find the selectedProvider's menu item
let el = menu.getElementsByAttribute("origin", selectedProvider.origin);
is(el.length, 1, "selected provider menu item exists");
is(el[0].getAttribute("checked"), "true", "selected provider menu item is checked");
}
// the menu is not populated until onpopupshowing, so wait for popupshown
BrowserTestUtils.waitForEvent(menu, "popupshown", true).then(()=>{
menu.hidePopup(); // doesn't need visibility
// first provider should already be visible in the sidebar
is(Social.providers[0].origin, SocialSidebar.provider.origin, "selected provider in sidebar");
checkProviderMenu(Social.providers[0]);
// Now activate "provider 2"
BrowserTestUtils.waitForEvent(sbrowser, "load", true).then(()=>{
checkUIStateMatchesProvider(Social.providers[1]);
BrowserTestUtils.waitForEvent(sbrowser, "load", true).then(()=>{
checkUIStateMatchesProvider(Social.providers[0]);
next();
});
// show the menu again so the menu is updated with the correct commands
BrowserTestUtils.waitForEvent(menu, "popupshown", true).then(()=>{
// click on the provider menuitem to switch providers
let el = menu.getElementsByAttribute("origin", Social.providers[0].origin);
is(el.length, 1, "selected provider menu item exists");
EventUtils.synthesizeMouseAtCenter(el[0], {});
});
EventUtils.synthesizeMouseAtCenter(button, {});
});
SocialSidebar.provider = Social.providers[1];
});
EventUtils.synthesizeMouseAtCenter(button, {});
}
}
function checkUIStateMatchesProvider(provider) {
// Sidebar
is(document.getElementById("social-sidebar-browser").getAttribute("src"), provider.sidebarURL, "side bar URL is set");
}

Просмотреть файл

@ -0,0 +1,98 @@
/* 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/. */
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
function test() {
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addEventListener("visibilitychange", function() {
sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
});
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
registerCleanupFunction(function () {
mm.removeDelayedFrameScript(frameScript);
});
SocialService.addProvider(manifest, function() {
// the test will remove the provider
doTest();
});
}
function doTest() {
ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
ok(!SocialSidebar.opened, "social sidebar should not be open by default");
let command = document.getElementById("Social:ToggleSidebar");
let sidebar = document.getElementById("social-sidebar-box");
let browser = sidebar.lastChild;
ok(!browser.docShellIsActive, "sidebar is not active");
is(sidebar.hidden, true, "sidebar should be hidden");
is(command.getAttribute("checked"), "false", "toggle command should be unchecked");
function checkShown(shouldBeShown) {
is(command.getAttribute("checked"), shouldBeShown ? "true" : "false",
"toggle command should be " + (shouldBeShown ? "checked" : "unchecked"));
is(sidebar.hidden, !shouldBeShown,
"sidebar should be " + (shouldBeShown ? "visible" : "hidden"));
is(browser.docShellIsActive, shouldBeShown, "sidebar isActive in correct state");
if (shouldBeShown) {
is(browser.getAttribute('src'), SocialSidebar.provider.sidebarURL, "sidebar url should be set");
// We don't currently check docShellIsActive as this is only set
// after load event fires, and the tests below explicitly wait for this
// anyway.
}
else {
ok(!browser.docShellIsActive, "sidebar should have an inactive docshell");
// sidebar will only be immediately unloaded (and thus set to
// about:blank) when canShow is false.
if (SocialSidebar.canShow) {
// should not have unloaded so will still be the provider URL.
is(browser.getAttribute('src'), SocialSidebar.provider.sidebarURL, "sidebar url should be set");
} else {
// should have been an immediate unload.
is(browser.getAttribute('src'), "about:blank", "sidebar url should be blank");
}
}
}
ensureFrameLoaded(browser).then(() => {
// First check the the sidebar is initially visible, and loaded
ok(!command.hidden, "toggle command should be visible");
let mm = getGroupMessageManager("social");
mm.addMessageListener("visibility", function shown(msg) {
if (msg.data == "shown") {
mm.removeMessageListener("visibility", shown);
checkShown(true);
info("Toggling sidebar to closed");
SocialSidebar.toggleSidebar();
}
});
mm.addMessageListener("visibility", function handler(msg) {
if (msg.data == "hidden") {
mm.removeMessageListener("visibility", handler);
// disable social.
SocialService.disableProvider(SocialSidebar.provider.origin, function() {
checkShown(false);
is(Social.providers.length, 0, "no providers left");
defaultFinishChecks();
// Finish the test
executeSoon(finish);
});
}
});
});
SocialSidebar.show();
}

Просмотреть файл

@ -0,0 +1,216 @@
/* 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/. */
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
var manifest = { // builtin provider
name: "provider example.com",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
var manifest2 = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
statusURL: "https://test1.example.com/browser/browser/base/content/test/social/social_panel.html",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
var manifest3 = { // used for testing install
name: "provider test2",
origin: "https://test2.example.com",
sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
version: "1.0"
};
function openWindowAndWaitForInit(callback) {
let topic = "browser-delayed-startup-finished";
let w = OpenBrowserWindow();
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, topic);
executeSoon(() => callback(w));
}, topic, false);
}
function test() {
waitForExplicitFinish();
let frameScript = "data:,(" + function frame_script() {
addMessageListener("socialTest-sendEvent", function(msg) {
let data = msg.data;
let evt = content.document.createEvent("CustomEvent");
evt.initCustomEvent(data.name, true, true, JSON.stringify(data.data));
content.document.documentElement.dispatchEvent(evt);
});
}.toString() + ")();";
let mm = getGroupMessageManager("social");
mm.loadFrameScript(frameScript, true);
PopupNotifications.panel.setAttribute("animate", "false");
registerCleanupFunction(function () {
PopupNotifications.panel.removeAttribute("animate");
mm.removeDelayedFrameScript(frameScript);
});
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, function () {
Services.prefs.clearUserPref("social.remote-install.enabled");
// just in case the tests failed, clear these here as well
Services.prefs.clearUserPref("social.whitelist");
CustomizableUI.reset();
finishcb();
});
});
}
var tests = {
testNoButtonOnEnable: function(next) {
// we expect the addon install dialog to appear, we need to accept the
// install from the dialog.
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
info("servicesInstall-notification panel opened");
panel.button.click();
})
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
let doc = tab.linkedBrowser.contentDocument;
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest3,
window: window
}
Social.installProvider(data, function(addonManifest) {
// enable the provider so we know the button would have appeared
SocialService.enableProvider(manifest3.origin, function(provider) {
is(provider.origin, manifest3.origin, "provider is installed");
let id = SocialStatus._toolbarHelper.idFromOrigin(provider.origin);
let widget = CustomizableUI.getWidget(id);
ok(!widget || !widget.forWindow(window).node, "no button added to widget set");
Social.uninstallProvider(manifest3.origin, function() {
BrowserTestUtils.removeTab(tab).then(next);
});
});
});
});
},
testButtonOnEnable: function(next) {
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
info("servicesInstall-notification panel opened");
panel.button.click();
});
// enable the provider now
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
BrowserTestUtils.openNewForegroundTab(gBrowser, activationURL).then(tab => {
let doc = tab.linkedBrowser.contentDocument;
let data = {
origin: doc.nodePrincipal.origin,
url: doc.location.href,
manifest: manifest2,
window: window
}
Social.installProvider(data, function(addonManifest) {
SocialService.enableProvider(manifest2.origin, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id).forWindow(window);
ok(widget.node, "button added to widget set");
checkSocialUI(window);
BrowserTestUtils.removeTab(tab).then(next);
});
});
});
},
testStatusPanel: function(next) {
let icon = {
name: "testIcon",
iconURL: "chrome://browser/skin/Info.png",
counter: 1
};
// click on panel to open and wait for visibility
let provider = Social._getProviderFromOrigin(manifest2.origin);
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id);
let btn = widget.forWindow(window).node;
// Disable the transition
let panel = document.getElementById("social-notification-panel");
panel.setAttribute("animate", "false");
BrowserTestUtils.waitForEvent(panel, "popupshown").then(() => {
ensureFrameLoaded(panel.firstChild).then(() => {
let mm = panel.firstChild.messageManager;
mm.sendAsyncMessage("socialTest-sendEvent", { name: "Social:Notification", data: icon });
BrowserTestUtils.waitForCondition(
() => { return btn.getAttribute("badge"); }, "button updated by notification").then(() => {
is(btn.style.listStyleImage, "url(\"" + icon.iconURL + "\")", "notification icon updated");
panel.hidePopup();
});
});
});
BrowserTestUtils.waitForEvent(panel, "popuphidden").then(() => {
panel.removeAttribute("animate");
next();
});
btn.click(); // open the panel
},
testPanelOffline: function(next) {
// click on panel to open and wait for visibility
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider.enabled, "provider is enabled");
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id);
let btn = widget.forWindow(window).node;
ok(btn, "got a status button");
let frameId = btn.getAttribute("notificationFrameId");
let frame = document.getElementById(frameId);
goOffline().then(function() {
info("testing offline error page");
// wait for popupshown
let panel = document.getElementById("social-notification-panel");
BrowserTestUtils.waitForEvent(panel, "popupshown").then(() => {
ensureFrameLoaded(frame).then(() => {
is(frame.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly"), 0, "social error page is showing "+frame.contentDocument.documentURI);
// We got our error page, reset to avoid test leak.
BrowserTestUtils.waitForEvent(frame, "load", true).then(() => {
is(frame.contentDocument.documentURI, "about:blank", "closing error panel");
BrowserTestUtils.waitForEvent(panel, "popuphidden").then(next);
panel.hidePopup();
});
goOnline().then(() => {
info("resetting error panel");
frame.setAttribute("src", "about:blank");
});
});
});
// reload after going offline, wait for unload to open panel
BrowserTestUtils.waitForEvent(frame, "unload", true).then(() => {
btn.click();
});
frame.contentDocument.location.reload();
});
},
testButtonOnDisable: function(next) {
// enable the provider now
let provider = Social._getProviderFromOrigin(manifest2.origin);
ok(provider, "provider is installed");
SocialService.disableProvider(manifest2.origin, function() {
let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
BrowserTestUtils.waitForCondition(() => { return !document.getElementById(id) },
"button does not exist after disabling the provider").then(() => {
Social.uninstallProvider(manifest2.origin, next);
});
});
}
}

Просмотреть файл

@ -0,0 +1,251 @@
// 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/.
// Test the top-level window UI for social.
var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
// This function should "reset" Social such that the next time Social.init()
// is called (eg, when a new window is opened), it re-performs all
// initialization.
function resetSocial() {
Social.initialized = false;
Social.providers = [];
// *sob* - listeners keep getting added...
SocialService._providerListeners.clear();
}
var createdWindows = [];
function openWindowAndWaitForInit(parentWin, callback) {
// this notification tells us SocialUI.init() has been run...
let topic = "browser-delayed-startup-finished";
let w = parentWin.OpenBrowserWindow();
createdWindows.push(w);
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, topic);
info(topic + " observer was notified - continuing test");
executeSoon(() => callback(w));
}, topic, false);
}
function closeWindow(w, cb) {
waitForNotification("domwindowclosed", cb);
w.close();
}
function closeOneWindow(cb) {
let w = createdWindows.pop();
if (!w || w.closed) {
cb();
return;
}
closeWindow(w, function() {
closeOneWindow(cb);
});
w.close();
}
function postTestCleanup(cb) {
closeOneWindow(cb);
}
var manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
};
var manifest2 = { // used for testing install
name: "provider test1",
origin: "https://test1.example.com",
sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
};
function test() {
waitForExplicitFinish();
requestLongerTimeout(2);
runSocialTests(tests, undefined, postTestCleanup);
}
var tests = {
// check when social is totally disabled at startup (ie, no providers enabled)
testInactiveStartup: function(cbnext) {
is(Social.providers.length, 0, "needs zero providers to start this test.");
ok(!SocialService.hasEnabledProviders, "no providers are enabled");
resetSocial();
openWindowAndWaitForInit(window, function(w1) {
checkSocialUI(w1);
// Now social is (re-)initialized, open a secondary window and check that.
openWindowAndWaitForInit(window, function(w2) {
checkSocialUI(w2);
checkSocialUI(w1);
cbnext();
});
});
},
// Check when providers are enabled and social is turned on at startup.
testEnabledStartup: function(cbnext) {
setManifestPref("social.manifest.test", manifest);
ok(!SocialSidebar.opened, "sidebar is closed initially");
SocialService.addProvider(manifest, function() {
SocialService.addProvider(manifest2, function (provider) {
SocialSidebar.show();
BrowserTestUtils.waitForCondition(
() => SocialSidebar.opened, "sidebar did not open").then(() => {
ok(SocialSidebar.opened, "first window sidebar is open");
openWindowAndWaitForInit(window, function(w1) {
ok(w1.SocialSidebar.opened, "new window sidebar is open");
ok(SocialService.hasEnabledProviders, "providers are enabled");
checkSocialUI(w1);
// now init is complete, open a second window
openWindowAndWaitForInit(window, function(w2) {
ok(w1.SocialSidebar.opened, "w1 sidebar is open");
ok(w2.SocialSidebar.opened, "w2 sidebar is open");
checkSocialUI(w2);
checkSocialUI(w1);
// disable social and re-check
SocialService.disableProvider(manifest.origin, function() {
SocialService.disableProvider(manifest2.origin, function() {
ok(!Social.enabled, "social is disabled");
is(Social.providers.length, 0, "no providers");
ok(!w1.SocialSidebar.opened, "w1 sidebar is closed");
ok(!w2.SocialSidebar.opened, "w2 sidebar is closed");
checkSocialUI(w2);
checkSocialUI(w1);
Services.prefs.clearUserPref("social.manifest.test");
cbnext();
});
});
});
});
});
}, cbnext);
}, cbnext);
},
testGlobalState: function(cbnext) {
setManifestPref("social.manifest.test", manifest);
ok(!SocialSidebar.opened, "sidebar is closed initially");
ok(!Services.prefs.prefHasUserValue("social.sidebar.provider"), "global state unset");
// mimick no session state in opener so we exercise the global state via pref
SessionStore.deleteWindowValue(window, "socialSidebar");
ok(!SessionStore.getWindowValue(window, "socialSidebar"), "window state unset");
SocialService.addProvider(manifest, function() {
openWindowAndWaitForInit(window, function(w1) {
w1.SocialSidebar.show();
BrowserTestUtils.waitForCondition(() => w1.SocialSidebar.opened, "sidebar opened").then(() => {
ok(Services.prefs.prefHasUserValue("social.sidebar.provider"), "global state set");
ok(!SocialSidebar.opened, "1. main sidebar is still closed");
ok(w1.SocialSidebar.opened, "1. window sidebar is open");
closeWindow(w1, function() {
// this time, the global state should cause the sidebar to be opened
// in the new window
openWindowAndWaitForInit(window, function(w1) {
ok(!SocialSidebar.opened, "2. main sidebar is still closed");
ok(w1.SocialSidebar.opened, "2. window sidebar is open");
w1.SocialSidebar.hide();
ok(!w1.SocialSidebar.opened, "2. window sidebar is closed");
ok(!Services.prefs.prefHasUserValue("social.sidebar.provider"), "2. global state unset");
// global state should now be no sidebar gets opened on new window
closeWindow(w1, function() {
ok(!Services.prefs.prefHasUserValue("social.sidebar.provider"), "3. global state unset");
ok(!SocialSidebar.opened, "3. main sidebar is still closed");
openWindowAndWaitForInit(window, function(w1) {
ok(!Services.prefs.prefHasUserValue("social.sidebar.provider"), "4. global state unset");
ok(!SocialSidebar.opened, "4. main sidebar is still closed");
ok(!w1.SocialSidebar.opened, "4. window sidebar is closed");
SocialService.disableProvider(manifest.origin, function() {
Services.prefs.clearUserPref("social.manifest.test");
cbnext();
});
});
});
});
});
});
});
});
},
// Check per window sidebar functionality, including migration from using
// prefs to using session state, and state inheritance of windows (new windows
// inherit state from the opener).
testPerWindowSidebar: function(cbnext) {
function finishCheck() {
// disable social and re-check
SocialService.disableProvider(manifest.origin, function() {
SocialService.disableProvider(manifest2.origin, function() {
ok(!Social.enabled, "social is disabled");
is(Social.providers.length, 0, "no providers");
Services.prefs.clearUserPref("social.manifest.test");
cbnext();
});
});
}
setManifestPref("social.manifest.test", manifest);
ok(!SocialSidebar.opened, "sidebar is closed initially");
SocialService.addProvider(manifest, function() {
SocialService.addProvider(manifest2, function (provider) {
// test the migration of the social.sidebar.open pref. We'll set a user
// level pref to indicate it was open (along with the old
// social.provider.current pref), then we'll open a window. During the
// restoreState of the window, those prefs should be migrated, and the
// sidebar should be opened. Both prefs are then removed.
Services.prefs.setCharPref("social.provider.current", "https://example.com");
Services.prefs.setBoolPref("social.sidebar.open", true);
openWindowAndWaitForInit(window, function(w1) {
ok(w1.SocialSidebar.opened, "new window sidebar is open");
ok(SocialService.hasEnabledProviders, "providers are enabled");
ok(!Services.prefs.prefHasUserValue("social.provider.current"), "social.provider.current pref removed");
ok(!Services.prefs.prefHasUserValue("social.sidebar.open"), "social.sidebar.open pref removed");
checkSocialUI(w1);
// now init is complete, open a second window, it's state should be the same as the opener
openWindowAndWaitForInit(w1, function(w2) {
ok(w1.SocialSidebar.opened, "w1 sidebar is open");
ok(w2.SocialSidebar.opened, "w2 sidebar is open");
checkSocialUI(w2);
checkSocialUI(w1);
// change the sidebar in w2
w2.SocialSidebar.show(manifest2.origin);
let sbrowser1 = w1.document.getElementById("social-sidebar-browser");
is(manifest.origin, sbrowser1.getAttribute("origin"), "w1 sidebar origin matches");
let sbrowser2 = w2.document.getElementById("social-sidebar-browser");
is(manifest2.origin, sbrowser2.getAttribute("origin"), "w2 sidebar origin matches");
// hide sidebar, w1 sidebar should still be open
w2.SocialSidebar.hide();
ok(w1.SocialSidebar.opened, "w1 sidebar is opened");
ok(!w2.SocialSidebar.opened, "w2 sidebar is closed");
ok(sbrowser2.parentNode.hidden, "w2 sidebar is hidden");
// open a 3rd window from w2, it should inherit the state of w2
openWindowAndWaitForInit(w2, function(w3) {
// since the sidebar is not open, we need to ensure the provider
// is selected to test we inherited the provider from the opener
w3.SocialSidebar.ensureProvider();
is(w3.SocialSidebar.provider, w2.SocialSidebar.provider, "w3 has same provider as w2");
ok(!w3.SocialSidebar.opened, "w2 sidebar is closed");
// open a 4th window from w1, it should inherit the state of w1
openWindowAndWaitForInit(w1, function(w4) {
is(w4.SocialSidebar.provider, w1.SocialSidebar.provider, "w4 has same provider as w1");
ok(w4.SocialSidebar.opened, "w4 sidebar is opened");
finishCheck();
});
});
});
});
}, cbnext);
}, cbnext);
}
}

Двоичные данные
browser/base/content/test/social/checked.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 785 B

Просмотреть файл

@ -56,15 +56,17 @@ function defaultFinishChecks() {
function runSocialTestWithProvider(manifest, callback, finishcallback) {
let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
let manifests = Array.isArray(manifest) ? manifest : [manifest];
// Check that none of the provider's content ends up in history.
function finishCleanUp() {
ok(!SocialSidebar.provider, "no provider in sidebar");
SessionStore.setWindowValue(window, "socialSidebar", "");
for (let i = 0; i < manifests.length; i++) {
let m = manifests[i];
for (let what of ['iconURL', 'shareURL']) {
for (let what of ['sidebarURL', 'iconURL', 'shareURL', 'markURL']) {
if (m[what]) {
yield promiseSocialUrlNotRemembered(m[what]);
}
@ -193,7 +195,14 @@ function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
// A fairly large hammer which checks all aspects of the SocialUI for
// internal consistency.
function checkSocialUI(win) {
let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
win = win || window;
let doc = win.document;
let enabled = win.SocialUI.enabled;
let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
!PrivateBrowsingUtils.isWindowPrivate(win);
let sidebarEnabled = win.SocialSidebar.provider ? enabled : false;
// if we have enabled providers, we should also have instances of those
// providers
if (SocialService.hasEnabledProviders) {
@ -201,6 +210,78 @@ function checkSocialUI(win) {
} else {
is(Social.providers.length, 0, "providers are not enabled");
}
// some local helpers to avoid log-spew for the many checks made here.
let numGoodTests = 0, numTests = 0;
function _ok(what, msg) {
numTests++;
if (!ok)
ok(what, msg)
else
++numGoodTests;
}
function _is(a, b, msg) {
numTests++;
if (a != b)
is(a, b, msg)
else
++numGoodTests;
}
function isbool(a, b, msg) {
_is(!!a, !!b, msg);
}
isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?");
let contextMenus = [
{
type: "link",
id: "context-marklinkMenu",
label: "social.marklinkMenu.label"
},
{
type: "page",
id: "context-markpageMenu",
label: "social.markpageMenu.label"
}
];
for (let c of contextMenus) {
let leMenu = document.getElementById(c.id);
let parent, menus;
let markProviders = SocialMarks.getProviders();
if (markProviders.length > SocialMarks.MENU_LIMIT) {
// menus should be in a submenu, not in the top level of the context menu
parent = leMenu.firstChild;
menus = document.getElementsByClassName("context-mark" + c.type);
_is(menus.length, 0, "menu's are not in main context menu\n");
menus = parent.childNodes;
_is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
} else {
// menus should be in the top level of the context menu, not in a submenu
parent = leMenu.parentNode;
menus = document.getElementsByClassName("context-mark" + c.type);
_is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
menus = leMenu.firstChild.childNodes;
_is(menus.length, 0, "menu's are not in context submenu\n");
}
for (let m of menus)
_is(m.parentNode, parent, "menu has correct parent");
}
// and for good measure, check all the social commands.
isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?");
isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
// and report on overall success of failure of the various checks here.
is(numGoodTests, numTests, "The Social UI tests succeeded.")
}
function waitForNotification(topic, cb) {
function observer(subject, topic, data) {
Services.obs.removeObserver(observer, topic);
cb();
}
Services.obs.addObserver(observer, topic, false);
}
function setManifestPref(name, manifest) {
@ -216,6 +297,32 @@ function getManifestPrefname(aManifest) {
return "social.manifest." + originUri.hostPort.replace('.','-');
}
function setBuiltinManifestPref(name, manifest) {
// we set this as a default pref, it must not be a user pref
manifest.builtin = true;
let string = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
string.data = JSON.stringify(manifest);
Services.prefs.getDefaultBranch(null).setComplexValue(name, Ci.nsISupportsString, string);
// verify this is set on the default branch
let stored = Services.prefs.getComplexValue(name, Ci.nsISupportsString).data;
is(stored, string.data, "manifest '"+name+"' stored in default prefs");
// don't dirty our manifest, we'll need it without this flag later
delete manifest.builtin;
// verify we DO NOT have a user-level pref
ok(!Services.prefs.prefHasUserValue(name), "manifest '"+name+"' is not in user-prefs");
}
function resetBuiltinManifestPref(name) {
Services.prefs.getDefaultBranch(null).deleteBranch(name);
is(Services.prefs.getDefaultBranch(null).getPrefType(name),
Services.prefs.PREF_INVALID, "default manifest removed");
}
function ensureEventFired(elem, event) {
return BrowserTestUtils.waitForEvent(elem, event, true);
}
function ensureFrameLoaded(frame, uri) {
return new Promise(resolve => {
if (frame.contentDocument && frame.contentDocument.readyState == "complete" &&
@ -232,6 +339,243 @@ function ensureFrameLoaded(frame, uri) {
});
}
// chat test help functions
// And lots of helpers for the resize tests.
function get3ChatsForCollapsing(mode, cb) {
// We make one chat, then measure its size. We then resize the browser to
// ensure a second can be created fully visible but a third can not - then
// create the other 2. first will will be collapsed, second fully visible
// and the third also visible and the "selected" one.
let chatbar = getChatBar();
let chatWidth = undefined;
let num = 0;
is(chatbar.childNodes.length, 0, "chatbar starting empty");
is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
makeChat(mode, "first chat", function() {
// got the first one.
checkPopup();
ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
// we kinda cheat here and get the width of the first chat, assuming
// that all future chats will have the same width when open.
chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
let desired = chatWidth * 2.5;
resizeWindowToChatAreaWidth(desired, function(sizedOk) {
ok(sizedOk, "can't do any tests without this width");
checkPopup();
makeChat(mode, "second chat", function() {
is(chatbar.childNodes.length, 2, "now have 2 chats");
checkPopup();
// and create the third.
makeChat(mode, "third chat", function() {
is(chatbar.childNodes.length, 3, "now have 3 chats");
checkPopup();
// XXX - this is a hacky implementation detail around the order of
// the chats. Ideally things would be a little more sane wrt the
// other in which the children were created.
let second = chatbar.childNodes[2];
let first = chatbar.childNodes[1];
let third = chatbar.childNodes[0];
is(first.collapsed, true, "first collapsed state as promised");
is(second.collapsed, false, "second collapsed state as promised");
is(third.collapsed, false, "third collapsed state as promised");
is(chatbar.selectedChat, third, "third is selected as promised")
info("have 3 chats for collapse testing - starting actual test...");
cb(first, second, third);
}, mode);
}, mode);
});
}, mode);
}
function makeChat(mode, uniqueid, cb) {
info("making a chat window '" + uniqueid +"'");
let provider = SocialSidebar.provider;
let chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
// chatURL is not a part of the provider class, but is added by tests if we
// want to use a specific url (different than above) for testing
if (provider.chatURL) {
chatUrl = provider.chatURL;
}
// Note that we use promiseChatLoaded instead of the callback to ensure the
// content has started loading.
let chatbox = getChatBar().openChat({
origin: provider.origin,
title: provider.name,url: chatUrl + "?id=" + uniqueid,
mode: mode
});
chatbox.promiseChatLoaded.then(
() => {
info("chat window has opened");
chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
title: uniqueid
});
cb(chatbox);
});
}
function checkPopup() {
// popup only showing if any collapsed popup children.
let chatbar = getChatBar();
let numCollapsed = 0;
for (let chat of chatbar.childNodes) {
if (chat.collapsed) {
numCollapsed += 1;
// and it have a menuitem weakmap
is(chatbar.menuitemMap.get(chat).nodeName, "menuitem", "collapsed chat has a menu item");
} else {
ok(!chatbar.menuitemMap.has(chat), "open chat has no menu item");
}
}
is(chatbar.menupopup.parentNode.collapsed, numCollapsed == 0, "popup matches child collapsed state");
is(chatbar.menupopup.childNodes.length, numCollapsed, "popup has correct count of children");
// todo - check each individual elt is what we expect?
}
// Resize the main window so the chat area's boxObject is |desired| wide.
// Does a callback passing |true| if the window is now big enough or false
// if we couldn't resize large enough to satisfy the test requirement.
function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
let current = getChatBar().getBoundingClientRect().width;
let delta = desired - current;
info(count + ": resizing window so chat area is " + desired + " wide, currently it is "
+ current + ". Screen avail is " + window.screen.availWidth
+ ", current outer width is " + window.outerWidth);
// WTF? Sometimes we will get fractional values due to the - err - magic
// of DevPointsPerCSSPixel etc, so we allow a couple of pixels difference.
let widthDeltaCloseEnough = function(d) {
return Math.abs(d) < 2;
}
// attempting to resize by (0,0), unsurprisingly, doesn't cause a resize
// event - so just callback saying all is well.
if (widthDeltaCloseEnough(delta)) {
info(count + ": skipping this as screen width is close enough");
executeSoon(function() {
cb(true);
});
return;
}
// On lo-res screens we may already be maxed out but still smaller than the
// requested size, so asking to resize up also will not cause a resize event.
// So just callback now saying the test must be skipped.
if (window.screen.availWidth - window.outerWidth < delta) {
info(count + ": skipping this as screen available width is less than necessary");
executeSoon(function() {
cb(false);
});
return;
}
function resize_handler(event) {
// we did resize - but did we get far enough to be able to continue?
let newSize = getChatBar().getBoundingClientRect().width;
let sizedOk = widthDeltaCloseEnough(newSize - desired);
if (!sizedOk)
return;
window.removeEventListener("resize", resize_handler, true);
info(count + ": resized window width is " + newSize);
executeSoon(function() {
cb(sizedOk);
});
}
// Otherwise we request resize and expect a resize event
window.addEventListener("resize", resize_handler, true);
window.resizeBy(delta, 0);
}
function resizeAndCheckWidths(first, second, third, checks, cb) {
if (checks.length == 0) {
cb(); // nothing more to check!
return;
}
let count = checks.length;
let [width, numExpectedVisible, why] = checks.shift();
info("<< Check " + count + ": " + why);
info(count + ": " + "resizing window to " + width + ", expect " + numExpectedVisible + " visible items");
resizeWindowToChatAreaWidth(width, function(sizedOk) {
checkPopup();
ok(sizedOk, count+": window resized correctly");
function collapsedObserver(r, m) {
if ([first, second, third].filter(item => !item.collapsed).length == numExpectedVisible) {
if (m) {
m.disconnect();
}
ok(true, count + ": " + "correct number of chats visible");
info(">> Check " + count);
executeSoon(function() {
resizeAndCheckWidths(first, second, third, checks, cb);
});
}
}
let m = new MutationObserver(collapsedObserver);
m.observe(first, {attributes: true });
m.observe(second, {attributes: true });
m.observe(third, {attributes: true });
// and just in case we are already at the right size, explicitly call the
// observer.
collapsedObserver(undefined, m);
}, count);
}
function getChatBar() {
let cb = document.getElementById("pinnedchats");
cb.hidden = false;
return cb;
}
function getPopupWidth() {
let chatbar = getChatBar();
let popup = chatbar.menupopup;
ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
let cs = document.defaultView.getComputedStyle(popup.parentNode);
let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
return popup.parentNode.getBoundingClientRect().width + margins;
}
function promiseNodeRemoved(aNode) {
return new Promise(resolve => {
let parent = aNode.parentNode;
let observer = new MutationObserver(function onMutatations(mutations) {
for (let mutation of mutations) {
for (let i = 0; i < mutation.removedNodes.length; i++) {
let node = mutation.removedNodes.item(i);
if (node != aNode) {
continue;
}
observer.disconnect();
resolve();
}
}
});
observer.observe(parent, {childList: true});
});
}
function promiseCloseChat(chat) {
let promise = promiseNodeRemoved(chat);
chat.close();
return promise;
}
function closeAllChats() {
let chatbar = getChatBar();
while (chatbar.selectedChat) {
yield promiseCloseChat(chatbar.selectedChat);
}
}
function openChatViaUser() {
let sidebarDoc = document.getElementById("social-sidebar-browser").contentDocument;
let button = sidebarDoc.getElementById("chat-opener");
// Note we must use synthesizeMouseAtCenter() rather than calling
// .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
// to be true.
EventUtils.synthesizeMouseAtCenter(button, {}, sidebarDoc.defaultView);
}
// Support for going on and offline.
// (via browser/base/content/test/browser_bookmark_titles.js)
var origProxyType = Services.prefs.getIntPref('network.proxy.type');

Просмотреть файл

@ -13,7 +13,8 @@ var data = {
"icon64URL": "chrome://branding/content/icon64.png",
// at least one of these must be defined
"shareURL": "/browser/browser/base/content/test/social/social_share.html",
"sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html",
"statusURL": "/browser/browser/base/content/test/social/social_panel.html",
"postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
// should be available for display purposes

Просмотреть файл

@ -13,7 +13,7 @@ var data = {
"icon64URL": "chrome://branding/content/icon64.png",
// at least one of these must be defined
"shareURL": "/browser/browser/base/content/test/social/social_share.html",
"sidebarURL": "/browser/browser/base/content/test/social/social_sidebar_empty.html",
"postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
// should be available for display purposes

Просмотреть файл

@ -0,0 +1,15 @@
<html>
<head>
<meta charset="utf-8">
<title>test chat window</title>
</head>
<body>
<p>This is a test social chat window.</p>
<!-- a couple of input fields to help with focus testing -->
<input id="input1"/>
<input id="input2"/>
<!-- an iframe here so this one page generates multiple load events -->
<iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,25 @@
<html>
<head>
<meta charset="utf-8">
<script>
window.addEventListener("socialTest-MakeWider", function(e) {
document.body.setAttribute("style", "width: 500px; height: 500px; margin: 0; overflow: hidden;");
document.body.offsetWidth; // force a layout flush
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent("SocialTest-DoneMakeWider", true, true, {});
document.documentElement.dispatchEvent(evt);
}, false);
window.addEventListener("socialTest-CloseSelf", function(e) {
window.close();
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent("SocialTest-DoneCloseSelf", true, true, {});
document.documentElement.dispatchEvent(evt);
}, false);
</script>
</head>
<body style="width: 400px; height: 400px; margin: 0; overflow: hidden;">
<p>This is a test social flyout panel.</p>
<a id="traversal" href="https://test.example.com">test link</a>
</body>
</html>

Просмотреть файл

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<link id="siteicon" rel="icon" href="./icon.png"/>
<title>Demo Mark Window</title>
<script type="text/javascript">
function updateTextNode(parent, text) {
var textNode = parent.childNodes[0];
if (textNode)
parent.removeChild(textNode);
textNode = document.createTextNode(text);
parent.appendChild(textNode);
}
function onLoad() {
updateTextNode(document.getElementById("shared"), location.search);
socialMarkUpdate(true);
}
function socialMarkUpdate(isMarked) {
var evt = document.createEvent("CustomEvent");
evt.initCustomEvent("socialMarkUpdate", true, true, JSON.stringify({marked: isMarked}));
document.documentElement.dispatchEvent(evt);
}
var shareData;
addEventListener("OpenGraphData", function(e) {
shareData = JSON.parse(e.detail);
updateTextNode(document.getElementById("shared"), shareData.url);
socialMarkUpdate(true);
});
</script>
</head>
<body onload="onLoad()">
<div id="content">
<h3>This window shows the mark data</h3>
<div>Page Marked: <div id="shared" class="textbox"></div></div>
<button id="unmark" onclick="socialMarkUpdate(false); window.close()">Unmark</button>
<button onclick="window.close();">Close</button>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,8 @@
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>This is a test social panel.</p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,17 @@
<html>
<head>
<meta charset="utf-8">
<script>
addEventListener("test-flyout-open", function(e) {
navigator.mozSocial.openPanel("social_flyout.html");
}, false);
addEventListener("test-flyout-close", function(e) {
navigator.mozSocial.closePanel();
}, false);
</script>
</head>
<body>
<p>This is a test social sidebar.</p>
<button id="chat-opener" onclick="navigator.mozSocial.openChatWindow('./social_chat.html');"/>
</body>
</html>

Просмотреть файл

@ -0,0 +1,8 @@
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>This is a test social sidebar.</p>
</body>
</html>

Двоичные данные
browser/base/content/test/social/unchecked.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 779 B

Просмотреть файл

@ -101,6 +101,7 @@ browser.jar:
#endif
content/browser/browser-thumbnails.js (content/browser-thumbnails.js)
content/browser/browser-trackingprotection.js (content/browser-trackingprotection.js)
* content/browser/chatWindow.xul (content/chatWindow.xul)
content/browser/tab-content.js (content/tab-content.js)
content/browser/content.js (content/content.js)
content/browser/social-content.js (content/social-content.js)
@ -188,6 +189,8 @@ browser.jar:
#ifdef XP_WIN
content/browser/win6BrowserOverlay.xul (content/win6BrowserOverlay.xul)
#endif
content/browser/socialmarks.xml (content/socialmarks.xml)
content/browser/socialchat.xml (content/socialchat.xml)
# the following files are browser-specific overrides
* content/browser/license.html (/toolkit/content/license.html)
% override chrome://global/content/license.html chrome://browser/content/license.html

Просмотреть файл

@ -16,6 +16,7 @@ MOCHITEST_CHROME_MANIFESTS += [
BROWSER_CHROME_MANIFESTS += [
'content/test/alerts/browser.ini',
'content/test/chat/browser.ini',
'content/test/general/browser.ini',
'content/test/newtab/browser.ini',
'content/test/plugins/browser.ini',

Просмотреть файл

@ -549,8 +549,14 @@ const CustomizableWidgets = [
let win = doc.defaultView;
let menu = doc.getElementById("viewSidebarMenu");
// First clear any existing menuitems then populate. Add it to the
// First clear any existing menuitems then populate. Social sidebar
// options may not have been added yet, so we do that here. Add it to the
// standard menu first, then copy all sidebar options to the panel.
win.SocialSidebar.clearProviderMenus();
let providerMenuSeps = menu.getElementsByClassName("social-provider-menu");
if (providerMenuSeps.length > 0)
win.SocialSidebar.populateProviderMenu(providerMenuSeps[0]);
let sidebarItems = doc.getElementById("PanelUI-sidebarItems");
clearSubview(sidebarItems);
fillSubviewFromMenuItems([...menu.children], sidebarItems);
@ -567,7 +573,7 @@ const CustomizableWidgets = [
node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
node.setAttribute("removable", "true");
node.setAttribute("observes", "Social:PageShareable");
node.setAttribute("observes", "Social:PageShareOrMark");
node.setAttribute("command", "Social:SharePage");
let listener = {

2
browser/extensions/pocket/bootstrap.js поставляемый
Просмотреть файл

@ -15,7 +15,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
"resource:///modules/SocialService.jsm");
"resource://gre/modules/SocialService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",

Просмотреть файл

@ -161,6 +161,7 @@ These should match what Safari and other Apple applications use on OS X Lion. --
<!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
<!ENTITY editThisBookmarkCmd.label "Edit This Bookmark">
<!ENTITY bookmarkThisPageCmd.commandkey "d">
<!ENTITY markPageCmd.commandkey "l">
<!-- LOCALIZATION NOTE (findShareServices.label):
- Use the unicode ellipsis char, \u2026,
- or use "..." if \u2026 doesn't suit traditions in your locale. -->
@ -773,8 +774,22 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY syncReAuthItem.accesskey "R">
<!ENTITY syncToolbarButton.label "Sync">
<!ENTITY socialToolbar.title "Social Toolbar Button">
<!ENTITY social.ok.label "OK">
<!ENTITY social.ok.accesskey "O">
<!ENTITY social.toggleSidebar.label "Show sidebar">
<!ENTITY social.toggleSidebar.accesskey "s">
<!ENTITY social.addons.label "Manage Services…">
<!ENTITY social.toggleNotifications.label "Show desktop notifications">
<!ENTITY social.toggleNotifications.accesskey "n">
<!ENTITY social.learnMore.label "Learn more…">
<!ENTITY social.learnMore.accesskey "l">
<!ENTITY social.directory.label "Activations Directory">
<!ENTITY social.directory.text "You can activate Share services from the directory.">
<!ENTITY social.directory.button "Take me there!">
@ -795,6 +810,15 @@ you can use these alternative items. Otherwise, their values should be empty. -
<!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">
<!ENTITY customizeMode.lwthemes.menuGetMore.accessKey "G">
<!ENTITY social.chatBar.commandkey "c">
<!ENTITY social.chatBar.label "Focus chats">
<!ENTITY social.chatBar.accesskey "c">
<!ENTITY social.markpageMenu.accesskey "P">
<!ENTITY social.markpageMenu.label "Save Page To…">
<!ENTITY social.marklinkMenu.accesskey "L">
<!ENTITY social.marklinkMenu.label "Save Link To…">
<!ENTITY getUserMedia.selectCamera.label "Camera to share:">
<!ENTITY getUserMedia.selectCamera.accesskey "C">
<!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">

323
browser/modules/Chat.jsm Normal file
Просмотреть файл

@ -0,0 +1,323 @@
/* 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";
// A module for working with chat windows.
this.EXPORTED_SYMBOLS = ["Chat", "kDefaultButtonSet"];
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const kDefaultButtonSet = new Set(["minimize", "swap", "close"]);
const kHiddenDefaultButtons = new Set(["minimize", "close"]);
var gCustomButtons = new Map();
// A couple of internal helper function.
function isWindowChromeless(win) {
// XXX - stolen from browser-social.js, but there's no obvious place to
// put this so it can be shared.
// Is this a popup window that doesn't want chrome shown?
let docElem = win.document.documentElement;
// extrachrome is not restored during session restore, so we need
// to check for the toolbar as well.
let chromeless = docElem.getAttribute("chromehidden").includes("extrachrome") ||
docElem.getAttribute('chromehidden').includes("toolbar");
return chromeless;
}
function isWindowGoodForChats(win) {
return !win.closed &&
!!win.document.getElementById("pinnedchats") &&
!isWindowChromeless(win) &&
!PrivateBrowsingUtils.isWindowPrivate(win);
}
function getChromeWindow(contentWin) {
return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
}
/*
* The exported Chat object
*/
var Chat = {
/**
* Iterator of <chatbox> elements from this module in all windows.
*/
get chatboxes() {
return function*() {
let winEnum = Services.wm.getEnumerator("navigator:browser");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
let chatbar = win.document.getElementById("pinnedchats");
if (!chatbar)
continue;
// Make a new array instead of the live NodeList so this iterator can be
// used for closing/deleting.
let chatboxes = [...chatbar.children];
for (let chatbox of chatboxes) {
yield chatbox;
}
}
// include standalone chat windows
winEnum = Services.wm.getEnumerator("Social:Chat");
while (winEnum.hasMoreElements()) {
let win = winEnum.getNext();
if (win.closed)
continue;
yield win.document.getElementById("chatter");
}
}();
},
/**
* Open a new chatbox.
*
* @param contentWindow [optional]
* The content window that requested this chat. May be null.
* @param options
* Object that may contain the following properties:
* - origin
* The origin for the chat. This is primarily used as an identifier
* to help identify all chats from the same provider.
* - title
* The title to be used if a new chat window is created.
* - url
* The URL for the that. Should be under the origin. If an existing
* chatbox exists with the same URL, it will be reused and returned.
* - mode [optional]
* May be undefined or 'minimized'
* - focus [optional]
* Indicates if the chatbox should be focused. If undefined the chat
* will be focused if the window is currently handling user input (ie,
* if the chat is being opened as a direct result of user input)
* - remote [optional]
* Indicates if the chatbox browser should use the remote bindings
* to run in the content process when TRUE.
* @param callback
* Function to be invoked once the chat constructed. The chatbox binding
* is passed as the first argument.
*
* @return A chatbox binding. This binding has a number of promises which
* can be used to determine when the chatbox is being created and
* has loaded. Will return null if no chat can be created (Which
* should only happen in edge-cases)
*/
open: function(contentWindow, options, callback) {
let chromeWindow = this.findChromeWindowForChats(contentWindow);
if (!chromeWindow) {
Cu.reportError("Failed to open a chat window - no host window could be found.");
return null;
}
let chatbar = chromeWindow.document.getElementById("pinnedchats");
chatbar.hidden = false;
if (options.remote) {
// Double check that current window can handle remote browser elements.
let browser = chromeWindow.gBrowser && chromeWindow.gBrowser.selectedBrowser;
if (!browser || browser.getAttribute("remote") != "true") {
options.remote = false;
}
}
let chatbox = chatbar.openChat(options, callback);
// getAttention is ignored if the target window is already foreground, so
// we can call it unconditionally.
chromeWindow.getAttention();
// If focus is undefined we want automatic focus handling, and only focus
// if a direct result of user action.
if (!("focus" in options)) {
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
options.focus = dwu.isHandlingUserInput;
}
if (options.focus) {
chatbar.focus();
}
return chatbox;
},
/**
* Close all chats from the specified origin.
*
* @param origin
* The origin from which all chats should be closed.
*/
closeAll: function(origin) {
for (let chatbox of this.chatboxes) {
if (chatbox.content.getAttribute("origin") != origin) {
continue;
}
chatbox.close();
}
},
/**
* Focus the chatbar associated with a window
*
* @param window
*/
focus: function(win) {
let chatbar = win.document.getElementById("pinnedchats");
if (chatbar && !chatbar.hidden) {
chatbar.focus();
}
},
// This is exported as socialchat.xml needs to find a window when a chat
// is re-docked.
findChromeWindowForChats: function(preferredWindow) {
if (preferredWindow) {
preferredWindow = getChromeWindow(preferredWindow);
if (isWindowGoodForChats(preferredWindow)) {
return preferredWindow;
}
}
// no good - we just use the "most recent" browser window which can host
// chats (we used to try and "group" all chats in the same browser window,
// but that didn't work out so well - see bug 835111
// Try first the most recent window as getMostRecentWindow works
// even on platforms where getZOrderDOMWindowEnumerator is broken
// (ie. Linux). This will handle most cases, but won't work if the
// foreground window is a popup.
let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
if (isWindowGoodForChats(mostRecent))
return mostRecent;
let topMost, enumerator;
// *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
// Windows. We use BROKEN_WM_Z_ORDER as that is what some other code uses
// and a few bugs recommend searching mxr for this symbol to identify the
// workarounds - we want this code to be hit in such searches.
let os = Services.appinfo.OS;
const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
if (BROKEN_WM_Z_ORDER) {
// this is oldest to newest and no way to change the order.
enumerator = Services.wm.getEnumerator("navigator:browser");
} else {
// here we explicitly ask for bottom-to-top so we can use the same logic
// where BROKEN_WM_Z_ORDER is true.
enumerator = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", false);
}
while (enumerator.hasMoreElements()) {
let win = enumerator.getNext();
if (!win.closed && isWindowGoodForChats(win))
topMost = win;
}
return topMost;
},
/**
* Adds a button to the collection of custom buttons that can be added to the
* titlebar of a chatbox.
* For the button to be visible, `Chat#loadButtonSet` has to be called with
* the new buttons' ID in the buttonSet argument.
*
* @param {Object} button Button object that may contain the following fields:
* - {String} id Button identifier.
* - {Function} [onBuild] Function that returns a valid DOM node to
* represent the button.
* - {Function} [onCommand] Callback function that is invoked when the DOM
* node is clicked.
*/
registerButton: function(button) {
if (gCustomButtons.has(button.id))
return;
gCustomButtons.set(button.id, button);
},
/**
* Load a set of predefined buttons in a chatbox' titlebar.
*
* @param {XULDOMNode} chatbox Chatbox XUL element.
* @param {Set|String} buttonSet Set of buttons to show in the titlebar. This
* may be a comma-separated string or a predefined
* set object.
*/
loadButtonSet: function(chatbox, buttonSet = kDefaultButtonSet) {
if (!buttonSet)
return;
// When the buttonSet is coming from an XML attribute, it will be a string.
if (typeof buttonSet == "string") {
buttonSet = buttonSet.split(",").map(button => button.trim());
}
// Make sure to keep the current set around.
chatbox.setAttribute("buttonSet", [...buttonSet].join(","));
let isUndocked = !chatbox.chatbar;
let document = chatbox.ownerDocument;
let titlebarNode = document.getAnonymousElementByAttribute(chatbox, "class",
"chat-titlebar");
let buttonsSeen = new Set();
for (let buttonId of buttonSet) {
buttonId = buttonId.trim();
buttonsSeen.add(buttonId);
let nodes, node;
if (kDefaultButtonSet.has(buttonId)) {
node = document.getAnonymousElementByAttribute(chatbox, "anonid", buttonId);
if (!node)
continue;
node.hidden = isUndocked && kHiddenDefaultButtons.has(buttonId) ? true : false;
} else if (gCustomButtons.has(buttonId)) {
let button = gCustomButtons.get(buttonId);
let buttonClass = "chat-" + buttonId;
// Custom buttons are not defined in the chatbox binding, thus not
// anonymous elements.
nodes = titlebarNode.getElementsByClassName(buttonClass);
node = nodes && nodes.length ? nodes[0] : null;
if (!node) {
// Allow custom buttons to build their own button node.
if (button.onBuild) {
node = button.onBuild(chatbox);
} else {
// We can also build a normal toolbarbutton to insert.
node = document.createElementNS(kNSXUL, "toolbarbutton");
node.classList.add(buttonClass);
node.classList.add("chat-toolbarbutton");
}
if (button.onCommand) {
node.addEventListener("command", e => {
button.onCommand(e, chatbox);
});
}
titlebarNode.appendChild(node);
}
// When the chat is undocked and the button wants to be visible then, it
// will be.
node.hidden = isUndocked && !button.visibleWhenUndocked;
} else {
Cu.reportError("Chatbox button '" + buttonId + "' could not be found!\n");
}
}
// Hide any button that is part of the default set, but not of the current set.
for (let button of kDefaultButtonSet) {
if (!buttonsSeen.has(button))
document.getAnonymousElementByAttribute(chatbox, "anonid", button).hidden = true;
}
}
};

Просмотреть файл

@ -4,7 +4,8 @@
"use strict";
this.EXPORTED_SYMBOLS = ["Social", "OpenGraphBuilder",
this.EXPORTED_SYMBOLS = ["Social", "CreateSocialStatusWidget",
"CreateSocialMarkWidget", "OpenGraphBuilder",
"DynamicResizeWatcher", "sizeSocialPanelToContent"];
const Ci = Components.interfaces;
@ -22,7 +23,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
"resource:///modules/SocialService.jsm");
"resource://gre/modules/SocialService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
"resource://gre/modules/PageMetadata.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@ -31,6 +32,46 @@ XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
function promiseSetAnnotation(aURI, providerList) {
let deferred = Promise.defer();
// Delaying to catch issues with asynchronous behavior while waiting
// to implement asynchronous annotations in bug 699844.
Services.tm.mainThread.dispatch(function() {
try {
if (providerList && providerList.length > 0) {
PlacesUtils.annotations.setPageAnnotation(
aURI, "social/mark", JSON.stringify(providerList), 0,
PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
} else {
PlacesUtils.annotations.removePageAnnotation(aURI, "social/mark");
}
} catch (e) {
Cu.reportError("SocialAnnotation failed: " + e);
}
deferred.resolve();
}, Ci.nsIThread.DISPATCH_NORMAL);
return deferred.promise;
}
function promiseGetAnnotation(aURI) {
let deferred = Promise.defer();
// Delaying to catch issues with asynchronous behavior while waiting
// to implement asynchronous annotations in bug 699844.
Services.tm.mainThread.dispatch(function() {
let val = null;
try {
val = PlacesUtils.annotations.getPageAnnotation(aURI, "social/mark");
} catch (ex) { }
deferred.resolve(val);
}, Ci.nsIThread.DISPATCH_NORMAL);
return deferred.promise;
}
this.Social = {
initialized: false,
lastEventReceived: 0,
@ -108,6 +149,11 @@ this.Social = {
return !this._disabledForSafeMode && this.providers.length > 0;
},
toggleNotifications: function SocialNotifications_toggle() {
let prefValue = Services.prefs.getBoolPref("social.toast-notifications.enabled");
Services.prefs.setBoolPref("social.toast-notifications.enabled", !prefValue);
},
_getProviderFromOrigin: function (origin) {
for (let p of this.providers) {
if (p.origin == origin) {
@ -134,9 +180,139 @@ this.Social = {
// It's OK if the provider has already been activated - we still get called
// back with it.
SocialService.enableProvider(origin, callback);
},
// Page Marking functionality
isURIMarked: function(origin, aURI, aCallback) {
promiseGetAnnotation(aURI).then(function(val) {
if (val) {
let providerList = JSON.parse(val);
val = providerList.indexOf(origin) >= 0;
}
aCallback(!!val);
}).then(null, Cu.reportError);
},
markURI: function(origin, aURI, aCallback) {
// update or set our annotation
promiseGetAnnotation(aURI).then(function(val) {
let providerList = val ? JSON.parse(val) : [];
let marked = providerList.indexOf(origin) >= 0;
if (marked)
return;
providerList.push(origin);
// we allow marking links in a page that may not have been visited yet.
// make sure there is a history entry for the uri, then annotate it.
let place = {
uri: aURI,
visits: [{
visitDate: Date.now() + 1000,
transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
}]
};
PlacesUtils.asyncHistory.updatePlaces(place, {
handleError: () => Cu.reportError("couldn't update history for socialmark annotation"),
handleResult: function () {},
handleCompletion: function () {
promiseSetAnnotation(aURI, providerList).then(function() {
if (aCallback)
schedule(function() { aCallback(true); } );
}).then(null, Cu.reportError);
}
});
}).then(null, Cu.reportError);
},
unmarkURI: function(origin, aURI, aCallback) {
// this should not be called if this.provider or the port is null
// set our annotation
promiseGetAnnotation(aURI).then(function(val) {
let providerList = val ? JSON.parse(val) : [];
let marked = providerList.indexOf(origin) >= 0;
if (marked) {
// remove the annotation
providerList.splice(providerList.indexOf(origin), 1);
promiseSetAnnotation(aURI, providerList).then(function() {
if (aCallback)
schedule(function() { aCallback(false); } );
}).then(null, Cu.reportError);
}
}).then(null, Cu.reportError);
}
};
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
function CreateSocialStatusWidget(aId, aProvider) {
if (!aProvider.statusURL)
return;
let widget = CustomizableUI.getWidget(aId);
// The widget is only null if we've created then destroyed the widget.
// Once we've actually called createWidget the provider will be set to
// PROVIDER_API.
if (widget && widget.provider == CustomizableUI.PROVIDER_API)
return;
CustomizableUI.createWidget({
id: aId,
type: "custom",
removable: true,
defaultArea: CustomizableUI.AREA_NAVBAR,
onBuild: function(aDocument) {
let node = aDocument.createElement("toolbarbutton");
node.id = this.id;
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional social-status-button badged-button");
node.style.listStyleImage = "url(" + (aProvider.icon32URL || aProvider.iconURL) + ")";
node.setAttribute("origin", aProvider.origin);
node.setAttribute("label", aProvider.name);
node.setAttribute("tooltiptext", aProvider.name);
node.setAttribute("oncommand", "SocialStatus.showPopup(this);");
node.setAttribute("constrain-size", "true");
return node;
}
});
}
function CreateSocialMarkWidget(aId, aProvider) {
if (!aProvider.markURL)
return;
let widget = CustomizableUI.getWidget(aId);
// The widget is only null if we've created then destroyed the widget.
// Once we've actually called createWidget the provider will be set to
// PROVIDER_API.
if (widget && widget.provider == CustomizableUI.PROVIDER_API)
return;
CustomizableUI.createWidget({
id: aId,
type: "custom",
removable: true,
defaultArea: CustomizableUI.AREA_NAVBAR,
onBuild: function(aDocument) {
let node = aDocument.createElement("toolbarbutton");
node.id = this.id;
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional social-mark-button");
node.setAttribute("type", "socialmark");
node.setAttribute("constrain-size", "true");
node.style.listStyleImage = "url(" + (aProvider.unmarkedIcon || aProvider.icon32URL || aProvider.iconURL) + ")";
node.setAttribute("origin", aProvider.origin);
let window = aDocument.defaultView;
let menuLabel = window.gNavigatorBundle.getFormattedString("social.markpageMenu.label", [aProvider.name]);
node.setAttribute("label", menuLabel);
node.setAttribute("tooltiptext", menuLabel);
node.setAttribute("observes", "Social:PageShareOrMark");
return node;
}
});
}
function sizeSocialPanelToContent(panel, iframe, requestedSize) {
let doc = iframe.contentDocument;
if (!doc || !doc.body) {

Просмотреть файл

@ -17,6 +17,7 @@ EXTRA_JS_MODULES += [
'BrowserUsageTelemetry.jsm',
'CaptivePortalWatcher.jsm',
'CastingApps.jsm',
'Chat.jsm',
'ContentClick.jsm',
'ContentCrashHandlers.jsm',
'ContentLinkHandler.jsm',
@ -42,7 +43,6 @@ EXTRA_JS_MODULES += [
'SelfSupportBackend.jsm',
'SitePermissions.jsm',
'Social.jsm',
'SocialService.jsm',
'TabGroupsMigrator.jsm',
'TransientPrefs.jsm',
'URLBarZoom.jsm',

Просмотреть файл

@ -116,7 +116,7 @@ function do_initialize_social(enabledOnStartup, cb) {
}
// import and initialize everything
SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
do_check_eq(enabledOnStartup, SocialService.hasEnabledProviders, "Service has enabled providers");
Social = Cu.import("resource:///modules/Social.jsm", {}).Social;
do_check_false(Social.initialized, "Social is not initialized");
@ -125,86 +125,3 @@ function do_initialize_social(enabledOnStartup, cb) {
if (!enabledOnStartup)
do_execute_soon(cb);
}
function AsyncRunner() {
do_test_pending();
do_register_cleanup(() => this.destroy());
this._callbacks = {
done: do_test_finished,
error: function (err) {
// xpcshell test functions like do_check_eq throw NS_ERROR_ABORT on
// failure. Ignore those so they aren't rethrown here.
if (err !== Cr.NS_ERROR_ABORT) {
if (err.stack) {
err = err + " - See following stack:\n" + err.stack +
"\nUseless do_throw stack";
}
do_throw(err);
}
},
consoleError: function (scriptErr) {
// Try to ensure the error is related to the test.
let filename = scriptErr.sourceName || scriptErr.toString() || "";
if (filename.indexOf("/toolkit/components/social/") >= 0)
do_throw(scriptErr);
},
};
this._iteratorQueue = [];
// This catches errors reported to the console, e.g., via Cu.reportError, but
// not on the runner's stack.
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).
registerListener(this);
}
AsyncRunner.prototype = {
appendIterator: function appendIterator(iter) {
this._iteratorQueue.push(iter);
},
next: function next(arg) {
if (!this._iteratorQueue.length) {
this.destroy();
this._callbacks.done();
return;
}
try {
var { done, value: val } = this._iteratorQueue[0].next(arg);
if (done) {
this._iteratorQueue.shift();
this.next();
return;
}
}
catch (err) {
this._callbacks.error(err);
}
// val is an iterator => prepend it to the queue and start on it
// val is otherwise truthy => call next
if (val) {
if (typeof(val) != "boolean")
this._iteratorQueue.unshift(val);
this.next();
}
},
destroy: function destroy() {
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).
unregisterListener(this);
this.destroy = function alreadyDestroyed() {};
},
observe: function observe(msg) {
if (msg instanceof Ci.nsIScriptError &&
!(msg.flags & Ci.nsIScriptError.warningFlag))
{
this._callbacks.consoleError(msg);
}
},
};

Просмотреть файл

@ -19,7 +19,7 @@ function testStartupEnabled() {
}
function testDisableAfterStartup() {
let SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService;
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
SocialService.disableProvider(Social.providers[0].origin, function() {
do_wait_observer("social:providers-changed", function() {
do_check_eq(Social.enabled, false, "Social is disabled");

Просмотреть файл

@ -7,7 +7,3 @@ support-files = blocklist.xml
[test_social.js]
[test_socialDisabledStartup.js]
[test_SocialService.js]
[test_SocialServiceMigration21.js]
[test_SocialServiceMigration22.js]
[test_SocialServiceMigration29.js]

Просмотреть файл

@ -1409,7 +1409,6 @@ html|span.ac-emphasize-text-url {
}
/* social share panel */
%include ../shared/social/social.inc.css
.social-share-frame {
border-top: 1px solid #f8f8f8;
@ -1458,6 +1457,12 @@ html|span.ac-emphasize-text-url {
max-height: 16px;
}
/* social recommending panel */
#social-mark-button {
-moz-image-region: rect(0, 16px, 16px, 0);
}
/* bookmarks menu-button */
#bookmarks-menu-button[cui-areatype="toolbar"] > .toolbarbutton-menubutton-dropmarker {
@ -1852,6 +1857,30 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton:not(:h
margin-inline-end: 2px;
}
/* social toolbar provider menu */
#social-statusarea-popup {
margin-top: 0;
margin-left: -12px;
margin-right: -12px;
}
.social-statusarea-user {
list-style-image:url("chrome://global/skin/icons/information-32.png");
}
.social-statusarea-user-portrait {
width: 32px;
height: 32px;
border-radius: 2px;
margin: 10px;
}
.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
%include ../shared/social/chat.inc.css
/* Customization mode */
%include ../shared/customizableui/customizeMode.inc.css

Просмотреть файл

@ -2029,6 +2029,7 @@ html|span.ac-emphasize-text-url {
#share-container {
min-width: 756px;
background-color: white;
background-repeat: no-repeat;
background-position: center center;
}
@ -2043,7 +2044,9 @@ html|span.ac-emphasize-text-url {
opacity: 0;
}
#manage-share-providers {
#manage-share-providers,
#social-sidebar-button:hover,
#social-sidebar-button:hover:active {
-moz-image-region: rect(18px, 468px, 36px, 450px);
}
@ -2072,6 +2075,12 @@ html|span.ac-emphasize-text-url {
max-height: 16px;
}
/* social recommending panel */
#social-mark-button {
-moz-image-region: rect(0, 16px, 16px, 0);
}
/* BOOKMARKING PANEL */
#editBookmarkPanelStarIcon {
list-style-image: url("chrome://browser/skin/places/starred48.png");
@ -2992,6 +3001,10 @@ menuitem:hover > hbox > .alltabs-endimage[soundplaying] {
0 0 3px 2px -moz-mac-focusring;
}
#social-notification-icon > .toolbarbutton-icon {
height: 16px;
}
/* Translation */
%include ../shared/translation/infobar.inc.css
@ -3247,8 +3260,29 @@ menulist.translate-infobar-element > .menulist-dropmarker {
border-radius: 1px;
}
/* Share */
%include ../shared/social/social.inc.css
/* === end of social toolbar button === */
/* === social toolbar provider menu === */
.social-statusarea-user {
list-style-image:url("chrome://global/skin/icons/information-32.png");
}
.social-statusarea-user-portrait {
width: 32px;
height: 32px;
margin: 4px;
margin-inline-start: 0;
}
.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
/* fixup rounded corners for osx panels */
.social-panel > .social-panel-frame {
border-radius: inherit;
}
#social-share-panel {
min-height: 100px;
@ -3274,6 +3308,10 @@ menulist.translate-infobar-element > .menulist-dropmarker {
border-top-right-radius: inherit;
}
/* === end of social toolbar provider menu === */
%include ../shared/social/chat.inc.css
/* Customization mode */
%include ../shared/customizableui/customizeMode.inc.css

Просмотреть файл

@ -100,6 +100,7 @@
skin/classic/browser/search-indicator-magnifying-glass.svg (../shared/search/search-indicator-magnifying-glass.svg)
skin/classic/browser/search-arrow-go.svg (../shared/search/search-arrow-go.svg)
skin/classic/browser/gear.svg (../shared/search/gear.svg)
skin/classic/browser/social/chat-icons.svg (../shared/social/chat-icons.svg)
skin/classic/browser/social/gear_default.png (../shared/social/gear_default.png)
skin/classic/browser/social/gear_clicked.png (../shared/social/gear_clicked.png)
skin/classic/browser/tabbrowser/connecting.png (../shared/tabbrowser/connecting.png)

Просмотреть файл

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-3 -3 16 16">
<style>
use:not(:target) {
display: none;
}
use {
fill: #666;
}
use[id$="-hover"] {
fill: #4a4a4a;
}
use[id$="-active"] {
fill: #4a4a4a;
}
use[id$="-disabled"] {
fill: #666;
}
use[id$="-white"] {
fill: #fff;
}
</style>
<defs>
<polygon id="close-shape" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668 3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
<path id="dropdown-shape" fill-rule="evenodd" d="M9,3L4.984,7L1,3H9z"/>
<g id="expand-shape">
<path fill-rule="evenodd" d="M9.429,7.072v2.143c0,0.531-0.188,0.985-0.566,1.363c-0.377,0.377-0.832,0.565-1.363,0.565H1.929 c-0.531,0-0.986-0.188-1.363-0.565C0.188,10.2,0,9.746,0,9.214V3.643c0-0.531,0.188-0.985,0.566-1.362 c0.377-0.378,0.832-0.566,1.363-0.566h4.714c0.062,0,0.114,0.021,0.154,0.061s0.06,0.092,0.06,0.154v0.428 c0,0.063-0.02,0.114-0.06,0.154S6.705,2.572,6.643,2.572H1.929c-0.295,0-0.547,0.104-0.757,0.314S0.857,3.348,0.857,3.643v5.571 c0,0.295,0.105,0.547,0.315,0.757s0.462,0.314,0.757,0.314H7.5c0.294,0,0.547-0.104,0.757-0.314 c0.209-0.21,0.314-0.462,0.314-0.757V7.072c0-0.062,0.02-0.114,0.061-0.154c0.04-0.04,0.091-0.061,0.154-0.061h0.428 c0.062,0,0.114,0.021,0.154,0.061S9.429,7.009,9.429,7.072z"/>
<path fill-rule="evenodd" d="M7.07,5.82L6.179,4.93C6.127,4.878,6.101,4.818,6.101,4.75s0.026-0.128,0.079-0.18l2.594-2.594L7.648,0.852 C7.549,0.753,7.5,0.636,7.5,0.5s0.049-0.252,0.148-0.351S7.864,0,8,0h3.5c0.136,0,0.252,0.05,0.351,0.149S12,0.365,12,0.5V4 c0,0.136-0.05,0.253-0.149,0.351C11.752,4.451,11.635,4.5,11.5,4.5c-0.136,0-0.253-0.05-0.352-0.149l-1.124-1.125L7.429,5.82 c-0.052,0.052-0.112,0.079-0.18,0.079"/>
</g>
<rect id="minimize-shape" y="7.5" width="10" height="2.2"/>
<path id="exit-shape" fill-rule="evenodd" d="M5.01905144,3.00017279 C5.01277908,3.00005776 5.0064926,3 5.00019251,3 L1.99980749,3 C1.44371665,3 1,3.44762906 1,3.99980749 L1,7.00019251 C1,7.55628335 1.44762906,8 1.99980749,8 L5.00019251,8 C5.00649341,8 5.01277988,7.99994253 5.01905144,7.99982809 L5.01905144,8.5391818 C5.01905144,10.078915 5.37554713,10.2645548 5.81530684,9.9314625 L10.8239665,6.13769619 C11.2653143,5.80340108 11.2637262,5.26455476 10.8239665,4.93146254 L5.81530684,1.13769619 C5.37395904,0.80340108 5.01905144,0.98023404 5.01905144,1.52997693 L5.01905144,3.00017279 Z M-1,1 L4,1 L4,2 L0,2 L0,9 L4,9 L4,10.0100024 L-1,10.0100021 L-1,1 Z" />
</defs>
<use id="close" xlink:href="#close-shape"/>
<use id="close-active" xlink:href="#close-shape"/>
<use id="close-disabled" xlink:href="#close-shape"/>
<use id="close-hover" xlink:href="#close-shape"/>
<use id="exit-white" xlink:href="#exit-shape"/>
<use id="expand" xlink:href="#expand-shape"/>
<use id="expand-active" xlink:href="#expand-shape"/>
<use id="expand-disabled" xlink:href="#expand-shape"/>
<use id="expand-hover" xlink:href="#expand-shape"/>
<use id="expand-white" xlink:href="#expand-shape"/>
<use id="minimize" xlink:href="#minimize-shape"/>
<use id="minimize-active" xlink:href="#minimize-shape"/>
<use id="minimize-disabled" xlink:href="#minimize-shape"/>
<use id="minimize-hover" xlink:href="#minimize-shape"/>
<use id="minimize-white" xlink:href="#minimize-shape"/>
</svg>

После

Ширина:  |  Высота:  |  Размер: 3.6 KiB

Просмотреть файл

@ -0,0 +1,238 @@
%if 0
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
#social-sidebar-header {
padding: 3px;
}
#manage-share-providers,
#social-sidebar-button {
list-style-image: url("chrome://browser/skin/Toolbar.png");
-moz-image-region: rect(0, 468px, 18px, 450px);
}
#social-sidebar-button {
-moz-appearance: none;
border: none;
padding: 0;
margin: 2px;
}
#manage-share-providers > .toolbarbutton-icon,
#social-sidebar-button > .toolbarbutton-icon {
min-height: 18px;
min-width: 18px;
}
#social-sidebar-button > .toolbarbutton-menu-dropmarker {
display: none;
}
#social-sidebar-button[loading="true"] {
list-style-image: url("chrome://global/skin/icons/loading.png");
}
#social-sidebar-favico {
max-height: 16px;
max-width: 16px;
padding: 0;
margin: 2px;
}
.chat-status-icon {
max-height: 16px;
max-width: 16px;
padding: 0;
}
.chat-toolbarbutton {
-moz-appearance: none;
border: none;
padding: 0 3px;
margin: 0;
background: none;
}
.chat-toolbarbutton > .toolbarbutton-text {
display: none;
}
.chat-toolbarbutton > .toolbarbutton-icon {
width: 16px;
height: 16px;
}
.chat-close-button {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#close");
}
.chat-close-button:hover {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#close-hover");
}
.chat-close-button:hover:active {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#close-active");
}
.chat-minimize-button {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize");
}
.chat-minimize-button:hover {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize-hover");
}
.chat-minimize-button:hover:active {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize-active");
}
.chat-swap-button {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand");
transform: rotate(180deg);
}
.chat-swap-button:hover {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand-hover");
}
.chat-swap-button:hover:active {
list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand-active");
}
chatbar > chatbox > .chat-titlebar > .chat-swap-button {
transform: none;
}
.chat-title {
color: #666;
text-shadow: none;
cursor: inherit;
}
.chat-titlebar {
height: 26px;
min-height: 26px;
width: 100%;
margin: 0;
padding: 5px 4px;
border: 1px solid #ebebeb;
border-bottom: 0;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
cursor: pointer;
background-color: #ebebeb;
}
.chat-titlebar[selected] {
background-color: #f0f0f0;
}
.chat-titlebar > .notification-anchor-icon {
margin-left: 2px;
margin-right: 2px;
}
.chat-titlebar[minimized="true"] {
border-bottom: none;
}
.chat-titlebar[activity] {
background-image: radial-gradient(ellipse closest-side at center, rgb(255,255,255), transparent);
background-repeat: no-repeat;
background-size: 100% 20px;
background-position: 0 -10px;
}
.chat-frame {
padding: 0;
margin: 0;
overflow: hidden;
}
.chatbar-button {
list-style-image: url("chrome://browser/skin/social/services-16.png");
margin: 0;
padding: 2px;
height: 21px;
width: 21px;
border: 1px solid #ccc;
border-bottom: none;
background-color: #d9d9d9;
background-image: linear-gradient(rgba(255,255,255,.43), transparent);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
@media (min-resolution: 2dppx) {
.chatbar-button {
list-style-image: url("chrome://browser/skin/social/services-16@2x.png");
}
}
.chatbar-button:hover,
.chatbar-button[open="true"] {
background-color: #f0f0f0;
}
.chatbar-button[activity]:not([open]) {
background-image: radial-gradient(circle farthest-corner at center 2px, rgb(254,254,255) 3%, rgba(210,235,255,0.9) 12%, rgba(148,205,253,0.6) 30%, rgba(148,205,253,0.2) 70%);
}
.chatbar-button > .toolbarbutton-icon {
width: 16px;
}
.chatbar-button > menupopup > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
width: auto;
height: auto;
max-height: 16px;
max-width: 16px;
}
.chatbar-button[open="true"] {
box-shadow: inset 0 2px 5px rgba(0,0,0,0.6), 0 1px rgba(255,255,255,0.2);
}
.chatbar-button > .toolbarbutton-text,
.chatbar-button > .toolbarbutton-menu-dropmarker {
display: none;
}
.chatbar-button > menupopup > menuitem[activity] {
font-weight: bold;
}
.chatbar-innerbox {
background: transparent;
overflow: hidden;
}
chatbar {
margin-inline-end: 20px;
}
chatbox {
margin-inline-start: 4px;
background-color: transparent;
}
chatbar > chatbox {
/* Apply the same border-radius as the .chat-titlebar to make the box-shadow
go round nicely. */
border-top-left-radius: 4px;
border-top-right-radius: 4px;
box-shadow: 0 0 5px rgba(0,0,0,.3);
/* Offset the chatbox the same amount as the box-shadows' spread, to make it
visible. */
margin-inline-end: 5px;
}
window > chatbox {
margin-inline-start: 0px;
margin: 0px;
border: none;
padding: 0px;
border-radius: 4px;
}

Просмотреть файл

@ -1,23 +0,0 @@
%if 0
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
#manage-share-providers {
list-style-image: url("chrome://browser/skin/Toolbar.png");
-moz-image-region: rect(0, 468px, 18px, 450px);
}
#manage-share-providers > .toolbarbutton-icon {
min-height: 18px;
min-width: 18px;
}
.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
/* fixup corners for share panel */
.social-panel > .social-panel-frame {
border-radius: inherit;
}

Просмотреть файл

@ -54,7 +54,9 @@
}
.sidebar-splitter,
#appcontent ~ .sidebar-splitter {
#appcontent ~ .sidebar-splitter,
.chatbar-button,
chatbar > chatbox {
border-color: #A9B7C9;
}

Просмотреть файл

@ -1867,11 +1867,6 @@ html|span.ac-emphasize-text-url {
}
/* social share panel */
%include ../shared/social/social.inc.css
.social-panel-frame {
border-radius: inherit;
}
.social-share-frame {
min-width: 756px;
@ -1918,6 +1913,12 @@ html|span.ac-emphasize-text-url {
max-height: 16px;
}
/* fixup corners for share panel */
.social-panel > .social-panel-frame {
border-radius: inherit;
}
#social-share-panel {
min-height: 100px;
min-width: 766px;
@ -1941,6 +1942,12 @@ html|span.ac-emphasize-text-url {
border-top-right-radius: inherit;
}
/* social recommending panel */
#social-mark-button {
-moz-image-region: rect(0, 16px, 16px, 0);
}
/* bookmarks menu-button */
#nav-bar #bookmarks-menu-button[cui-areatype="toolbar"]:not([overflowedItem=true]) > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
@ -2528,6 +2535,53 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton {
margin-inline-end: 5px;
}
/* social toolbar provider menu */
.social-statusarea-popup {
margin-top: 0;
margin-left: -12px;
margin-right: -12px;
}
.social-statusarea-user {
-moz-appearance: none;
border-bottom: 1px solid rgb(221,221,221);
background-color: -moz-Dialog;
position: relative;
cursor: pointer;
list-style-image:url("chrome://global/skin/icons/information-32.png");
}
.social-statusarea-user-portrait {
width: 32px;
height: 32px;
border-radius: 2px;
margin: 10px;
}
.social-statusarea-loggedInStatus {
-moz-appearance: none;
background: transparent;
border: none;
color: -moz-nativehyperlinktext;
min-width: 0;
margin: 0 6px;
list-style-image: none;
}
.social-statusarea-user[_moz-menuactive] > vbox > .social-statusarea-loggedInStatus {
text-decoration: underline;
}
.social-panel > .panel-arrowcontainer > .panel-arrowcontent {
padding: 0;
}
.social-panel-frame {
border-radius: inherit;
}
%include ../shared/social/chat.inc.css
/* Customization mode */
%include ../shared/customizableui/customizeMode.inc.css

Просмотреть файл

@ -333,13 +333,8 @@ GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
// with no children, we don't have a hope of coming back from this call,
// so send the ack here. Otherwise, make a note of how many child scripts
// will be loaded so we known when it's safe to return.
// Child managers may not have child scripts yet (e.g. socialapi), so get
// the count from each child.
if (mm.childCount !== 0) {
this.curBrowser.frameRegsPending = 0;
for (let i = 0; i < mm.childCount; i++) {
this.curBrowser.frameRegsPending += mm.getChildAt(i).childCount;
}
this.curBrowser.frameRegsPending = mm.childCount;
}
}

Просмотреть файл

@ -614,7 +614,14 @@ Tester.prototype = {
sidebar.docShell.createAboutBlankContentViewer(null);
sidebar.setAttribute("src", "about:blank");
// Do the same for the social sidebar.
let socialSidebar = document.getElementById("social-sidebar-browser");
socialSidebar.setAttribute("src", "data:text/html;charset=utf-8,");
socialSidebar.docShell.createAboutBlankContentViewer(null);
socialSidebar.setAttribute("src", "about:blank");
SelfSupportBackend.uninit();
SocialFlyout.unload();
SocialShare.uninit();
}

Просмотреть файл

@ -75,6 +75,9 @@ if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
if CONFIG['MOZ_CRASHREPORTER']:
DIRS += ['crashes']
if CONFIG['MOZ_SOCIAL']:
DIRS += ['social']
if CONFIG['BUILD_CTYPES']:
DIRS += ['ctypes']

Просмотреть файл

@ -0,0 +1,298 @@
/* 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/. */
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SocialService", "resource://gre/modules/SocialService.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Social", "resource:///modules/Social.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
this.EXPORTED_SYMBOLS = [
"MozSocialAPI", "openChatWindow", "findChromeWindowForChats", "closeAllChatWindows",
"hookWindowCloseForPanelClose"
];
this.MozSocialAPI = {
_enabled: false,
_everEnabled: false,
set enabled(val) {
let enable = !!val;
if (enable == this._enabled) {
return;
}
this._enabled = enable;
if (enable) {
Services.obs.addObserver(injectController, "document-element-inserted", false);
if (!this._everEnabled) {
this._everEnabled = true;
Services.telemetry.getHistogramById("SOCIAL_ENABLED_ON_SESSION").add(true);
}
} else {
Services.obs.removeObserver(injectController, "document-element-inserted");
}
}
};
// Called on document-element-inserted, checks that the API should be injected,
// and then calls attachToWindow as appropriate
function injectController(doc, topic, data) {
try {
let window = doc.defaultView;
if (!window || PrivateBrowsingUtils.isContentWindowPrivate(window))
return;
// Do not attempt to load the API into about: error pages
if (doc.documentURIObject.scheme == "about") {
return;
}
let containingBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
// limit injecting into social panels or same-origin browser tabs if
// social.debug.injectIntoTabs is enabled
let allowTabs = false;
try {
allowTabs = containingBrowser.contentWindow == window &&
Services.prefs.getBoolPref("social.debug.injectIntoTabs");
} catch (e) {}
let origin = containingBrowser.getAttribute("origin");
if (!allowTabs && !origin) {
return;
}
// we always handle window.close on social content, even if they are not
// "enabled".
hookWindowCloseForPanelClose(window);
SocialService.getProvider(doc.nodePrincipal.origin, function(provider) {
if (provider && provider.enabled) {
attachToWindow(provider, window);
}
});
} catch (e) {
Cu.reportError("MozSocialAPI injectController: unable to attachToWindow for " + doc.location + ": " + e);
}
}
// Loads mozSocial support functions associated with provider into targetWindow
function attachToWindow(provider, targetWindow) {
// If the loaded document isn't from the provider's origin (or a protocol
// that inherits the principal), don't attach the mozSocial API.
let targetDocURI = targetWindow.document.documentURIObject;
if (!provider.isSameOrigin(targetDocURI)) {
let msg = "MozSocialAPI: not attaching mozSocial API for " + provider.origin +
" to " + targetDocURI.spec + " since origins differ."
Services.console.logStringMessage(msg);
return;
}
let mozSocialObj = {
openChatWindow: {
enumerable: true,
configurable: true,
writable: true,
value: function(toURL, callback) {
let url = targetWindow.document.documentURIObject.resolve(toURL);
let dwu = getChromeWindow(targetWindow)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
openChatWindow(targetWindow, provider, url, chatWin => {
if (chatWin && dwu.isHandlingUserInput)
chatWin.focus();
if (callback)
callback(!!chatWin);
});
}
},
openPanel: {
enumerable: true,
configurable: true,
writable: true,
value: function(toURL, offset, callback) {
let chromeWindow = getChromeWindow(targetWindow);
if (!chromeWindow.SocialFlyout)
return;
let url = targetWindow.document.documentURIObject.resolve(toURL);
if (!provider.isSameOrigin(url))
return;
chromeWindow.SocialFlyout.open(url, offset, callback);
}
},
closePanel: {
enumerable: true,
configurable: true,
writable: true,
value: function(toURL, offset, callback) {
let chromeWindow = getChromeWindow(targetWindow);
if (!chromeWindow.SocialFlyout || !chromeWindow.SocialFlyout.panel)
return;
chromeWindow.SocialFlyout.panel.hidePopup();
}
},
// allow a provider to share to other providers through the browser
share: {
enumerable: true,
configurable: true,
writable: true,
value: function(data) {
let chromeWindow = getChromeWindow(targetWindow);
if (!chromeWindow.SocialShare || chromeWindow.SocialShare.shareButton.hidden)
throw new Error("Share is unavailable");
// ensure user action initates the share
let dwu = chromeWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
if (!dwu.isHandlingUserInput)
throw new Error("Attempt to share without user input");
// limit to a few params we want to support for now
let dataOut = {};
for (let sub of ["url", "title", "description", "source"]) {
dataOut[sub] = data[sub];
}
if (data.image)
dataOut.previews = [data.image];
chromeWindow.SocialShare.sharePage(null, dataOut);
}
},
getAttention: {
enumerable: true,
configurable: true,
writable: true,
value: function() {
getChromeWindow(targetWindow).getAttention();
}
},
isVisible: {
enumerable: true,
configurable: true,
get: function() {
return targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell).isActive;
}
}
};
let contentObj = Cu.createObjectIn(targetWindow);
Object.defineProperties(contentObj, mozSocialObj);
Cu.makeObjectPropsNormal(contentObj);
targetWindow.navigator.wrappedJSObject.__defineGetter__("mozSocial", function() {
// We do this in a getter, so that we create these objects
// only on demand (this is a potential concern, since
// otherwise we might add one per iframe, and keep them
// alive for as long as the window is alive).
delete targetWindow.navigator.wrappedJSObject.mozSocial;
return targetWindow.navigator.wrappedJSObject.mozSocial = contentObj;
});
}
function hookWindowCloseForPanelClose(targetWindow) {
let _mozSocialDOMWindowClose;
if ("messageManager" in targetWindow) {
let _mozSocialSwapped;
let mm = targetWindow.messageManager;
mm.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
mm.addMessageListener("Social:DOMWindowClose", _mozSocialDOMWindowClose = function() {
targetWindow.removeEventListener("SwapDocShells", _mozSocialSwapped);
closePanel(targetWindow);
});
targetWindow.addEventListener("SwapDocShells", _mozSocialSwapped = function(ev) {
targetWindow.removeEventListener("SwapDocShells", _mozSocialSwapped);
targetWindow = ev.detail;
targetWindow.messageManager.addMessageListener("Social:DOMWindowClose", _mozSocialDOMWindowClose);
});
return;
}
// We allow window.close() to close the panel, so add an event handler for
// this, then cancel the event (so the window itself doesn't die) and
// close the panel instead.
// However, this is typically affected by the dom.allow_scripts_to_close_windows
// preference, but we can avoid that check by setting a flag on the window.
let dwu = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
dwu.allowScriptsToClose();
targetWindow.addEventListener("DOMWindowClose", _mozSocialDOMWindowClose = function(evt) {
let elt = targetWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
closePanel(elt);
// preventDefault stops the default window.close() function being called,
// which doesn't actually close anything but causes things to get into
// a bad state (an internal 'closed' flag is set and debug builds start
// asserting as the window is used.).
// None of the windows we inject this API into are suitable for this
// default close behaviour, so even if we took no action above, we avoid
// the default close from doing anything.
evt.preventDefault();
}, true);
}
function closePanel(elt) {
while (elt) {
if (elt.localName == "panel") {
elt.hidePopup();
break;
} else if (elt.localName == "chatbox") {
elt.close();
break;
}
elt = elt.parentNode;
}
}
function schedule(callback) {
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
}
function getChromeWindow(contentWin) {
return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
}
this.openChatWindow =
function openChatWindow(contentWindow, provider, url, callback, mode) {
let fullURI = provider.resolveUri(url);
if (!provider.isSameOrigin(fullURI)) {
Cu.reportError("Failed to open a social chat window - the requested URL is not the same origin as the provider.");
return;
}
let chatbox = Chat.open(contentWindow, {
origin: provider.origin,
title: provider.name,
url: fullURI.spec,
mode: mode
});
if (callback) {
chatbox.promiseChatLoaded.then(() => {
callback(chatbox);
});
}
}
this.closeAllChatWindows = function closeAllChatWindows(provider) {
return Chat.closeAll(provider.origin);
}

Просмотреть файл

@ -16,6 +16,8 @@ const ADDON_TYPE_SERVICE = "service";
const ID_SUFFIX = "@services.mozilla.org";
const STRING_TYPE_NAME = "type.%ID%.name";
XPCOMUtils.defineLazyModuleGetter(this, "MozSocialAPI", "resource://gre/modules/MozSocialAPI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "closeAllChatWindows", "resource://gre/modules/MozSocialAPI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "etld",
@ -139,6 +141,7 @@ XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
try {
if (ActiveProviders.has(manifest.origin)) {
// enable the api when a provider is enabled
MozSocialAPI.enabled = true;
let provider = new SocialProvider(manifest);
providers[provider.origin] = provider;
}
@ -401,6 +404,7 @@ this.SocialService = {
throw new Error("SocialService.addProvider: provider with this origin already exists");
// enable the api when a provider is enabled
MozSocialAPI.enabled = true;
let provider = new SocialProvider(manifest);
SocialServiceInternal.providers[provider.origin] = provider;
ActiveProviders.add(provider.origin);
@ -430,6 +434,8 @@ this.SocialService = {
ActiveProviders.delete(provider.origin);
delete SocialServiceInternal.providers[origin];
// disable the api if we have no enabled providers
MozSocialAPI.enabled = SocialServiceInternal.enabled;
if (addon) {
// we have to do this now so the addon manager ui will update an uninstall
@ -497,7 +503,7 @@ this.SocialService = {
},
_manifestFromData: function(type, data, installOrigin) {
let featureURLs = ['shareURL'];
let featureURLs = ['sidebarURL', 'shareURL', 'statusURL', 'markURL'];
let resolveURLs = featureURLs.concat(['postActivationURL']);
if (type == 'directory' || type == 'internal') {
@ -698,7 +704,12 @@ function SocialProvider(input) {
this.iconURL = input.iconURL;
this.icon32URL = input.icon32URL;
this.icon64URL = input.icon64URL;
this.sidebarURL = input.sidebarURL;
this.shareURL = input.shareURL;
this.statusURL = input.statusURL;
this.markURL = input.markURL;
this.markedIcon = input.markedIcon;
this.unmarkedIcon = input.unmarkedIcon;
this.postActivationURL = input.postActivationURL;
this.origin = input.origin;
let originUri = Services.io.newURI(input.origin, null, null);
@ -753,11 +764,29 @@ SocialProvider.prototype = {
return undefined;
},
// Map of objects describing the provider's notification icons, whose
// properties include:
// name, iconURL, counter, contentPanel
// See https://developer.mozilla.org/en-US/docs/Social_API
ambientNotificationIcons: null,
// Called by the workerAPI to add/update a notification icon.
setAmbientNotification: function(notification) {
if (!this.ambientNotificationIcons[notification.name] &&
Object.keys(this.ambientNotificationIcons).length >= 3) {
throw new Error("ambient notification limit reached");
}
this.ambientNotificationIcons[notification.name] = notification;
Services.obs.notifyObservers(null, "social:ambient-notification-changed", this.origin);
},
// Internal helper methods
_activate: function _activate() {
},
_terminate: function _terminate() {
closeAllChatWindows(this);
this.errorState = null;
},

Просмотреть файл

@ -0,0 +1,17 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
if CONFIG['MOZ_SOCIAL']:
# social is turned off for android
EXTRA_JS_MODULES += [
'MozSocialAPI.jsm',
'SocialService.jsm',
]
with Files('**'):
BUG_COMPONENT = ('Firefox', 'SocialAPI')

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
<emItems>
<emItem blockID="s1" id="bad.com@services.mozilla.org"></emItem>
</emItems>
</blocklist>

Просмотреть файл

@ -0,0 +1,125 @@
/* 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/. */
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
const MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
const gProfD = do_get_profile();
const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
var gAppInfo = null;
function createAppInfo(ID, name, version, platformVersion="1.0") {
let tmp = {};
Cu.import("resource://testing-common/AppInfo.jsm", tmp);
tmp.updateAppInfo({
ID, name, version, platformVersion,
crashReporter: true,
});
gAppInfo = tmp.getAppInfo();
}
function initApp() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
// prepare a blocklist file for the blocklist service
var blocklistFile = gProfD.clone();
blocklistFile.append("blocklist.xml");
if (blocklistFile.exists())
blocklistFile.remove(false);
var source = do_get_file("blocklist.xml");
source.copyTo(gProfD, "blocklist.xml");
blocklistFile.lastModifiedTime = Date.now();
}
function AsyncRunner() {
do_test_pending();
do_register_cleanup(() => this.destroy());
this._callbacks = {
done: do_test_finished,
error: function (err) {
// xpcshell test functions like do_check_eq throw NS_ERROR_ABORT on
// failure. Ignore those so they aren't rethrown here.
if (err !== Cr.NS_ERROR_ABORT) {
if (err.stack) {
err = err + " - See following stack:\n" + err.stack +
"\nUseless do_throw stack";
}
do_throw(err);
}
},
consoleError: function (scriptErr) {
// Try to ensure the error is related to the test.
let filename = scriptErr.sourceName || scriptErr.toString() || "";
if (filename.indexOf("/toolkit/components/social/") >= 0)
do_throw(scriptErr);
},
};
this._iteratorQueue = [];
// This catches errors reported to the console, e.g., via Cu.reportError, but
// not on the runner's stack.
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).
registerListener(this);
}
AsyncRunner.prototype = {
appendIterator: function appendIterator(iter) {
this._iteratorQueue.push(iter);
},
next: function next(arg) {
if (!this._iteratorQueue.length) {
this.destroy();
this._callbacks.done();
return;
}
try {
var { done, value: val } = this._iteratorQueue[0].next(arg);
if (done) {
this._iteratorQueue.shift();
this.next();
return;
}
}
catch (err) {
this._callbacks.error(err);
}
// val is an iterator => prepend it to the queue and start on it
// val is otherwise truthy => call next
if (val) {
if (typeof(val) != "boolean")
this._iteratorQueue.unshift(val);
this.next();
}
},
destroy: function destroy() {
Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService).
unregisterListener(this);
this.destroy = function alreadyDestroyed() {};
},
observe: function observe(msg) {
if (msg instanceof Ci.nsIScriptError &&
!(msg.flags & Ci.nsIScriptError.warningFlag))
{
this._callbacks.consoleError(msg);
}
},
};

Просмотреть файл

@ -16,16 +16,17 @@ function run_test() {
{ // normal provider
name: "provider 1",
origin: "https://example1.com",
shareURL: "https://example1.com/share/",
sidebarURL: "https://example1.com/sidebar/",
},
{ // provider without workerURL
name: "provider 2",
origin: "https://example2.com",
shareURL: "https://example2.com/share/",
sidebarURL: "https://example2.com/sidebar/",
}
];
Cu.import("resource:///modules/SocialService.jsm");
Cu.import("resource://gre/modules/SocialService.jsm");
Cu.import("resource://gre/modules/MozSocialAPI.jsm");
let runner = new AsyncRunner();
let next = runner.next.bind(runner);
@ -44,6 +45,7 @@ function* testAddProviders(manifests, next) {
do_check_false(SocialService.enabled);
let provider = yield SocialService.addProvider(manifests[0], next);
do_check_true(SocialService.enabled);
do_check_true(MozSocialAPI._enabled);
do_check_false(provider.enabled);
provider = yield SocialService.addProvider(manifests[1], next);
do_check_false(provider.enabled);
@ -150,7 +152,7 @@ function* testOrderedProviders(manifests, next) {
let startDate = Date.now() * 1000;
for (let i = 0; i < 10; i++) {
visits.push({
uri: Services.io.newURI(providers[1].shareURL + i, null, null),
uri: Services.io.newURI(providers[1].sidebarURL + i, null, null),
visitDate: startDate + i
});
}

Просмотреть файл

@ -22,7 +22,7 @@ function run_test() {
DEFAULT_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
Services.prefs.setBoolPref("social.active", true);
Cu.import("resource:///modules/SocialService.jsm");
Cu.import("resource://gre/modules/SocialService.jsm");
let runner = new AsyncRunner();
let next = runner.next.bind(runner);

Просмотреть файл

@ -32,7 +32,7 @@ function run_test() {
Services.prefs.setComplexValue("social.activeProviders",
Ci.nsISupportsString, activeVal);
Cu.import("resource:///modules/SocialService.jsm");
Cu.import("resource://gre/modules/SocialService.jsm");
let runner = new AsyncRunner();
let next = runner.next.bind(runner);

Просмотреть файл

@ -33,7 +33,7 @@ function run_test() {
// b) unset social.enabled
Services.prefs.setBoolPref("social.enabled", false);
Cu.import("resource:///modules/SocialService.jsm");
Cu.import("resource://gre/modules/SocialService.jsm");
let runner = new AsyncRunner();
let next = runner.next.bind(runner);

Просмотреть файл

@ -0,0 +1,13 @@
[DEFAULT]
head = head.js
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files = blocklist.xml
[test_SocialService.js]
[test_SocialServiceMigration21.js]
[test_SocialServiceMigration22.js]
[test_SocialServiceMigration29.js]