Bug 1014332 activation support for share panel, r=jaws

This commit is contained in:
Shane Caraveo 2014-10-09 12:02:21 -07:00
Родитель eb7bee7d96
Коммит d44c10e1f6
5 изменённых файлов: 199 добавлений и 67 удалений

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

@ -179,7 +179,7 @@ SocialUI = {
// about:home or the share panel, we bypass the enable prompt. Any website
// activation, such as from the activations directory or a providers website
// will still get the prompt.
_activationEventHandler: function SocialUI_activationHandler(e, aBypassUserEnable=false) {
_activationEventHandler: function SocialUI_activationHandler(e, options={}) {
let targetDoc;
let node;
if (e.target instanceof HTMLDocument) {
@ -193,7 +193,9 @@ SocialUI = {
if (!(targetDoc instanceof HTMLDocument))
return;
if (!aBypassUserEnable && targetDoc.defaultView != content)
// The share panel iframe will not match "content" so it passes a bypass
// flag
if (!options.bypassContentCheck && targetDoc.defaultView != content)
return;
// If we are in PB mode, we silently do nothing (bug 829404 exists to
@ -240,12 +242,25 @@ SocialUI = {
// ensure correct state
SocialUI.onCustomizeEnd(window);
}
// make this new provider the selected provider. If the panel hasn't
// been opened, we need to make the frame first.
SocialShare._createFrame();
SocialShare.iframe.setAttribute('src', 'data:text/plain;charset=utf8,');
SocialShare.iframe.setAttribute('origin', provider.origin);
// get the right button selected
SocialShare.populateProviderMenu();
if (SocialShare.panel.state == "open") {
SocialShare.sharePage(provider.origin);
}
}
if (provider.postActivationURL) {
openUILinkIn(provider.postActivationURL, "tab");
// if activated from an open share panel, we load the landing page in
// a background tab
gBrowser.loadOneTab(provider.postActivationURL, {inBackground: SocialShare.panel.state == "open"});
}
});
}, aBypassUserEnable);
}, options);
},
showLearnMore: function() {
@ -502,8 +517,15 @@ SocialShare = {
return this.panel.lastChild;
},
_activationHandler: function(event) {
if (!Services.prefs.getBoolPref("social.share.activationPanelEnabled"))
return;
SocialUI._activationEventHandler(event, { bypassContentCheck: true, bypassInstallPanel: true });
},
uninit: function () {
if (this.iframe) {
this.iframe.removeEventListener("ActivateSocialFeature", this._activationHandler, true, true);
this.iframe.remove();
}
},
@ -522,6 +544,7 @@ SocialShare = {
iframe.setAttribute("disableglobalhistory", "true");
iframe.setAttribute("flex", "1");
panel.appendChild(iframe);
this.iframe.addEventListener("ActivateSocialFeature", this._activationHandler, true, true);
this.populateProviderMenu();
},

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

@ -11,6 +11,7 @@ support-files =
opengraph/shorturl_linkrel.html
microdata.html
share.html
share_activate.html
social_activate.html
social_activate_iframe.html
social_chat.html

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

@ -10,11 +10,55 @@ let manifest = { // normal provider
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
};
let activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html";
function waitForProviderEnabled(cb) {
Services.obs.addObserver(function providerSet(subject, topic, data) {
Services.obs.removeObserver(providerSet, "social:provider-enabled");
info("social:provider-enabled observer was notified");
cb();
}, "social:provider-enabled", false);
}
function sendActivationEvent(subframe, callback) {
// hack Social.lastEventReceived so we don't hit the "too many events" check.
Social.lastEventReceived = 0;
let doc = subframe.contentDocument;
// if our test has a frame, use it
let button = doc.getElementById("activation");
ok(!!button, "got the activation button");
EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
if (callback)
executeSoon(callback);
}
function waitForEvent(iframe, eventName, callback) {
iframe.addEventListener(eventName, function load() {
info("page load is " + iframe.contentDocument.location.href);
if (iframe.contentDocument.location.href != "data:text/plain;charset=utf8,") {
iframe.removeEventListener(eventName, load, true);
executeSoon(callback);
}
}, true);
}
function test() {
waitForExplicitFinish();
runSocialTests(tests);
Services.prefs.setCharPref("social.shareDirectory", activationPage);
registerCleanupFunction(function () {
Services.prefs.clearUserPref("social.directories");
Services.prefs.clearUserPref("social.shareDirectory");
Services.prefs.clearUserPref("social.share.activationPanelEnabled");
});
runSocialTests(tests, undefined, function(next) {
let shareButton = SocialShare.shareButton;
if (shareButton) {
CustomizableUI.removeWidgetFromArea("social-share-button", CustomizableUI.AREA_NAVBAR)
shareButton.remove();
}
ok(CustomizableUI.inDefaultState, "Should start in default state.");
next();
});
}
let corpus = [
@ -75,11 +119,10 @@ let corpus = [
function loadURLInTab(url, callback) {
info("Loading tab with "+url);
let tab = gBrowser.selectedTab = gBrowser.addTab(url);
tab.linkedBrowser.addEventListener("load", function listener() {
waitForEvent(tab.linkedBrowser, "load", () => {
is(tab.linkedBrowser.currentURI.spec, url, "tab loaded")
tab.linkedBrowser.removeEventListener("load", listener, true);
executeSoon(function() { callback(tab) });
}, true);
callback(tab)
});
}
function hasoptions(testOptions, options) {
@ -105,12 +148,17 @@ var tests = {
// starting on about:blank page, share should be visible but disabled when
// adding provider
is(gBrowser.contentDocument.location.href, "about:blank");
// initialize the button into the navbar
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
// ensure correct state
SocialUI.onCustomizeEnd(window);
SocialService.addProvider(manifest, function(provider) {
is(SocialUI.enabled, true, "SocialUI is enabled");
checkSocialUI();
// share should not be enabled since we only have about:blank page
let shareButton = SocialShare.shareButton;
is(shareButton.disabled, true, "share button is disabled");
// verify the attribute for proper css
is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled");
// button should be visible
@ -121,6 +169,11 @@ var tests = {
testShareEnabledOnActivation: function(next) {
// starting from *some* page, share should be visible and enabled when
// activating provider
// initialize the button into the navbar
CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR);
// ensure correct state
SocialUI.onCustomizeEnd(window);
let testData = corpus[0];
loadURLInTab(testData.url, function(tab) {
SocialService.addProvider(manifest, function(provider) {
@ -128,7 +181,6 @@ var tests = {
checkSocialUI();
// share should not be enabled since we only have about:blank page
let shareButton = SocialShare.shareButton;
is(shareButton.disabled, false, "share button is enabled");
// verify the attribute for proper css
ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
// button should be visible
@ -149,7 +201,7 @@ var tests = {
function runOneTest() {
loadURLInTab(testData.url, function(tab) {
testTab = tab;
SocialShare.sharePage();
SocialShare.sharePage(manifest.origin);
});
}
@ -241,5 +293,48 @@ var tests = {
SocialShare.sharePage(manifest.origin, null, target);
});
});
},
testSharePanelActivation: function(next) {
let testTab;
// cleared in the cleanup function
Services.prefs.setCharPref("social.directories", "https://example.com");
Services.prefs.setBoolPref("social.share.activationPanelEnabled", true);
// make the iframe so we can wait on the load
SocialShare._createFrame();
let iframe = SocialShare.iframe;
waitForEvent(iframe, "load", () => {
let subframe = iframe.contentDocument.getElementById("activation-frame");
waitForCondition(() => {
// sometimes the iframe is ready before the panel is open, we need to
// wait for both conditions
return SocialShare.panel.state == "open"
&& subframe.contentDocument
&& subframe.contentDocument.readyState == "complete";
}, () => {
is(subframe.contentDocument.location.href, activationPage, "activation page loaded");
waitForProviderEnabled(() => {
let provider = Social._getProviderFromOrigin(manifest.origin);
let port = provider.getWorkerPort();
ok(!!port, "got port");
port.onmessage = function (e) {
let topic = e.data.topic;
switch (topic) {
case "got-share-data-message":
ok(true, "share completed");
gBrowser.removeTab(testTab);
SocialService.uninstallProvider(manifest.origin, next);
break;
}
}
port.postMessage({topic: "test-init"});
});
sendActivationEvent(subframe);
}, "share panel did not open and load share page");
});
loadURLInTab(activationPage, function(tab) {
testTab = tab;
SocialShare.sharePage();
});
}
}

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

@ -0,0 +1,36 @@
<html>
<!-- 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/. -->
<head>
<title>Activation test</title>
</head>
<script>
var data = {
// currently required
"name": "Demo Social Service",
// browser_share.js serves this page from "https://example.com"
"origin": "https://example.com",
"iconURL": "chrome://branding/content/icon16.png",
"icon32URL": "chrome://branding/content/favicon32.png",
"icon64URL": "chrome://branding/content/icon64.png",
"workerURL": "/browser/browser/base/content/test/social/social_worker.js",
"shareURL": "/browser/browser/base/content/test/social/share.html"
}
function activate(node) {
node.setAttribute("data-service", JSON.stringify(data));
var event = new CustomEvent("ActivateSocialFeature");
node.dispatchEvent(event);
}
</script>
<body>
nothing to see here
<button id="activation" onclick="activate(this, true)">Activate the share provider</button>
</body>
</html>

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

@ -152,8 +152,9 @@ XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
function getOriginActivationType(origin) {
// if this is an about uri, treat it as a directory
let originUri = Services.io.newURI(origin, null, null);
if (originUri.scheme == "moz-safe-about") {
let URI = Services.io.newURI(origin, null, null);
let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(URI);
if (Services.scriptSecurityManager.isSystemPrincipal(principal) || origin == "moz-safe-about:home") {
return "internal";
}
@ -585,26 +586,24 @@ this.SocialService = {
action, [], options);
},
installProvider: function(aDOMDocument, data, installCallback, aBypassUserEnable=false) {
installProvider: function(aDOMDocument, data, installCallback, options={}) {
let manifest;
let installOrigin = aDOMDocument.nodePrincipal.origin;
if (data) {
let installType = getOriginActivationType(installOrigin);
// if we get data, we MUST have a valid manifest generated from the data
manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal);
if (!manifest)
throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href);
let installType = getOriginActivationType(installOrigin);
// if we get data, we MUST have a valid manifest generated from the data
manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal);
if (!manifest)
throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href);
let addon = new AddonWrapper(manifest);
if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
throw new Error("installProvider: provider with origin [" +
installOrigin + "] is blocklisted");
// manifestFromData call above will enforce correct origin. To support
// activation from about: uris, we need to be sure to use the updated
// origin on the manifest.
installOrigin = manifest.origin;
}
let addon = new AddonWrapper(manifest);
if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
throw new Error("installProvider: provider with origin [" +
installOrigin + "] is blocklisted");
// manifestFromData call above will enforce correct origin. To support
// activation from about: uris, we need to be sure to use the updated
// origin on the manifest.
installOrigin = manifest.origin;
let id = getAddonIDFromOrigin(installOrigin);
AddonManager.getAddonByID(id, function(aAddon) {
@ -613,7 +612,7 @@ this.SocialService = {
aAddon.userDisabled = false;
}
schedule(function () {
this._installProvider(aDOMDocument, manifest, aBypassUserEnable, aManifest => {
this._installProvider(aDOMDocument, manifest, options, aManifest => {
this._notifyProviderListeners("provider-installed", aManifest.origin);
installCallback(aManifest);
});
@ -621,43 +620,21 @@ this.SocialService = {
}.bind(this));
},
_installProvider: function(aDOMDocument, manifest, aBypassUserEnable, installCallback) {
let sourceURI = aDOMDocument.location.href;
let installOrigin = aDOMDocument.nodePrincipal.origin;
_installProvider: function(aDOMDocument, manifest, options, installCallback) {
if (!manifest)
throw new Error("Cannot install provider without manifest data");
let installType = getOriginActivationType(installOrigin);
let installer;
switch(installType) {
case "foreign":
if (!Services.prefs.getBoolPref("social.remote-install.enabled"))
throw new Error("Remote install of services is disabled");
if (!manifest)
throw new Error("Cannot install provider without manifest data");
let installType = getOriginActivationType(aDOMDocument.nodePrincipal.origin);
if (installType == "foreign" && !Services.prefs.getBoolPref("social.remote-install.enabled"))
throw new Error("Remote install of services is disabled");
installer = new AddonInstaller(sourceURI, manifest, installCallback);
this._showInstallNotification(aDOMDocument, installer);
break;
case "internal":
// double check here since "builtin" falls through this as well.
aBypassUserEnable = installType == "internal" && manifest.oneclick;
case "directory":
// a manifest is requried, and will have been vetted by reviewers. We
// also handle in-product installations without the verification step.
if (aBypassUserEnable) {
installer = new AddonInstaller(sourceURI, manifest, installCallback);
installer.install();
return;
}
// a manifest is required, we'll catch a missing manifest below.
if (!manifest)
throw new Error("Cannot install provider without manifest data");
installer = new AddonInstaller(sourceURI, manifest, installCallback);
this._showInstallNotification(aDOMDocument, installer);
break;
default:
throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n");
break;
}
let installer = new AddonInstaller(aDOMDocument.location.href, manifest, installCallback);
let bypassPanel = options.bypassInstallPanel ||
(installType == "internal" && manifest.oneclick);
if (bypassPanel)
installer.install();
else
this._showInstallNotification(aDOMDocument, installer);
},
createWrapper: function(manifest) {