mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
bug 891219 new social bookmarks, part 2, new implementation, r=markh
This commit is contained in:
parent
967bf9736a
commit
fbbdb70eaf
@ -45,6 +45,10 @@
|
||||
label="&saveLinkCmd.label;"
|
||||
accesskey="&saveLinkCmd.accesskey;"
|
||||
oncommand="gContextMenu.saveLink();"/>
|
||||
<menu id="context-marklinkMenu" label="&social.marklinkMenu.label;"
|
||||
accesskey="&social.marklink.accesskey;">
|
||||
<menupopup/>
|
||||
</menu>
|
||||
<menuitem id="context-copyemail"
|
||||
label="©EmailCmd.label;"
|
||||
accesskey="©EmailCmd.accesskey;"
|
||||
@ -247,6 +251,10 @@
|
||||
label="&savePageCmd.label;"
|
||||
accesskey="&savePageCmd.accesskey2;"
|
||||
oncommand="gContextMenu.savePageAs();"/>
|
||||
<menu id="context-markpageMenu" label="&social.markpageMenu.label;"
|
||||
accesskey="&social.markpage.accesskey;">
|
||||
<menupopup/>
|
||||
</menu>
|
||||
<menuseparator id="context-sep-viewbgimage"/>
|
||||
<menuitem id="context-viewbgimage"
|
||||
label="&viewBGImageCmd.label;"
|
||||
|
@ -6,6 +6,7 @@
|
||||
let SocialUI,
|
||||
SocialChatBar,
|
||||
SocialFlyout,
|
||||
SocialMarks,
|
||||
SocialShare,
|
||||
SocialMenu,
|
||||
SocialToolbar,
|
||||
@ -26,6 +27,18 @@ XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
|
||||
return tmp.OpenGraphBuilder;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DynamicResizeWatcher", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/Social.jsm", tmp);
|
||||
return tmp.DynamicResizeWatcher;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "sizeSocialPanelToContent", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource:///modules/Social.jsm", tmp);
|
||||
return tmp.sizeSocialPanelToContent;
|
||||
});
|
||||
|
||||
SocialUI = {
|
||||
// Called on delayed startup to initialize the UI
|
||||
init: function SocialUI_init() {
|
||||
@ -44,6 +57,10 @@ SocialUI = {
|
||||
Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
|
||||
|
||||
gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
|
||||
window.addEventListener("aftercustomization", function() {
|
||||
if (SocialUI.enabled)
|
||||
SocialMarks.populateContextMenu(SocialMarks);
|
||||
}, false);
|
||||
|
||||
if (!Social.initialized) {
|
||||
Social.init();
|
||||
@ -82,15 +99,19 @@ SocialUI = {
|
||||
try {
|
||||
switch (topic) {
|
||||
case "social:provider-installed":
|
||||
SocialMarks.setPosition(data);
|
||||
SocialStatus.setPosition(data);
|
||||
break;
|
||||
case "social:provider-uninstalled":
|
||||
SocialMarks.removePosition(data);
|
||||
SocialStatus.removePosition(data);
|
||||
break;
|
||||
case "social:provider-enabled":
|
||||
SocialMarks.populateToolbarPalette();
|
||||
SocialStatus.populateToolbarPalette();
|
||||
break;
|
||||
case "social:provider-disabled":
|
||||
SocialMarks.removeProvider(data);
|
||||
SocialStatus.removeProvider(data);
|
||||
break;
|
||||
case "social:provider-reload":
|
||||
@ -115,6 +136,7 @@ SocialUI = {
|
||||
SocialSidebar.update();
|
||||
SocialToolbar.update();
|
||||
SocialStatus.populateToolbarPalette();
|
||||
SocialMarks.populateToolbarPalette();
|
||||
SocialMenu.populate();
|
||||
break;
|
||||
case "social:providers-changed":
|
||||
@ -124,6 +146,7 @@ SocialUI = {
|
||||
SocialToolbar.populateProviderMenus();
|
||||
SocialShare.populateProviderMenu();
|
||||
SocialStatus.populateToolbarPalette();
|
||||
SocialMarks.populateToolbarPalette();
|
||||
break;
|
||||
|
||||
// Provider-specific notifications
|
||||
@ -137,6 +160,7 @@ SocialUI = {
|
||||
case "social:profile-changed":
|
||||
if (this._matchesCurrentProvider(data)) {
|
||||
SocialToolbar.updateProvider();
|
||||
SocialMarks.update();
|
||||
SocialChatBar.update();
|
||||
}
|
||||
break;
|
||||
@ -363,6 +387,14 @@ SocialUI = {
|
||||
return !!Social.provider;
|
||||
},
|
||||
|
||||
// called on tab/urlbar/location changes and after customization. Update
|
||||
// anything that is tab specific.
|
||||
updateState: function() {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
SocialMarks.update();
|
||||
SocialShare.update();
|
||||
}
|
||||
}
|
||||
|
||||
SocialChatBar = {
|
||||
@ -403,63 +435,6 @@ SocialChatBar = {
|
||||
}
|
||||
}
|
||||
|
||||
function sizeSocialPanelToContent(panel, iframe) {
|
||||
// FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
|
||||
let doc = iframe.contentDocument;
|
||||
if (!doc || !doc.body) {
|
||||
return;
|
||||
}
|
||||
// We need an element to use for sizing our panel. See if the body defines
|
||||
// an id for that element, otherwise use the body itself.
|
||||
let body = doc.body;
|
||||
let bodyId = body.getAttribute("contentid");
|
||||
if (bodyId) {
|
||||
body = doc.getElementById(bodyId) || doc.body;
|
||||
}
|
||||
// offsetHeight/Width don't include margins, so account for that.
|
||||
let cs = doc.defaultView.getComputedStyle(body);
|
||||
let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
|
||||
let height = Math.max(computedHeight, PANEL_MIN_HEIGHT);
|
||||
let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
|
||||
let width = Math.max(computedWidth, PANEL_MIN_WIDTH);
|
||||
iframe.style.width = width + "px";
|
||||
iframe.style.height = height + "px";
|
||||
// since we do not use panel.sizeTo, we need to adjust the arrow ourselves
|
||||
if (panel.state == "open")
|
||||
panel.adjustArrowPosition();
|
||||
}
|
||||
|
||||
function DynamicResizeWatcher() {
|
||||
this._mutationObserver = null;
|
||||
}
|
||||
|
||||
DynamicResizeWatcher.prototype = {
|
||||
start: function DynamicResizeWatcher_start(panel, iframe) {
|
||||
this.stop(); // just in case...
|
||||
let doc = iframe.contentDocument;
|
||||
this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) {
|
||||
sizeSocialPanelToContent(panel, iframe);
|
||||
});
|
||||
// Observe anything that causes the size to change.
|
||||
let config = {attributes: true, characterData: true, childList: true, subtree: true};
|
||||
this._mutationObserver.observe(doc, config);
|
||||
// and since this may be setup after the load event has fired we do an
|
||||
// initial resize now.
|
||||
sizeSocialPanelToContent(panel, iframe);
|
||||
},
|
||||
stop: function DynamicResizeWatcher_stop() {
|
||||
if (this._mutationObserver) {
|
||||
try {
|
||||
this._mutationObserver.disconnect();
|
||||
} catch (ex) {
|
||||
// may get "TypeError: can't access dead object" which seems strange,
|
||||
// but doesn't seem to indicate a real problem, so ignore it...
|
||||
}
|
||||
this._mutationObserver = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SocialFlyout = {
|
||||
get panel() {
|
||||
return document.getElementById("social-flyout-panel");
|
||||
@ -765,7 +740,7 @@ SocialShare = {
|
||||
}
|
||||
this.currentShare = pageData;
|
||||
|
||||
let shareEndpoint = this._generateShareEndpointURL(provider.shareURL, pageData);
|
||||
let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
|
||||
|
||||
this._dynamicResizer = new DynamicResizeWatcher();
|
||||
// if we've already loaded this provider/page share endpoint, we don't want
|
||||
@ -810,44 +785,6 @@ SocialShare = {
|
||||
document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
|
||||
this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
|
||||
Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
|
||||
},
|
||||
|
||||
_generateShareEndpointURL: function(shareURL, pageData) {
|
||||
// support for existing share endpoints by supporting their querystring
|
||||
// arguments. parse the query string template and do replacements where
|
||||
// necessary the query names may be different than ours, so we could see
|
||||
// u=%{url} or url=%{url}
|
||||
let [shareEndpoint, queryString] = shareURL.split("?");
|
||||
let query = {};
|
||||
if (queryString) {
|
||||
queryString.split('&').forEach(function (val) {
|
||||
let [name, value] = val.split('=');
|
||||
let p = /%\{(.+)\}/.exec(value);
|
||||
if (!p) {
|
||||
// preserve non-template query vars
|
||||
query[name] = value;
|
||||
} else if (pageData[p[1]]) {
|
||||
query[name] = pageData[p[1]];
|
||||
} else if (p[1] == "body") {
|
||||
// build a body for emailers
|
||||
let body = "";
|
||||
if (pageData.title)
|
||||
body += pageData.title + "\n\n";
|
||||
if (pageData.description)
|
||||
body += pageData.description + "\n\n";
|
||||
if (pageData.text)
|
||||
body += pageData.text + "\n\n";
|
||||
body += pageData.url;
|
||||
query["body"] = body;
|
||||
}
|
||||
});
|
||||
}
|
||||
var str = [];
|
||||
for (let p in query)
|
||||
str.push(p + "=" + encodeURIComponent(query[p]));
|
||||
if (str.length)
|
||||
shareEndpoint = shareEndpoint + "?" + str.join("&");
|
||||
return shareEndpoint;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1450,7 +1387,7 @@ ToolbarHelper.prototype = {
|
||||
for (let provider of Social.providers) {
|
||||
let id = this.idFromOrgin(provider.origin);
|
||||
if (this._getExistingButton(id))
|
||||
return;
|
||||
continue;
|
||||
let button = this._createButton(provider);
|
||||
if (button && persistedById.hasOwnProperty(id)) {
|
||||
let parent = persistedById[id];
|
||||
@ -1684,4 +1621,130 @@ SocialStatus = {
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* SocialMarks
|
||||
*
|
||||
* Handles updates to toolbox and signals all buttons to update when necessary.
|
||||
*/
|
||||
SocialMarks = {
|
||||
update: function() {
|
||||
// signal each button to update itself
|
||||
let currentButtons = document.querySelectorAll('toolbarbutton[type="socialmark"]');
|
||||
for (let elt of currentButtons)
|
||||
elt.update();
|
||||
},
|
||||
|
||||
getProviders: function() {
|
||||
// only rely on providers that the user has placed in the UI somewhere. This
|
||||
// also means that populateToolbarPalette must be called prior to using this
|
||||
// method, otherwise you get a big fat zero. For our use case with context
|
||||
// menu's, this is ok.
|
||||
let tbh = this._toolbarHelper;
|
||||
return [p for (p of Social.providers) if (p.markURL &&
|
||||
document.getElementById(tbh.idFromOrgin(p.origin)))];
|
||||
},
|
||||
|
||||
populateContextMenu: function() {
|
||||
// only show a selection if enabled and there is more than one
|
||||
let providers = this.getProviders();
|
||||
|
||||
// remove all previous entries by class
|
||||
let menus = [m for (m of document.getElementsByClassName("context-socialmarks"))];
|
||||
[m.parentNode.removeChild(m) for (m of menus)];
|
||||
|
||||
let contextMenus = [
|
||||
{
|
||||
type: "link",
|
||||
id: "context-marklinkMenu",
|
||||
label: "social.marklink.label"
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
id: "context-markpageMenu",
|
||||
label: "social.markpage.label"
|
||||
}
|
||||
];
|
||||
for (let cfg of contextMenus) {
|
||||
this._populateContextPopup(cfg, providers);
|
||||
}
|
||||
},
|
||||
|
||||
MENU_LIMIT: 3, // adjustable for testing
|
||||
_populateContextPopup: function(menuInfo, providers) {
|
||||
let menu = document.getElementById(menuInfo.id);
|
||||
let popup = menu.firstChild;
|
||||
for (let provider of providers) {
|
||||
// We show up to MENU_LIMIT providers as single menuitems's at the top
|
||||
// level of the context menu, if we have more than that, dump them *all*
|
||||
// into the menu popup.
|
||||
let mi = document.createElement("menuitem");
|
||||
mi.setAttribute("oncommand", "gContextMenu.markLink(this.getAttribute('origin'));");
|
||||
mi.setAttribute("origin", provider.origin);
|
||||
mi.setAttribute("image", provider.iconURL);
|
||||
if (providers.length <= this.MENU_LIMIT) {
|
||||
// an extra class to make enable/disable easy
|
||||
mi.setAttribute("class", "menuitem-iconic context-socialmarks context-mark"+menuInfo.type);
|
||||
let menuLabel = gNavigatorBundle.getFormattedString(menuInfo.label, [provider.name]);
|
||||
mi.setAttribute("label", menuLabel);
|
||||
menu.parentNode.insertBefore(mi, menu);
|
||||
} else {
|
||||
mi.setAttribute("class", "menuitem-iconic context-socialmarks");
|
||||
mi.setAttribute("label", provider.name);
|
||||
popup.appendChild(mi);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
populateToolbarPalette: function() {
|
||||
this._toolbarHelper.populatePalette();
|
||||
this.populateContextMenu();
|
||||
},
|
||||
|
||||
setPosition: function(origin) {
|
||||
// this is called during install, before the provider is enabled so we have
|
||||
// to use the manifest rather than the provider instance as we do elsewhere.
|
||||
let manifest = Social.getManifestByOrigin(origin);
|
||||
if (!manifest.markURL)
|
||||
return;
|
||||
let tbh = this._toolbarHelper;
|
||||
tbh.setPersistentPosition(tbh.idFromOrgin(origin));
|
||||
},
|
||||
|
||||
removePosition: function(origin) {
|
||||
let tbh = this._toolbarHelper;
|
||||
tbh.removePersistence(tbh.idFromOrgin(origin));
|
||||
},
|
||||
|
||||
removeProvider: function(origin) {
|
||||
this._toolbarHelper.removeProviderButton(origin);
|
||||
},
|
||||
|
||||
get _toolbarHelper() {
|
||||
delete this._toolbarHelper;
|
||||
this._toolbarHelper = new ToolbarHelper("social-mark-button", this._createButton.bind(this));
|
||||
return this._toolbarHelper;
|
||||
},
|
||||
|
||||
_createButton: function(provider) {
|
||||
if (!provider.markURL)
|
||||
return null;
|
||||
let palette = document.getElementById("navigator-toolbox").palette;
|
||||
let button = document.createElement("toolbarbutton");
|
||||
button.setAttribute("type", "socialmark");
|
||||
button.setAttribute("class", "toolbarbutton-1 social-mark-button");
|
||||
button.style.listStyleImage = "url(" + provider.iconURL + ")";
|
||||
button.setAttribute("origin", provider.origin);
|
||||
button.setAttribute("id", this._toolbarHelper.idFromOrgin(provider.origin));
|
||||
palette.appendChild(button);
|
||||
return button
|
||||
},
|
||||
|
||||
markLink: function(aOrigin, aUrl) {
|
||||
// find the button for this provider, and open it
|
||||
let id = this._toolbarHelper.idFromOrgin(aOrigin);
|
||||
document.getElementById(id).markLink(aUrl);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -656,6 +656,14 @@ toolbarbutton[type="badged"] {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#toolbarbutton-badged");
|
||||
}
|
||||
|
||||
toolbarbutton[type="socialmark"] {
|
||||
-moz-binding: url("chrome://browser/content/socialmarks.xml#toolbarbutton-marks");
|
||||
}
|
||||
toolbarbutton[type="socialmark"] > .toolbarbutton-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Note the chatbox 'width' values are duplicated in socialchat.xml */
|
||||
chatbox {
|
||||
-moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
|
||||
|
@ -3445,7 +3445,7 @@ function BrowserToolboxCustomizeDone(aToolboxChanged) {
|
||||
URLBarSetURI();
|
||||
XULBrowserWindow.asyncUpdateUI();
|
||||
BookmarkingUI.updateStarState();
|
||||
SocialShare.update();
|
||||
SocialUI.updateState();
|
||||
}
|
||||
|
||||
TabsInTitlebar.allowedBy("customizing-toolbars", true);
|
||||
@ -3913,9 +3913,7 @@ var XULBrowserWindow = {
|
||||
|
||||
// Update starring UI
|
||||
BookmarkingUI.updateStarState();
|
||||
if (SocialUI.enabled) {
|
||||
SocialShare.update();
|
||||
}
|
||||
SocialUI.updateState();
|
||||
}
|
||||
|
||||
// Show or hide browser chrome based on the whitelist
|
||||
|
@ -309,6 +309,24 @@ 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");
|
||||
[m.hidden = !enablePageMarkItems for (m of linkmenus)];
|
||||
|
||||
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");
|
||||
[m.hidden = !enableLinkMarkItems for (m of linkmenus)];
|
||||
|
||||
// SocialShare
|
||||
let shareButton = SocialShare.shareButton;
|
||||
let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
|
||||
@ -1571,6 +1589,10 @@ nsContextMenu.prototype = {
|
||||
}, window.top);
|
||||
}
|
||||
},
|
||||
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);
|
||||
},
|
||||
shareLink: function CM_shareLink() {
|
||||
SocialShare.sharePage(null, { url: this.linkURI.spec });
|
||||
},
|
||||
|
236
browser/base/content/socialmarks.xml
Normal file
236
browser/base/content/socialmarks.xml
Normal file
@ -0,0 +1,236 @@
|
||||
<?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 disabled="true">
|
||||
<xul:panel anonid="panel" hidden="true" type="arrow" class="social-panel">
|
||||
<xul:iframe type="content" flex="1" tooltip="aHTMLTooltip"
|
||||
class="social-panel-frame" context="contentAreaContextMenu"
|
||||
xbl:inherits="src,origin"/>
|
||||
</xul: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"/>
|
||||
</content>
|
||||
<implementation implements="nsIDOMEventListener, nsIObserver">
|
||||
<field name="panel" readonly="true">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "panel");
|
||||
</field>
|
||||
|
||||
<field name="content" readonly="true">
|
||||
this.panel.firstChild;
|
||||
</field>
|
||||
|
||||
<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>
|
||||
this._isMarked = val;
|
||||
let provider = this.provider;
|
||||
// we cannot size the image when we apply it via listStyleImage, so
|
||||
// use the toolbar image
|
||||
if (val)
|
||||
this.setAttribute("image", provider.unmarkedIcon || provider.iconURL);
|
||||
else
|
||||
this.setAttribute("image", provider.markedIcon || provider.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.setAttribute("src", "about:blank");
|
||||
|
||||
// do we have a savable page loaded?
|
||||
let aURI = gBrowser.currentURI;
|
||||
this.disabled = !aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https'));
|
||||
|
||||
if (this.disabled) {
|
||||
this.isMarked = false;
|
||||
} else {
|
||||
Social.isURIMarked(provider.origin, aURI, (isMarked) => {
|
||||
this.isMarked = isMarked;
|
||||
});
|
||||
}
|
||||
this.setAttribute("label", provider.name);
|
||||
this.setAttribute("tooltiptext", provider.name);
|
||||
this.setAttribute("origin", provider.origin);
|
||||
this.panel.hidePopup();
|
||||
this.panel.hidden = true;
|
||||
this.pageData = null;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="loadPanel">
|
||||
<parameter name="pageData"/>
|
||||
<body><![CDATA[
|
||||
let provider = this.provider;
|
||||
this.panel.hidden = false;
|
||||
let URLTemplate = provider.markURL;
|
||||
this.pageData = pageData || OpenGraphBuilder.getData(gBrowser);
|
||||
let endpoint = OpenGraphBuilder.generateEndpointURL(URLTemplate, this.pageData);
|
||||
|
||||
// setup listeners
|
||||
this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
|
||||
if (event.target != this.contentDocument)
|
||||
return;
|
||||
this._loading = false;
|
||||
this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
|
||||
// add our resizer after the dom is ready
|
||||
let DynamicResizeWatcher = Cu.import("resource:///modules/Social.jsm", {}).DynamicResizeWatcher;
|
||||
this._dynamicResizer = new DynamicResizeWatcher();
|
||||
this._dynamicResizer.start(this.panel, this.content);
|
||||
// send the opengraph data
|
||||
let evt = this.contentDocument.createEvent("CustomEvent");
|
||||
evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(this.pageData));
|
||||
this.contentDocument.documentElement.dispatchEvent(evt);
|
||||
|
||||
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;
|
||||
let uri = Services.io.newURI(this.pageData.url, null, null);
|
||||
if (this.isMarked) {
|
||||
Social.markURI(provider.origin, uri);
|
||||
} else {
|
||||
Social.unmarkURI(provider.origin, uri, () => {
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
}.bind(this);
|
||||
contentWindow.addEventListener("socialMarkUpdate", markUpdate);
|
||||
contentWindow.addEventListener("unload", function unload() {
|
||||
contentWindow.removeEventListener("unload", unload);
|
||||
contentWindow.removeEventListener("socialMarkUpdate", markUpdate);
|
||||
});
|
||||
}, true);
|
||||
this._loading = true;
|
||||
this.setAttribute("src", endpoint);
|
||||
]]></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.getAttribute("src");
|
||||
if (!src || src == "about:blank") {
|
||||
this.loadPanel();
|
||||
this.isMarked = true;
|
||||
}
|
||||
if (openPanel)
|
||||
this.panel.openPopup(this, "bottomcenter topright", 0, 0, false, false);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="markLink">
|
||||
<parameter name="aUrl"/>
|
||||
<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.setAttribute("src", "about:blank");
|
||||
this.loadPanel({ url: aUrl });
|
||||
this.isMarked = true;
|
||||
this.panel.openPopup(this, "bottomcenter topright", 0, 0, false, false);
|
||||
this.panel.addEventListener("popuphidden", function _hidden() {
|
||||
this.panel.removeEventListener("popuphidden", _hidden);
|
||||
this.update();
|
||||
}.bind(this), false);
|
||||
]]></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>
|
||||
|
||||
</implementation>
|
||||
<handlers>
|
||||
<handler event="popupshown"><![CDATA[
|
||||
// because the panel may be preloaded, we need to size the panel when
|
||||
// showing as well as after load
|
||||
let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent;
|
||||
if (!this._loading && this.contentDocument.readyState == "complete") {
|
||||
this.dispatchPanelEvent("socialFrameShow");
|
||||
sizeSocialPanelToContent(this.panel, this.content);
|
||||
} else {
|
||||
this.content.addEventListener("load", function panelBrowserOnload(e) {
|
||||
this.content.removeEventListener("load", panelBrowserOnload, true);
|
||||
this.dispatchPanelEvent("socialFrameShow");
|
||||
sizeSocialPanelToContent(this.panel, this.content);
|
||||
}.bind(this), true);
|
||||
}
|
||||
]]></handler>
|
||||
<handler event="popuphidden"><![CDATA[
|
||||
this.dispatchPanelEvent("socialFrameHide");
|
||||
]]></handler>
|
||||
<handler event="command"><![CDATA[
|
||||
this.markCurrentPage();
|
||||
]]></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 uri = DOMLinkHandler.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);
|
||||
]]></handler>
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
</bindings>
|
@ -22,17 +22,20 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_social_multiprovider.js \
|
||||
browser_social_multiworker.js \
|
||||
browser_social_errorPage.js \
|
||||
browser_social_marks.js \
|
||||
browser_social_status.js \
|
||||
browser_social_window.js \
|
||||
social_activate.html \
|
||||
social_activate_iframe.html \
|
||||
browser_share.js \
|
||||
social_panel.html \
|
||||
social_mark_image.png \
|
||||
social_mark.html \
|
||||
social_sidebar.html \
|
||||
social_chat.html \
|
||||
social_flyout.html \
|
||||
social_window.html \
|
||||
social_worker.js \
|
||||
share.html \
|
||||
checked.jpg \
|
||||
unchecked.jpg \
|
||||
$(NULL)
|
||||
|
340
browser/base/content/test/social/browser_social_marks.js
Normal file
340
browser/base/content/test/social/browser_social_marks.js
Normal file
@ -0,0 +1,340 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
|
||||
|
||||
let manifest = { // builtin provider
|
||||
name: "provider example.com",
|
||||
origin: "https://example.com",
|
||||
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
|
||||
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
|
||||
};
|
||||
let manifest2 = { // used for testing install
|
||||
name: "provider test1",
|
||||
origin: "https://test1.example.com",
|
||||
workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
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/moz.png",
|
||||
version: 1
|
||||
};
|
||||
let 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/moz.png",
|
||||
version: 1
|
||||
};
|
||||
function makeMarkProvider(origin) {
|
||||
return { // used for testing install
|
||||
name: "mark provider " + origin,
|
||||
origin: "https://" + origin + ".example.com",
|
||||
workerURL: "https://" + origin + ".example.com/browser/browser/base/content/test/social/social_worker.js",
|
||||
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/moz.png",
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Services.prefs.setBoolPref("social.allowMultipleWorkers", true);
|
||||
let toolbar = document.getElementById("nav-bar");
|
||||
let currentsetAtStart = toolbar.currentSet;
|
||||
runSocialTestWithProvider(manifest, function () {
|
||||
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.allowMultipleWorkers");
|
||||
Services.prefs.clearUserPref("social.whitelist");
|
||||
|
||||
// This post-test test ensures that a new window maintains the same
|
||||
// toolbar button set as when we started. That means our insert/removal of
|
||||
// persistent id's is working correctly
|
||||
is(currentsetAtStart, toolbar.currentSet, "toolbar currentset unchanged");
|
||||
openWindowAndWaitForInit(function(w1) {
|
||||
checkSocialUI(w1);
|
||||
// Sometimes the new window adds other buttons to currentSet that are
|
||||
// outside the scope of what we're checking. So we verify that all
|
||||
// buttons from startup are in currentSet for a new window, and that the
|
||||
// provider buttons are properly removed. (e.g on try, window-controls
|
||||
// was not present in currentsetAtStart, but present on the second
|
||||
// window)
|
||||
let tb1 = w1.document.getElementById("nav-bar");
|
||||
let startupSet = Set(toolbar.currentSet.split(','));
|
||||
let newSet = Set(tb1.currentSet.split(','));
|
||||
let intersect = Set([x for (x of startupSet) if (newSet.has(x))]);
|
||||
let difference = Set([x for (x of newSet) if (!startupSet.has(x))]);
|
||||
is(startupSet.size, intersect.size, "new window toolbar same as old");
|
||||
// verify that our provider buttons are not in difference
|
||||
let id = SocialMarks._toolbarHelper.idFromOrgin(manifest2.origin);
|
||||
ok(!difference.has(id), "mark button not persisted at end");
|
||||
w1.close();
|
||||
finish();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var tests = {
|
||||
testNoButtonOnInstall: function(next) {
|
||||
// we expect the addon install dialog to appear, we need to accept the
|
||||
// install from the dialog.
|
||||
info("Waiting for install dialog");
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
})
|
||||
|
||||
let id = "social-mark-button-" + manifest3.origin;
|
||||
let toolbar = document.getElementById("nav-bar");
|
||||
let currentset = toolbar.getAttribute("currentset").split(',');
|
||||
ok(currentset.indexOf(id) < 0, "button is not part of currentset at start");
|
||||
|
||||
let activationURL = manifest3.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||
addTab(activationURL, function(tab) {
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
Social.installProvider(doc, manifest3, function(addonManifest) {
|
||||
// enable the provider so we know the button would have appeared
|
||||
SocialService.addBuiltinProvider(manifest3.origin, function(provider) {
|
||||
ok(provider, "provider is installed");
|
||||
currentset = toolbar.getAttribute("currentset").split(',');
|
||||
ok(currentset.indexOf(id) < 0, "button was not added to currentset");
|
||||
Social.uninstallProvider(manifest3.origin, function() {
|
||||
gBrowser.removeTab(tab);
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnInstall: function(next) {
|
||||
// we expect the addon install dialog to appear, we need to accept the
|
||||
// install from the dialog.
|
||||
info("Waiting for install dialog");
|
||||
let panel = document.getElementById("servicesInstall-notification");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
})
|
||||
|
||||
let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||
addTab(activationURL, function(tab) {
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
Social.installProvider(doc, manifest2, function(addonManifest) {
|
||||
// at this point, we should have a button id in the currentset for our provider
|
||||
let id = "social-mark-button-" + manifest2.origin;
|
||||
let toolbar = document.getElementById("nav-bar");
|
||||
|
||||
waitForCondition(function() {
|
||||
let currentset = toolbar.getAttribute("currentset").split(',');
|
||||
return currentset.indexOf(id) >= 0;
|
||||
},
|
||||
function() {
|
||||
// no longer need the tab
|
||||
gBrowser.removeTab(tab);
|
||||
next();
|
||||
}, "mark button added to currentset");
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnEnable: function(next) {
|
||||
// enable the provider now
|
||||
SocialService.addBuiltinProvider(manifest2.origin, function(provider) {
|
||||
ok(provider, "provider is installed");
|
||||
let id = "social-mark-button-" + manifest2.origin;
|
||||
waitForCondition(function() { return document.getElementById(id) },
|
||||
function() {
|
||||
checkSocialUI(window);
|
||||
next();
|
||||
}, "button exists after enabling social");
|
||||
});
|
||||
},
|
||||
|
||||
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 = "social-mark-button-" + provider.origin;
|
||||
let btn = document.getElementById(id)
|
||||
ok(btn, "got a mark button");
|
||||
let port = provider.getWorkerPort();
|
||||
ok(port, "got a port");
|
||||
|
||||
// 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"
|
||||
addTab(activationURL, function(tab) {
|
||||
ok(!btn.disabled, "button is enabled");
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "test-init-done":
|
||||
ok(true, "test-init-done received");
|
||||
ok(provider.profile.userName, "profile was set by test worker");
|
||||
// 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
|
||||
waitForCondition(function() btn.isMarked, function() {
|
||||
EventUtils.synthesizeMouseAtCenter(btn, {});
|
||||
}, "button is marked");
|
||||
break;
|
||||
case "got-social-panel-visibility":
|
||||
ok(true, "got the panel message " + e.data.result);
|
||||
if (e.data.result == "shown") {
|
||||
// unmark the page via the button in the page
|
||||
let doc = btn.contentDocument;
|
||||
let unmarkBtn = doc.getElementById("unmark");
|
||||
ok(unmarkBtn, "got the panel unmark button");
|
||||
EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
|
||||
} else {
|
||||
// page should no longer be marked
|
||||
port.close();
|
||||
waitForCondition(function() !btn.isMarked, function() {
|
||||
// after closing the addons tab, verify provider is still installed
|
||||
gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
|
||||
gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
|
||||
executeSoon(function () {
|
||||
ok(btn.disabled, "button is disabled");
|
||||
next();
|
||||
});
|
||||
});
|
||||
gBrowser.removeTab(tab);
|
||||
}, "button unmarked");
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
port.postMessage({topic: "test-init"});
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnDisable: function(next) {
|
||||
// enable the provider now
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
ok(provider, "provider is installed");
|
||||
SocialService.removeProvider(manifest2.origin, function() {
|
||||
let id = "social-mark-button-" + manifest2.origin;
|
||||
waitForCondition(function() { return !document.getElementById(id) },
|
||||
function() {
|
||||
checkSocialUI(window);
|
||||
next();
|
||||
}, "button does not exist after disabling the provider");
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnUninstall: function(next) {
|
||||
Social.uninstallProvider(manifest2.origin, function() {
|
||||
// test that the button is no longer persisted
|
||||
let id = "social-mark-button-" + manifest2.origin;
|
||||
let toolbar = document.getElementById("nav-bar");
|
||||
let currentset = toolbar.getAttribute("currentset").split(',');
|
||||
is(currentset.indexOf(id), -1, "button no longer in currentset");
|
||||
next();
|
||||
});
|
||||
},
|
||||
|
||||
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");
|
||||
PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
|
||||
PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
|
||||
info("servicesInstall-notification panel opened");
|
||||
panel.button.click();
|
||||
})
|
||||
|
||||
let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
|
||||
let id = "social-mark-button-" + manifest.origin;
|
||||
let toolbar = document.getElementById("nav-bar");
|
||||
addTab(activationURL, function(tab) {
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
Social.installProvider(doc, manifest, function(addonManifest) {
|
||||
|
||||
waitForCondition(function() {
|
||||
let currentset = toolbar.getAttribute("currentset").split(',');
|
||||
return currentset.indexOf(id) >= 0;
|
||||
},
|
||||
function() {
|
||||
// enable the provider so we know the button would have appeared
|
||||
SocialService.addBuiltinProvider(manifest.origin, function(provider) {
|
||||
waitForCondition(function() { return document.getElementById(id) },
|
||||
function() {
|
||||
gBrowser.removeTab(tab);
|
||||
installed.push(manifest.origin);
|
||||
// checkSocialUI will properly check where the menus are located
|
||||
checkSocialUI(window);
|
||||
executeSoon(function() {
|
||||
addProviders(callback);
|
||||
});
|
||||
}, "button exists after enabling social");
|
||||
});
|
||||
}, "mark button added to currentset");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
BIN
browser/base/content/test/social/checked.jpg
Normal file
BIN
browser/base/content/test/social/checked.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 785 B |
@ -247,6 +247,42 @@ function checkSocialUI(win) {
|
||||
if (provider) {
|
||||
for (let id of ["menu_socialSidebar", "menu_socialAmbientMenu"])
|
||||
_is(document.getElementById(id).getAttribute("label"), Social.provider.name, "element has the provider name");
|
||||
|
||||
let contextMenus = [
|
||||
{
|
||||
type: "link",
|
||||
id: "context-marklinkMenu",
|
||||
label: "social.marklink.label"
|
||||
},
|
||||
{
|
||||
type: "page",
|
||||
id: "context-markpageMenu",
|
||||
label: "social.markpage.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.
|
||||
|
49
browser/base/content/test/social/social_mark.html
Normal file
49
browser/base/content/test/social/social_mark.html
Normal file
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link id="siteicon" rel="icon" href="./icon.png"/>
|
||||
<title>Demo Mark Window</title>
|
||||
<script type="text/javascript">
|
||||
window.addEventListener("socialFrameShow", function(e) {
|
||||
var port = navigator.mozSocial.getWorker().port;
|
||||
port.postMessage({topic: "status-panel-visibility", result: "shown"});
|
||||
}, false);
|
||||
window.addEventListener("socialFrameHide", function(e) {
|
||||
var port = navigator.mozSocial.getWorker().port;
|
||||
port.postMessage({topic: "status-panel-visibility", result: "hidden"});
|
||||
}, false);
|
||||
|
||||
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>
|
Binary file not shown.
Before Width: | Height: | Size: 934 B |
BIN
browser/base/content/test/social/unchecked.jpg
Normal file
BIN
browser/base/content/test/social/unchecked.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 779 B |
@ -121,6 +121,7 @@ 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)
|
||||
|
@ -645,6 +645,11 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
||||
<!ENTITY social.chatBar.label "Focus chats">
|
||||
<!ENTITY social.chatBar.accesskey "c">
|
||||
|
||||
<!ENTITY social.markpage.accesskey "P">
|
||||
<!ENTITY social.markpageMenu.label "Save Page To…">
|
||||
<!ENTITY social.marklink.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:">
|
||||
|
@ -429,6 +429,11 @@ social.turnOff.accesskey=T
|
||||
social.turnOn.label=Turn on %S
|
||||
social.turnOn.accesskey=T
|
||||
|
||||
# LOCALIZATION NOTE (social.markpage.label): %S is the name of the social provider
|
||||
social.markpage.label=Save Page to %S
|
||||
# LOCALIZATION NOTE (social.marklink.label): %S is the name of the social provider
|
||||
social.marklink.label=Save Link to %S
|
||||
|
||||
# LOCALIZATION NOTE (social.error.message): %1$S is brandShortName (e.g. Firefox), %2$S is the name of the social provider
|
||||
social.error.message=%1$S is unable to connect with %2$S right now.
|
||||
social.error.tryAgain.label=Try Again
|
||||
|
@ -4,12 +4,16 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Social", "OpenGraphBuilder"];
|
||||
this.EXPORTED_SYMBOLS = ["Social", "OpenGraphBuilder", "DynamicResizeWatcher", "sizeSocialPanelToContent"];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cu = Components.utils;
|
||||
|
||||
// The minimum sizes for the auto-resize panel code.
|
||||
const PANEL_MIN_HEIGHT = 100;
|
||||
const PANEL_MIN_WIDTH = 330;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -303,45 +307,25 @@ this.Social = {
|
||||
},
|
||||
|
||||
// Page Marking functionality
|
||||
_getMarkablePageUrl: function Social_getMarkablePageUrl(aURI) {
|
||||
let uri = aURI.clone();
|
||||
try {
|
||||
// Setting userPass on about:config throws.
|
||||
uri.userPass = "";
|
||||
} catch (e) {}
|
||||
return uri.spec;
|
||||
},
|
||||
|
||||
isURIMarked: function(aURI, aCallback) {
|
||||
isURIMarked: function(origin, aURI, aCallback) {
|
||||
promiseGetAnnotation(aURI).then(function(val) {
|
||||
if (val) {
|
||||
let providerList = JSON.parse(val);
|
||||
val = providerList.indexOf(this.provider.origin) >= 0;
|
||||
val = providerList.indexOf(origin) >= 0;
|
||||
}
|
||||
aCallback(!!val);
|
||||
}.bind(this));
|
||||
}).then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
markURI: function(aURI, aCallback) {
|
||||
// this should not be called if this.provider or the port is null
|
||||
if (!this.provider) {
|
||||
Cu.reportError("Can't mark a page when no provider is current");
|
||||
return;
|
||||
}
|
||||
let port = this.provider.getWorkerPort();
|
||||
if (!port) {
|
||||
Cu.reportError("Can't mark page as no provider port is available");
|
||||
return;
|
||||
}
|
||||
|
||||
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(this.provider.origin) >= 0;
|
||||
let marked = providerList.indexOf(origin) >= 0;
|
||||
if (marked)
|
||||
return;
|
||||
providerList.push(this.provider.origin);
|
||||
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 = {
|
||||
@ -355,52 +339,30 @@ this.Social = {
|
||||
handleError: function () Cu.reportError("couldn't update history for socialmark annotation"),
|
||||
handleResult: function () {},
|
||||
handleCompletion: function () {
|
||||
promiseSetAnnotation(aURI, providerList).then();
|
||||
// post to the provider
|
||||
let url = this._getMarkablePageUrl(aURI);
|
||||
port.postMessage({
|
||||
topic: "social.page-mark",
|
||||
data: { url: url, 'marked': true }
|
||||
});
|
||||
port.close();
|
||||
promiseSetAnnotation(aURI, providerList).then(function() {
|
||||
if (aCallback)
|
||||
schedule(function() { aCallback(true); } );
|
||||
}.bind(this)
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
}).then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
unmarkURI: function(aURI, aCallback) {
|
||||
unmarkURI: function(origin, aURI, aCallback) {
|
||||
// this should not be called if this.provider or the port is null
|
||||
if (!this.provider) {
|
||||
Cu.reportError("Can't mark a page when no provider is current");
|
||||
return;
|
||||
}
|
||||
let port = this.provider.getWorkerPort();
|
||||
if (!port) {
|
||||
Cu.reportError("Can't mark page as no provider port is available");
|
||||
return;
|
||||
}
|
||||
|
||||
// set our annotation
|
||||
promiseGetAnnotation(aURI).then(function(val) {
|
||||
let providerList = val ? JSON.parse(val) : [];
|
||||
let marked = providerList.indexOf(this.provider.origin) >= 0;
|
||||
let marked = providerList.indexOf(origin) >= 0;
|
||||
if (marked) {
|
||||
// remove the annotation
|
||||
providerList.splice(providerList.indexOf(this.provider.origin), 1);
|
||||
promiseSetAnnotation(aURI, providerList).then();
|
||||
}
|
||||
// post to the provider regardless
|
||||
let url = this._getMarkablePageUrl(aURI);
|
||||
port.postMessage({
|
||||
topic: "social.page-mark",
|
||||
data: { url: url, 'marked': false }
|
||||
});
|
||||
port.close();
|
||||
providerList.splice(providerList.indexOf(origin), 1);
|
||||
promiseSetAnnotation(aURI, providerList).then(function() {
|
||||
if (aCallback)
|
||||
schedule(function() { aCallback(false); } );
|
||||
}.bind(this));
|
||||
}).then(null, Cu.reportError);
|
||||
}
|
||||
}).then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
setErrorListener: function(iframe, errorHandler) {
|
||||
@ -481,7 +443,108 @@ SocialErrorListener.prototype = {
|
||||
};
|
||||
|
||||
|
||||
function sizeSocialPanelToContent(panel, iframe) {
|
||||
let doc = iframe.contentDocument;
|
||||
if (!doc || !doc.body) {
|
||||
return;
|
||||
}
|
||||
// We need an element to use for sizing our panel. See if the body defines
|
||||
// an id for that element, otherwise use the body itself.
|
||||
let body = doc.body;
|
||||
let bodyId = body.getAttribute("contentid");
|
||||
if (bodyId) {
|
||||
body = doc.getElementById(bodyId) || doc.body;
|
||||
}
|
||||
// offsetHeight/Width don't include margins, so account for that.
|
||||
let cs = doc.defaultView.getComputedStyle(body);
|
||||
let width = PANEL_MIN_WIDTH;
|
||||
let height = PANEL_MIN_HEIGHT;
|
||||
// if the panel is preloaded prior to being shown, cs will be null. in that
|
||||
// case use the minimum size for the panel until it is shown.
|
||||
if (cs) {
|
||||
let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
|
||||
height = Math.max(computedHeight, height);
|
||||
let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
|
||||
width = Math.max(computedWidth, width);
|
||||
}
|
||||
iframe.style.width = width + "px";
|
||||
iframe.style.height = height + "px";
|
||||
// since we do not use panel.sizeTo, we need to adjust the arrow ourselves
|
||||
if (panel.state == "open")
|
||||
panel.adjustArrowPosition();
|
||||
}
|
||||
|
||||
function DynamicResizeWatcher() {
|
||||
this._mutationObserver = null;
|
||||
}
|
||||
|
||||
DynamicResizeWatcher.prototype = {
|
||||
start: function DynamicResizeWatcher_start(panel, iframe) {
|
||||
this.stop(); // just in case...
|
||||
let doc = iframe.contentDocument;
|
||||
this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) {
|
||||
sizeSocialPanelToContent(panel, iframe);
|
||||
});
|
||||
// Observe anything that causes the size to change.
|
||||
let config = {attributes: true, characterData: true, childList: true, subtree: true};
|
||||
this._mutationObserver.observe(doc, config);
|
||||
// and since this may be setup after the load event has fired we do an
|
||||
// initial resize now.
|
||||
sizeSocialPanelToContent(panel, iframe);
|
||||
},
|
||||
stop: function DynamicResizeWatcher_stop() {
|
||||
if (this._mutationObserver) {
|
||||
try {
|
||||
this._mutationObserver.disconnect();
|
||||
} catch (ex) {
|
||||
// may get "TypeError: can't access dead object" which seems strange,
|
||||
// but doesn't seem to indicate a real problem, so ignore it...
|
||||
}
|
||||
this._mutationObserver = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.OpenGraphBuilder = {
|
||||
generateEndpointURL: function(URLTemplate, pageData) {
|
||||
// support for existing oexchange style endpoints by supporting their
|
||||
// querystring arguments. parse the query string template and do
|
||||
// replacements where necessary the query names may be different than ours,
|
||||
// so we could see u=%{url} or url=%{url}
|
||||
let [endpointURL, queryString] = URLTemplate.split("?");
|
||||
let query = {};
|
||||
if (queryString) {
|
||||
queryString.split('&').forEach(function (val) {
|
||||
let [name, value] = val.split('=');
|
||||
let p = /%\{(.+)\}/.exec(value);
|
||||
if (!p) {
|
||||
// preserve non-template query vars
|
||||
query[name] = value;
|
||||
} else if (pageData[p[1]]) {
|
||||
query[name] = pageData[p[1]];
|
||||
} else if (p[1] == "body") {
|
||||
// build a body for emailers
|
||||
let body = "";
|
||||
if (pageData.title)
|
||||
body += pageData.title + "\n\n";
|
||||
if (pageData.description)
|
||||
body += pageData.description + "\n\n";
|
||||
if (pageData.text)
|
||||
body += pageData.text + "\n\n";
|
||||
body += pageData.url;
|
||||
query["body"] = body;
|
||||
}
|
||||
});
|
||||
}
|
||||
var str = [];
|
||||
for (let p in query)
|
||||
str.push(p + "=" + encodeURIComponent(query[p]));
|
||||
if (str.length)
|
||||
endpointURL = endpointURL + "?" + str.join("&");
|
||||
return endpointURL;
|
||||
},
|
||||
|
||||
getData: function(browser) {
|
||||
let res = {
|
||||
url: this._validateURL(browser, browser.currentURI.spec),
|
||||
|
@ -230,10 +230,10 @@ function attachToWindow(provider, targetWindow) {
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
while (elt) {
|
||||
if (elt.nodeName == "panel") {
|
||||
if (elt.localName == "panel") {
|
||||
elt.hidePopup();
|
||||
break;
|
||||
} else if (elt.nodeName == "chatbox") {
|
||||
} else if (elt.localName == "chatbox") {
|
||||
elt.close();
|
||||
break;
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ this.SocialService = {
|
||||
},
|
||||
|
||||
_manifestFromData: function(type, data, principal) {
|
||||
let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL'];
|
||||
let sameOriginRequired = ['workerURL', 'sidebarURL', 'shareURL', 'statusURL', 'markURL'];
|
||||
|
||||
if (type == 'directory') {
|
||||
// directory provided manifests must have origin in manifest, use that
|
||||
@ -728,6 +728,9 @@ function SocialProvider(input) {
|
||||
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.origin = input.origin;
|
||||
let originUri = Services.io.newURI(input.origin, null, null);
|
||||
this.principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
|
||||
|
Loading…
Reference in New Issue
Block a user