diff options
Diffstat (limited to 'browser/base/content/test/social')
23 files changed, 1899 insertions, 0 deletions
diff --git a/browser/base/content/test/social/.eslintrc.js b/browser/base/content/test/social/.eslintrc.js new file mode 100644 index 000000000..7c8021192 --- /dev/null +++ b/browser/base/content/test/social/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/browser/base/content/test/social/blocklist.xml b/browser/base/content/test/social/blocklist.xml new file mode 100644 index 000000000..2e3665c36 --- /dev/null +++ b/browser/base/content/test/social/blocklist.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist"> + <emItems> + <emItem blockID="s1" id="test1.example.com@services.mozilla.org"></emItem> + </emItems> +</blocklist> diff --git a/browser/base/content/test/social/browser.ini b/browser/base/content/test/social/browser.ini new file mode 100644 index 000000000..91f931602 --- /dev/null +++ b/browser/base/content/test/social/browser.ini @@ -0,0 +1,23 @@ +[DEFAULT] +support-files = + blocklist.xml + head.js + opengraph/og_invalid_url.html + opengraph/opengraph.html + opengraph/shortlink_linkrel.html + opengraph/shorturl_link.html + opengraph/shorturl_linkrel.html + microformats.html + share.html + share_activate.html + social_activate.html + social_activate_basic.html + social_activate_iframe.html + social_postActivation.html + !/browser/base/content/test/plugins/blockNoPlugins.xml + +[browser_aboutHome_activation.js] +[browser_addons.js] +[browser_blocklist.js] +[browser_share.js] +[browser_social_activation.js] diff --git a/browser/base/content/test/social/browser_aboutHome_activation.js b/browser/base/content/test/social/browser_aboutHome_activation.js new file mode 100644 index 000000000..37cca53d2 --- /dev/null +++ b/browser/base/content/test/social/browser_aboutHome_activation.js @@ -0,0 +1,229 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils", + "resource:///modules/AboutHome.jsm"); + +var snippet = +' <script>' + +' var manifest = {' + +' "name": "Demo Social Service",' + +' "origin": "https://example.com",' + +' "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",' + +' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' + +' };' + +' function activateProvider(node) {' + +' node.setAttribute("data-service", JSON.stringify(manifest));' + +' var event = new CustomEvent("ActivateSocialFeature");' + +' node.dispatchEvent(event);' + +' }' + +' </script>' + +' <div id="activationSnippet" onclick="activateProvider(this)">' + +' <img src="chrome://branding/content/icon32.png"></img>' + +' </div>'; + +// enable one-click activation +var snippet2 = +' <script>' + +' var manifest = {' + +' "name": "Demo Social Service",' + +' "origin": "https://example.com",' + +' "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",' + +' "postActivationURL": "https://example.com/browser/browser/base/content/test/social/social_postActivation.html",' + +' "oneclick": true' + +' };' + +' function activateProvider(node) {' + +' node.setAttribute("data-service", JSON.stringify(manifest));' + +' var event = new CustomEvent("ActivateSocialFeature");' + +' node.dispatchEvent(event);' + +' }' + +' </script>' + +' <div id="activationSnippet" onclick="activateProvider(this)">' + +' <img src="chrome://branding/content/icon32.png"></img>' + +' </div>'; + +var gTests = [ + +{ + desc: "Test activation with enable panel", + snippet: snippet, + panel: true +}, + +{ + desc: "Test activation bypassing enable panel", + snippet: snippet2, + panel: false +} +]; + +function test() +{ + waitForExplicitFinish(); + requestLongerTimeout(2); + ignoreAllUncaughtExceptions(); + PopupNotifications.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + PopupNotifications.panel.removeAttribute("animate"); + }); + + Task.spawn(function* () { + for (let test of gTests) { + info(test.desc); + + // Create a tab to run the test. + let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank"); + + // Add an event handler to modify the snippets map once it's ready. + let snippetsPromise = promiseSetupSnippetsMap(tab, test.snippet); + + // Start loading about:home and wait for it to complete, snippets should be loaded + yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted"); + + yield snippetsPromise; + + // ensure our activation snippet is indeed available + yield ContentTask.spawn(tab.linkedBrowser, {}, function*(arg) { + ok(!!content.document.getElementById("snippets"), "Found snippets element"); + ok(!!content.document.getElementById("activationSnippet"), "The snippet is present."); + }); + + yield new Promise(resolve => { + activateProvider(tab, test.panel).then(() => { + checkSocialUI(); + SocialService.uninstallProvider("https://example.com", function () { + info("provider uninstalled"); + resolve(); + }); + }); + }); + + // activation opened a post-activation info tab, close it. + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + yield BrowserTestUtils.removeTab(tab); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); +} + +/** + * Starts a load in an existing tab and waits for it to finish (via some event). + * + * @param aTab + * The tab to load into. + * @param aUrl + * The url to load. + * @param aEvent + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + */ +function promiseTabLoadEvent(aTab, aURL, aEventType="load") +{ + return new Promise(resolve => { + info("Wait tab event: " + aEventType); + aTab.linkedBrowser.addEventListener(aEventType, function load(event) { + if (event.originalTarget != aTab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank") { + info("skipping spurious load event"); + return; + } + aTab.linkedBrowser.removeEventListener(aEventType, load, true); + info("Tab event received: " + aEventType); + resolve(); + }, true, true); + aTab.linkedBrowser.loadURI(aURL); + }); +} + +/** + * Cleans up snippets and ensures that by default we don't try to check for + * remote snippets since that may cause network bustage or slowness. + * + * @param aTab + * The tab containing about:home. + * @param aSetupFn + * The setup function to be run. + * @return {Promise} resolved when the snippets are ready. Gets the snippets map. + */ +function promiseSetupSnippetsMap(aTab, aSnippet) +{ + info("Waiting for snippets map"); + + return ContentTask.spawn(aTab.linkedBrowser, + {snippetsVersion: AboutHomeUtils.snippetsVersion, + snippet: aSnippet}, + function*(arg) { + return new Promise(resolve => { + addEventListener("AboutHomeLoadSnippets", function load(event) { + removeEventListener("AboutHomeLoadSnippets", load, true); + + let cw = content.window.wrappedJSObject; + + // The snippets should already be ready by this point. Here we're + // just obtaining a reference to the snippets map. + cw.ensureSnippetsMapThen(function (aSnippetsMap) { + aSnippetsMap = Cu.waiveXrays(aSnippetsMap); + console.log("Got snippets map: " + + "{ last-update: " + aSnippetsMap.get("snippets-last-update") + + ", cached-version: " + aSnippetsMap.get("snippets-cached-version") + + " }"); + // Don't try to update. + aSnippetsMap.set("snippets-last-update", Date.now()); + aSnippetsMap.set("snippets-cached-version", arg.snippetsVersion); + // Clear snippets. + aSnippetsMap.delete("snippets"); + aSnippetsMap.set("snippets", arg.snippet); + resolve(); + }); + }, true, true); + }); + }); +} + + +function sendActivationEvent(tab) { + // hack Social.lastEventReceived so we don't hit the "too many events" check. + Social.lastEventReceived = 0; + let doc = tab.linkedBrowser.contentDocument; + // if our test has a frame, use it + if (doc.defaultView.frames[0]) + doc = doc.defaultView.frames[0].document; + let button = doc.getElementById("activationSnippet"); + BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser); +} + +function activateProvider(tab, expectPanel, aCallback) { + return new Promise(resolve => { + if (expectPanel) { + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => { + let panel = document.getElementById("servicesInstall-notification"); + panel.button.click(); + }); + } + waitForProviderLoad().then(() => { + checkSocialUI(); + resolve(); + }); + sendActivationEvent(tab); + }); +} + +function waitForProviderLoad(cb) { + return Promise.all([ + promiseObserverNotified("social:provider-enabled"), + ensureFrameLoaded(gBrowser, "https://example.com/browser/browser/base/content/test/social/social_postActivation.html"), + ]); +} diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js new file mode 100644 index 000000000..5a75d1d67 --- /dev/null +++ b/browser/base/content/test/social/browser_addons.js @@ -0,0 +1,217 @@ +var AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager; +var SocialService = Cu.import("resource:///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", + 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", + 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", + iconURL: "https://test2.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"); + }); + + let prefname = getManifestPrefname(manifest); + // ensure that manifest2 is NOT showing as builtin + is(SocialService.getOriginActivationType(manifest.origin), "foreign", "manifest is foreign"); + is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is foreign"); + + Services.prefs.setBoolPref("social.remote-install.enabled", true); + runSocialTests(tests, undefined, undefined, function () { + Services.prefs.clearUserPref("social.remote-install.enabled"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + // just in case the tests failed, clear these here as well + Services.prefs.clearUserPref("social.directories"); + finish(); + }); +} + +function installListener(next, aManifest) { + let expectEvent = "onInstalling"; + let prefname = getManifestPrefname(aManifest); + // wait for the actual removal to call next + SocialService.registerProviderListener(function providerListener(topic, origin, providers) { + if (topic == "provider-disabled") { + SocialService.unregisterProviderListener(providerListener); + is(origin, aManifest.origin, "provider disabled"); + executeSoon(next); + } + }); + + return { + onInstalling: function(addon) { + is(expectEvent, "onInstalling", "install started"); + is(addon.manifest.origin, aManifest.origin, "provider about to be installed"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + expectEvent = "onInstalled"; + }, + onInstalled: function(addon) { + is(addon.manifest.origin, aManifest.origin, "provider installed"); + ok(addon.installDate.getTime() > 0, "addon has installDate"); + ok(addon.updateDate.getTime() > 0, "addon has updateDate"); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + expectEvent = "onUninstalling"; + }, + onUninstalling: function(addon) { + is(expectEvent, "onUninstalling", "uninstall started"); + is(addon.manifest.origin, aManifest.origin, "provider about to be uninstalled"); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + expectEvent = "onUninstalled"; + }, + onUninstalled: function(addon) { + is(expectEvent, "onUninstalled", "provider has been uninstalled"); + is(addon.manifest.origin, aManifest.origin, "provider uninstalled"); + ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs"); + AddonManager.removeAddonListener(this); + } + }; +} + +var tests = { + testHTTPInstallFailure: function(next) { + let installFrom = "http://example.com"; + is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install"); + let data = { + origin: installFrom, + url: installFrom+"/activate", + manifest: manifest, + window: window + } + Social.installProvider(data, function(addonManifest) { + ok(!addonManifest, "unable to install provider over http"); + next(); + }); + }, + testAddonEnableToggle: function(next) { + let expectEvent; + let prefname = getManifestPrefname(manifest); + let listener = { + onEnabled: function(addon) { + is(expectEvent, "onEnabled", "provider onEnabled"); + ok(!addon.userDisabled, "provider enabled"); + executeSoon(function() { + expectEvent = "onDisabling"; + addon.userDisabled = true; + }); + }, + onEnabling: function(addon) { + is(expectEvent, "onEnabling", "provider onEnabling"); + expectEvent = "onEnabled"; + }, + onDisabled: function(addon) { + is(expectEvent, "onDisabled", "provider onDisabled"); + ok(addon.userDisabled, "provider disabled"); + AddonManager.removeAddonListener(listener); + // clear the provider user-level pref + Services.prefs.clearUserPref(prefname); + executeSoon(next); + }, + onDisabling: function(addon) { + is(expectEvent, "onDisabling", "provider onDisabling"); + expectEvent = "onDisabled"; + } + }; + AddonManager.addAddonListener(listener); + + // we're only testing enable disable, so we quickly set the user-level pref + // for this provider and test enable/disable toggling + setManifestPref(prefname, manifest); + ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs"); + AddonManager.getAddonsByTypes(["service"], function(addons) { + for (let addon of addons) { + if (addon.userDisabled) { + expectEvent = "onEnabling"; + addon.userDisabled = false; + // only test with one addon + return; + } + } + ok(false, "no addons toggled"); + next(); + }); + }, + testProviderEnableToggle: function(next) { + // enable and disabel a provider from the SocialService interface, check + // that the addon manager is updated + + let expectEvent; + let prefname = getManifestPrefname(manifest); + + let listener = { + onEnabled: function(addon) { + is(expectEvent, "onEnabled", "provider onEnabled"); + is(addon.manifest.origin, manifest.origin, "provider enabled"); + ok(!addon.userDisabled, "provider !userDisabled"); + }, + onEnabling: function(addon) { + is(expectEvent, "onEnabling", "provider onEnabling"); + is(addon.manifest.origin, manifest.origin, "provider about to be enabled"); + expectEvent = "onEnabled"; + }, + onDisabled: function(addon) { + is(expectEvent, "onDisabled", "provider onDisabled"); + is(addon.manifest.origin, manifest.origin, "provider disabled"); + ok(addon.userDisabled, "provider userDisabled"); + }, + onDisabling: function(addon) { + is(expectEvent, "onDisabling", "provider onDisabling"); + is(addon.manifest.origin, manifest.origin, "provider about to be disabled"); + expectEvent = "onDisabled"; + } + }; + AddonManager.addAddonListener(listener); + + expectEvent = "onEnabling"; + setManifestPref(prefname, manifest); + SocialService.enableProvider(manifest.origin, function(provider) { + expectEvent = "onDisabling"; + SocialService.disableProvider(provider.origin, function() { + AddonManager.removeAddonListener(listener); + Services.prefs.clearUserPref(prefname); + next(); + }); + }); + }, + testDirectoryInstall: function(next) { + AddonManager.addAddonListener(installListener(next, manifest2)); + + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => { + let panel = document.getElementById("servicesInstall-notification"); + info("servicesInstall-notification panel opened"); + panel.button.click(); + }); + + Services.prefs.setCharPref("social.directories", manifest2.origin); + is(SocialService.getOriginActivationType(manifest2.origin), "directory", "testing directory install"); + let data = { + origin: manifest2.origin, + url: manifest2.origin + "/directory", + manifest: manifest2, + window: window + } + Social.installProvider(data, function(addonManifest) { + Services.prefs.clearUserPref("social.directories"); + SocialService.enableProvider(addonManifest.origin, function(provider) { + Social.uninstallProvider(addonManifest.origin); + }); + }); + } +} diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js new file mode 100644 index 000000000..b67d5efb3 --- /dev/null +++ b/browser/base/content/test/social/browser_blocklist.js @@ -0,0 +1,211 @@ +/* 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/. */ + +// a place for miscellaneous social tests + +var SocialService = Cu.import("resource:///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"; + +var manifest = { // normal provider + name: "provider ok", + origin: "https://example.com", + shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.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", + iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png" +}; + +// blocklist testing +function updateBlocklist() { + var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] + .getService(Ci.nsITimerCallback); + let promise = promiseObserverNotified("blocklist-updated"); + blocklistNotifier.notify(null); + return promise; +} + +var _originalTestBlocklistURL = null; +function setAndUpdateBlocklist(aURL) { + if (!_originalTestBlocklistURL) + _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); + Services.prefs.setCharPref("extensions.blocklist.url", aURL); + return updateBlocklist(); +} + +function resetBlocklist() { + // XXX - this has "forked" from the head.js helpers in our parent directory :( + // But let's reuse their blockNoPlugins.xml. Later, we should arrange to + // use their head.js helpers directly + let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml"; + return new Promise(resolve => { + setAndUpdateBlocklist(noBlockedURL).then(() => { + Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); + resolve(); + }); + }); +} + +function test() { + waitForExplicitFinish(); + // turn on logging for nsBlocklistService.js + Services.prefs.setBoolPref("extensions.logging.enabled", true); + registerCleanupFunction(function () { + Services.prefs.clearUserPref("extensions.logging.enabled"); + }); + + runSocialTests(tests, undefined, undefined, function () { + resetBlocklist().then(finish); // restore to original pref + }); +} + +var tests = { + testSimpleBlocklist: function(next) { + // this really just tests adding and clearing our blocklist for later tests + setAndUpdateBlocklist(blocklistURL).then(() => { + ok(Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocking 'blocked'"); + ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest)), "not blocking 'good'"); + resetBlocklist().then(() => { + ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocklist cleared"); + next(); + }); + }); + }, + testAddingNonBlockedProvider: function(next) { + function finishTest(isgood) { + ok(isgood, "adding non-blocked provider ok"); + Services.prefs.clearUserPref("social.manifest.good"); + resetBlocklist().then(next); + } + setManifestPref("social.manifest.good", manifest); + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + SocialService.addProvider(manifest, function(provider) { + try { + SocialService.disableProvider(provider.origin, function() { + ok(true, "added and removed provider"); + finishTest(true); + }); + } catch (e) { + ok(false, "SocialService.disableProvider threw exception: " + e); + finishTest(false); + } + }); + } catch (e) { + ok(false, "SocialService.addProvider threw exception: " + e); + finishTest(false); + } + }); + }, + testAddingBlockedProvider: function(next) { + function finishTest(good) { + ok(good, "Unable to add blocklisted provider"); + Services.prefs.clearUserPref("social.manifest.blocked"); + resetBlocklist().then(next); + } + setManifestPref("social.manifest.blocked", manifest_bad); + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + SocialService.addProvider(manifest_bad, function(provider) { + SocialService.disableProvider(provider.origin, function() { + ok(false, "SocialService.addProvider should throw blocklist exception"); + finishTest(false); + }); + }); + } catch (e) { + ok(true, "SocialService.addProvider should throw blocklist exception: " + e); + finishTest(true); + } + }); + }, + testInstallingBlockedProvider: function(next) { + function finishTest(good) { + ok(good, "Unable to install blocklisted provider"); + resetBlocklist().then(next); + } + let activationURL = manifest_bad.origin + "/browser/browser/base/content/test/social/social_activate.html" + setAndUpdateBlocklist(blocklistURL).then(() => { + try { + // expecting an exception when attempting to install a hard blocked + // provider + let data = { + origin: manifest_bad.origin, + url: activationURL, + manifest: manifest_bad, + window: window + } + Social.installProvider(data, function(addonManifest) { + finishTest(false); + }); + } catch (e) { + finishTest(true); + } + }); + }, + testBlockingExistingProvider: function(next) { + let listener = { + _window: null, + onOpenWindow: function(aXULWindow) { + Services.wm.removeListener(this); + this._window = aXULWindow; + let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + + domwindow.addEventListener("load", function _load() { + domwindow.removeEventListener("load", _load, false); + + domwindow.addEventListener("unload", function _unload() { + domwindow.removeEventListener("unload", _unload, false); + info("blocklist window was closed"); + Services.wm.removeListener(listener); + next(); + }, false); + + is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused"); + // wait until after load to cancel so the dialog has initalized. we + // don't want to accept here since that restarts the browser. + executeSoon(() => { + let cancelButton = domwindow.document.documentElement.getButton("cancel"); + info("***** hit the cancel button\n"); + cancelButton.doCommand(); + }); + }, false); + }, + onCloseWindow: function(aXULWindow) { }, + onWindowTitleChange: function(aXULWindow, aNewTitle) { } + }; + + Services.wm.addListener(listener); + + setManifestPref("social.manifest.blocked", manifest_bad); + try { + SocialService.addProvider(manifest_bad, function(provider) { + // the act of blocking should cause a 'provider-disabled' notification + // from SocialService. + SocialService.registerProviderListener(function providerListener(topic, origin, providers) { + if (topic != "provider-disabled") + return; + SocialService.unregisterProviderListener(providerListener); + is(origin, provider.origin, "provider disabled"); + SocialService.getProvider(provider.origin, function(p) { + ok(p == null, "blocklisted provider disabled"); + Services.prefs.clearUserPref("social.manifest.blocked"); + resetBlocklist(); + }); + }); + // no callback - the act of updating should cause the listener above + // to fire. + setAndUpdateBlocklist(blocklistURL); + }); + } catch (e) { + ok(false, "unable to add provider " + e); + next(); + } + } +} diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js new file mode 100644 index 000000000..19dca519b --- /dev/null +++ b/browser/base/content/test/social/browser_share.js @@ -0,0 +1,396 @@ + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +var baseURL = "https://example.com/browser/browser/base/content/test/social/"; + +var manifest = { // normal provider + name: "provider 1", + origin: "https://example.com", + iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png", + shareURL: "https://example.com/browser/browser/base/content/test/social/share.html" +}; +var activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html"; + +function sendActivationEvent(subframe) { + // 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); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setCharPref("social.shareDirectory", activationPage); + + let frameScript = "data:,(" + function frame_script() { + addEventListener("OpenGraphData", function (aEvent) { + sendAsyncMessage("sharedata", aEvent.detail); + }, true, true); + /* bug 1042991, ensure history is available by calling history.back on close */ + addMessageListener("closeself", function(e) { + content.history.back(); + content.close(); + }, true); + /* if text is entered into field, onbeforeunload will cause a modal dialog + unless dialogs have been disabled for the iframe. */ + content.onbeforeunload = function(e) { + return 'FAIL.'; + }; + }.toString() + ")();"; + let mm = getGroupMessageManager("social"); + mm.loadFrameScript(frameScript, true); + + // Animation on the panel can cause intermittent failures such as bug 1115131. + SocialShare.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + SocialShare.panel.removeAttribute("animate"); + mm.removeDelayedFrameScript(frameScript); + 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(); + } + next(); + }); +} + +var corpus = [ + { + url: baseURL+"opengraph/opengraph.html", + options: { + // og:title + title: ">This is my title<", + // og:description + description: "A test corpus file for open graph tags we care about", + // medium: this.getPageMedium(), + // source: this.getSourceURL(), + // og:url + url: "https://www.mozilla.org/", + // shortUrl: this.getShortURL(), + // og:image + previews:["https://www.mozilla.org/favicon.png"], + // og:site_name + siteName: ">My simple test page<" + } + }, + { + // tests that og:url doesn't override the page url if it is bad + url: baseURL+"opengraph/og_invalid_url.html", + options: { + description: "A test corpus file for open graph tags passing a bad url", + url: baseURL+"opengraph/og_invalid_url.html", + previews: [], + siteName: "Evil chrome delivering website" + } + }, + { + url: baseURL+"opengraph/shorturl_link.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + }, + { + url: baseURL+"opengraph/shorturl_linkrel.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + }, + { + url: baseURL+"opengraph/shortlink_linkrel.html", + options: { + previews: ["http://example.com/1234/56789.jpg"], + url: "http://www.example.com/photos/56789/", + shortUrl: "http://imshort/p/abcde" + } + } +]; + +function hasoptions(testOptions, options) { + for (let option in testOptions) { + let data = testOptions[option]; + info("data: "+JSON.stringify(data)); + let message_data = options[option]; + info("message_data: "+JSON.stringify(message_data)); + if (Array.isArray(data)) { + // the message may have more array elements than we are testing for, this + // is ok since some of those are hard to test. So we just test that + // anything in our test data IS in the message. + ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option); + } else { + is(message_data, data, "option "+option); + } + } +} + +var tests = { + testShareDisabledOnActivation: function(next) { + // starting on about:blank page, share should be visible but disabled when + // adding provider + is(gBrowser.currentURI.spec, "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; + // verify the attribute for proper css + is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + SocialService.disableProvider(manifest.origin, next); + }); + }, + 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]; + BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => { + 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; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + BrowserTestUtils.removeTab(tab).then(next); + }); + }); + }, + testSharePage: function(next) { + let testTab; + let testIndex = 0; + let testData = corpus[testIndex++]; + + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + BrowserTestUtils.removeTab(testTab).then(() => { + hasoptions(testData.options, JSON.parse(msg.data)); + testData = corpus[testIndex++]; + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, "share panel closed").then(() => { + if (testData) { + runOneTest(); + } else { + mm.removeMessageListener("sharedata", handler); + SocialService.disableProvider(manifest.origin, next); + } + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + }); + + function runOneTest() { + BrowserTestUtils.openNewForegroundTab(gBrowser, testData.url).then(tab => { + testTab = tab; + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + SocialShare.sharePage(manifest.origin); + }); + } + executeSoon(runOneTest); + }, + testShareMicroformats: function(next) { + // 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) { + let target, testTab; + + let expecting = JSON.stringify({ + "url": "https://example.com/browser/browser/base/content/test/social/microformats.html", + "title": "Raspberry Pi Page", + "previews": ["https://example.com/someimage.jpg"], + "microformats": { + "items": [{ + "type": ["h-product"], + "properties": { + "name": ["Raspberry Pi"], + "photo": ["https://example.com/someimage.jpg"], + "description": [{ + "value": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.", + "html": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming." + } + ], + "url": ["https://example.com/"], + "price": ["29.95"], + "review": [{ + "value": "4.5 out of 5", + "type": ["h-review"], + "properties": { + "rating": ["4.5"] + } + } + ], + "category": ["Computer", "Education"] + } + } + ], + "rels": { + "tag": ["https://example.com/wiki/computer", "https://example.com/wiki/education"] + }, + "rel-urls": { + "https://example.com/wiki/computer": { + "text": "Computer", + "rels": ["tag"] + }, + "https://example.com/wiki/education": { + "text": "Education", + "rels": ["tag"] + } + } + } + }); + + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + is(msg.data, expecting, "microformats data ok"); + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, + "share panel closed").then(() => { + mm.removeMessageListener("sharedata", handler); + BrowserTestUtils.removeTab(testTab).then(() => { + SocialService.disableProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + + let url = "https://example.com/browser/browser/base/content/test/social/microformats.html" + BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => { + testTab = tab; + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + let doc = tab.linkedBrowser.contentDocument; + target = doc.getElementById("simple-hcard"); + 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; + + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + + ensureFrameLoaded(iframe).then(() => { + let subframe = iframe.contentDocument.getElementById("activation-frame"); + ensureFrameLoaded(subframe, activationPage).then(() => { + is(subframe.contentDocument.location.href, activationPage, "activation page loaded"); + promiseObserverNotified("social:provider-enabled").then(() => { + let mm = getGroupMessageManager("social"); + mm.addMessageListener("sharedata", function handler(msg) { + ok(true, "share completed"); + + BrowserTestUtils.waitForCondition(() => { return SocialShare.currentShare == null; }, + "share panel closed").then(() => { + BrowserTestUtils.removeTab(testTab).then(() => { + mm.removeMessageListener("sharedata", handler); + SocialService.uninstallProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + }); + sendActivationEvent(subframe); + }); + }); + BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => { + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + testTab = tab; + SocialShare.sharePage(); + }); + }, + testSharePanelDialog: function(next) { + let testTab; + // initialize the button into the navbar + CustomizableUI.addWidgetToArea("social-share-button", CustomizableUI.AREA_NAVBAR); + // ensure correct state + SocialUI.onCustomizeEnd(window); + SocialShare._createFrame(); + + SocialService.addProvider(manifest, () => { + BrowserTestUtils.openNewForegroundTab(gBrowser, activationPage).then(tab => { + ensureFrameLoaded(SocialShare.iframe).then(() => { + // send keys to the input field. An unexpected failure will happen + // if the onbeforeunload handler is fired. + EventUtils.sendKey("f"); + EventUtils.sendKey("a"); + EventUtils.sendKey("i"); + EventUtils.sendKey("l"); + + SocialShare.panel.addEventListener("popuphidden", function hidden(evt) { + SocialShare.panel.removeEventListener("popuphidden", hidden); + let topwin = Services.wm.getMostRecentWindow(null); + is(topwin, window, "no dialog is open"); + + BrowserTestUtils.removeTab(testTab).then(() => { + SocialService.disableProvider(manifest.origin, next); + }); + }); + SocialShare.iframe.messageManager.sendAsyncMessage("closeself", {}); + }); + + let shareButton = SocialShare.shareButton; + // verify the attribute for proper css + ok(!shareButton.hasAttribute("disabled"), "share button is enabled"); + // button should be visible + is(shareButton.hidden, false, "share button is visible"); + + testTab = tab; + SocialShare.sharePage(); + }); + }); + } +} diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js new file mode 100644 index 000000000..2af0d8021 --- /dev/null +++ b/browser/base/content/test/social/browser_social_activation.js @@ -0,0 +1,270 @@ +/* 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/. */ + +// +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejection should be fixed. +// +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null"); + + +var SocialService = Cu.import("resource:///modules/SocialService.jsm", {}).SocialService; + +var tabsToRemove = []; + +function removeProvider(provider) { + return new Promise(resolve => { + // a full install sets the manifest into a pref, addProvider alone doesn't, + // make sure we uninstall if the manifest was added. + if (provider.manifest) { + SocialService.uninstallProvider(provider.origin, resolve); + } else { + SocialService.disableProvider(provider.origin, resolve); + } + }); +} + +function postTestCleanup(callback) { + Task.spawn(function* () { + // any tabs opened by the test. + for (let tab of tabsToRemove) { + yield BrowserTestUtils.removeTab(tab); + } + tabsToRemove = []; + // all the providers may have been added. + while (Social.providers.length > 0) { + yield removeProvider(Social.providers[0]); + } + }).then(callback); +} + +function newTab(url) { + return new Promise(resolve => { + BrowserTestUtils.openNewForegroundTab(gBrowser, url).then(tab => { + tabsToRemove.push(tab); + resolve(tab); + }); + }); +} + +function sendActivationEvent(tab, callback, nullManifest) { + // hack Social.lastEventReceived so we don't hit the "too many events" check. + Social.lastEventReceived = 0; + BrowserTestUtils.synthesizeMouseAtCenter("#activation", {}, tab.linkedBrowser); + executeSoon(callback); +} + +function activateProvider(domain, callback, nullManifest) { + let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_basic.html" + newTab(activationURL).then(tab => { + sendActivationEvent(tab, callback, nullManifest); + }); +} + +function activateIFrameProvider(domain, callback) { + let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html" + newTab(activationURL).then(tab => { + sendActivationEvent(tab, callback, false); + }); +} + +function waitForProviderLoad(origin) { + return Promise.all([ + ensureFrameLoaded(gBrowser, origin + "/browser/browser/base/content/test/social/social_postActivation.html"), + ]); +} + +function getAddonItemInList(aId, aList) { + var item = aList.firstChild; + while (item) { + if ("mAddon" in item && item.mAddon.id == aId) { + aList.ensureElementIsVisible(item); + return item; + } + item = item.nextSibling; + } + return null; +} + +function clickAddonRemoveButton(tab, aCallback) { + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + let addon = aAddons[0]; + + let doc = tab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + + let item = getAddonItemInList(addon.id, list); + let button = item._removeBtn; + isnot(button, null, "Should have a remove button"); + ok(!button.disabled, "Button should not be disabled"); + + // uninstall happens after about:addons tab is closed, so we wait on + // disabled + promiseObserverNotified("social:provider-disabled").then(() => { + is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling"); + executeSoon(function() { aCallback(addon); }); + }); + + BrowserTestUtils.synthesizeMouseAtCenter(button, {}, tab.linkedBrowser); + }); +} + +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"); + if (finishActivation) + panel.button.click(); + else + panel.closebutton.click(); + }); + BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => { + ok(panel.hidden, "servicesInstall-notification panel hidden"); + if (!finishActivation) { + ok(panel.hidden, "activation panel is not showing"); + executeSoon(aCallback); + } else { + waitForProviderLoad(manifest.origin).then(() => { + checkSocialUI(); + executeSoon(aCallback); + }); + } + }); + + // the test will continue as the popup events fire... + activateProvider(manifest.origin, function() { + info("waiting on activation panel to open/close..."); + }); +} + +var gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"]; +var gProviders = [ + { + name: "provider 1", + origin: "https://example.com", + shareURL: "https://example.com/browser/browser/base/content/test/social/social_share.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", + 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", + iconURL: "chrome://branding/content/about-logo.png" + } +]; + + +function test() { + PopupNotifications.panel.setAttribute("animate", "false"); + registerCleanupFunction(function () { + PopupNotifications.panel.removeAttribute("animate"); + }); + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 1]]}, () => { + runSocialTests(tests, undefined, postTestCleanup); + }); +} + +var tests = { + testActivationWrongOrigin: function(next) { + // At this stage none of our providers exist, so we expect failure. + Services.prefs.setBoolPref("social.remote-install.enabled", false); + activateProvider(gTestDomains[0], function() { + is(SocialUI.enabled, false, "SocialUI is not enabled"); + let panel = document.getElementById("servicesInstall-notification"); + ok(panel.hidden, "activation panel still hidden"); + checkSocialUI(); + Services.prefs.clearUserPref("social.remote-install.enabled"); + next(); + }); + }, + + testIFrameActivation: function(next) { + activateIFrameProvider(gTestDomains[0], function() { + is(SocialUI.enabled, false, "SocialUI is not enabled"); + let panel = document.getElementById("servicesInstall-notification"); + ok(panel.hidden, "activation panel still hidden"); + checkSocialUI(); + next(); + }); + }, + + testActivationFirstProvider: function(next) { + // 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. + checkSocialUI(); + next(); + }); + }, + + testActivationMultipleProvider: function(next) { + // The trick with this test is to make sure that Social.providers[1] is + // the current provider when doing the undo - this makes sure that the + // Social code doesn't fallback to Social.providers[0], which it will + // do in some cases (but those cases do not include what this test does) + // first enable the 2 providers + SocialService.addProvider(gProviders[0], function() { + SocialService.addProvider(gProviders[1], function() { + checkSocialUI(); + // activate the last provider. + activateOneProvider(gProviders[2], false, function() { + // we deactivated - the first provider should be enabled. + checkSocialUI(); + next(); + }); + }); + }); + }, + + testAddonManagerDoubleInstall: function(next) { + // Create a new tab and load about:addons + let addonsTab = gBrowser.addTab(); + gBrowser.selectedTab = addonsTab; + BrowserOpenAddonsMgr('addons://list/service'); + gBrowser.selectedBrowser.addEventListener("load", function tabLoad() { + gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true); + is(addonsTab.linkedBrowser.currentURI.spec, "about:addons", "about:addons should load into blank tab."); + + activateOneProvider(gProviders[0], true, function() { + info("first activation completed"); + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_activate_basic.html", "activation page selected"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + tabsToRemove.pop(); + // uninstall the provider + clickAddonRemoveButton(addonsTab, function(addon) { + checkSocialUI(); + activateOneProvider(gProviders[0], true, function() { + info("second activation completed"); + is(gBrowser.contentDocument.location.href, gProviders[0].origin + "/browser/browser/base/content/test/social/social_postActivation.html", "postActivationURL loaded"); + BrowserTestUtils.removeTab(gBrowser.selectedTab).then(() => { + + // after closing the addons tab, verify provider is still installed + AddonManager.getAddonsByTypes(["service"], function(aAddons) { + is(aAddons.length, 1, "there can be only one"); + + let doc = addonsTab.linkedBrowser.contentDocument; + let list = doc.getElementById("addon-list"); + is(list.childNodes.length, 1, "only one addon is displayed"); + + BrowserTestUtils.removeTab(addonsTab).then(next); + }); + }); + }); + }); + }); + }); + }); + }, true); + } +} diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js new file mode 100644 index 000000000..ea175c97a --- /dev/null +++ b/browser/base/content/test/social/head.js @@ -0,0 +1,273 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); + + +function promiseObserverNotified(aTopic) { + return new Promise(resolve => { + Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) { + dump("notification promised "+aTopic); + Services.obs.removeObserver(onNotification, aTopic); + TestUtils.executeSoon(() => resolve({subject: aSubject, data: aData})); + }, aTopic, false); + }); +} + +// Check that a specified (string) URL hasn't been "remembered" (ie, is not +// in history, will not appear in about:newtab or auto-complete, etc.) +function promiseSocialUrlNotRemembered(url) { + return new Promise(resolve => { + let uri = Services.io.newURI(url, null, null); + PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) { + ok(!aIsVisited, "social URL " + url + " should not be in global history"); + resolve(); + }); + }); +} + +var gURLsNotRemembered = []; + + +function checkProviderPrefsEmpty(isError) { + let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); + let prefs = MANIFEST_PREFS.getChildList("", []); + let c = 0; + for (let pref of prefs) { + if (MANIFEST_PREFS.prefHasUserValue(pref)) { + info("provider [" + pref + "] manifest left installed from previous test"); + c++; + } + } + is(c, 0, "all provider prefs uninstalled from previous test"); + is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length); +} + +function defaultFinishChecks() { + checkProviderPrefsEmpty(true); + finish(); +} + +function runSocialTestWithProvider(manifest, callback, finishcallback) { + + let SocialService = Cu.import("resource:///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() { + for (let i = 0; i < manifests.length; i++) { + let m = manifests[i]; + for (let what of ['iconURL', 'shareURL']) { + if (m[what]) { + yield promiseSocialUrlNotRemembered(m[what]); + } + } + } + for (let i = 0; i < gURLsNotRemembered.length; i++) { + yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]); + } + gURLsNotRemembered = []; + } + + info("runSocialTestWithProvider: " + manifests.toSource()); + + let finishCount = 0; + function finishIfDone(callFinish) { + finishCount++; + if (finishCount == manifests.length) + Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks); + } + function removeAddedProviders(cleanup) { + manifests.forEach(function (m) { + // If we're "cleaning up", don't call finish when done. + let callback = cleanup ? function () {} : finishIfDone; + // Similarly, if we're cleaning up, catch exceptions from removeProvider + let removeProvider = SocialService.disableProvider.bind(SocialService); + if (cleanup) { + removeProvider = function (origin, cb) { + try { + SocialService.disableProvider(origin, cb); + } catch (ex) { + // Ignore "provider doesn't exist" errors. + if (ex.message.indexOf("SocialService.disableProvider: no provider with origin") == 0) + return; + info("Failed to clean up provider " + origin + ": " + ex); + } + } + } + removeProvider(m.origin, callback); + }); + } + function finishSocialTest(cleanup) { + removeAddedProviders(cleanup); + } + + let providersAdded = 0; + + manifests.forEach(function (m) { + SocialService.addProvider(m, function(provider) { + + providersAdded++; + info("runSocialTestWithProvider: provider added"); + + // we want to set the first specified provider as the UI's provider + if (provider.origin == manifests[0].origin) { + firstProvider = provider; + } + + // If we've added all the providers we need, call the callback to start + // the tests (and give it a callback it can call to finish them) + if (providersAdded == manifests.length) { + registerCleanupFunction(function () { + finishSocialTest(true); + }); + BrowserTestUtils.waitForCondition(() => provider.enabled, + "providers added and enabled").then(() => { + info("provider has been enabled"); + callback(finishSocialTest); + }); + } + }); + }); +} + +function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) { + let testIter = (function*() { + for (let name in tests) { + if (tests.hasOwnProperty(name)) { + yield [name, tests[name]]; + } + } + })(); + let providersAtStart = Social.providers.length; + info("runSocialTests: start test run with " + providersAtStart + " providers"); + window.focus(); + + + if (cbPreTest === undefined) { + cbPreTest = function(cb) { cb() }; + } + if (cbPostTest === undefined) { + cbPostTest = function(cb) { cb() }; + } + + function runNextTest() { + let result = testIter.next(); + if (result.done) { + // out of items: + (cbFinish || defaultFinishChecks)(); + is(providersAtStart, Social.providers.length, + "runSocialTests: finish test run with " + Social.providers.length + " providers"); + return; + } + let [name, func] = result.value; + // We run on a timeout to help keep the debug messages sane. + executeSoon(function() { + function cleanupAndRunNextTest() { + info("sub-test " + name + " complete"); + cbPostTest(runNextTest); + } + cbPreTest(function() { + info("pre-test: starting with " + Social.providers.length + " providers"); + info("sub-test " + name + " starting"); + try { + func.call(tests, cleanupAndRunNextTest); + } catch (ex) { + ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack); + cleanupAndRunNextTest(); + } + }) + }); + } + runNextTest(); +} + +// 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; + // if we have enabled providers, we should also have instances of those + // providers + if (SocialService.hasEnabledProviders) { + ok(Social.providers.length > 0, "providers are enabled"); + } else { + is(Social.providers.length, 0, "providers are not enabled"); + } +} + +function setManifestPref(name, manifest) { + let string = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + string.data = JSON.stringify(manifest); + Services.prefs.setComplexValue(name, Ci.nsISupportsString, string); +} + +function getManifestPrefname(aManifest) { + // is same as the generated name in SocialServiceInternal.getManifestPrefname + let originUri = Services.io.newURI(aManifest.origin, null, null); + return "social.manifest." + originUri.hostPort.replace('.', '-'); +} + +function ensureFrameLoaded(frame, uri) { + return new Promise(resolve => { + if (frame.contentDocument && frame.contentDocument.readyState == "complete" && + (!uri || frame.contentDocument.location.href == uri)) { + resolve(); + } else { + frame.addEventListener("load", function handler() { + if (uri && frame.contentDocument.location.href != uri) + return; + frame.removeEventListener("load", handler, true); + resolve() + }, true); + } + }); +} + +// Support for going on and offline. +// (via browser/base/content/test/browser_bookmark_titles.js) +var origProxyType = Services.prefs.getIntPref('network.proxy.type'); + +function toggleOfflineStatus(goOffline) { + // Bug 968887 fix. when going on/offline, wait for notification before continuing + return new Promise(resolve => { + if (!goOffline) { + Services.prefs.setIntPref('network.proxy.type', origProxyType); + } + if (goOffline != Services.io.offline) { + info("initial offline state " + Services.io.offline); + let expect = !Services.io.offline; + Services.obs.addObserver(function offlineChange(subject, topic, data) { + Services.obs.removeObserver(offlineChange, "network:offline-status-changed"); + info("offline state changed to " + Services.io.offline); + is(expect, Services.io.offline, "network:offline-status-changed successful toggle"); + resolve(); + }, "network:offline-status-changed", false); + BrowserOffline.toggleOfflineStatus(); + } else { + resolve(); + } + if (goOffline) { + Services.prefs.setIntPref('network.proxy.type', 0); + // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache. + Services.cache2.clear(); + } + }); +} + +function goOffline() { + // Simulate a network outage with offline mode. (Localhost is still + // accessible in offline mode, so disable the test proxy as well.) + return toggleOfflineStatus(true); +} + +function goOnline(callback) { + return toggleOfflineStatus(false); +} diff --git a/browser/base/content/test/social/microformats.html b/browser/base/content/test/social/microformats.html new file mode 100644 index 000000000..1a0e4436b --- /dev/null +++ b/browser/base/content/test/social/microformats.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + <body> + <head><title>Raspberry Pi Page</title></head> + <div class="hproduct"> + <h2 class="fn">Raspberry Pi</h2> + <img class="photo" src="https://example.com/someimage.jpg" /> + <p class="description">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p> + <a class="url" href="https://example.com/">More info about the Raspberry Pi</a> + <p class="price">29.95</p> + <p class="review hreview"><span id="test-review" class="rating">4.5</span> out of 5</p> + <p>Categories: + <a rel="tag" href="https://example.com/wiki/computer" class="category">Computer</a>, + <a rel="tag" href="https://example.com/wiki/education" class="category">Education</a> + </p> + </div> + </body> +</html> diff --git a/browser/base/content/test/social/moz.png b/browser/base/content/test/social/moz.png Binary files differnew file mode 100644 index 000000000..769c63634 --- /dev/null +++ b/browser/base/content/test/social/moz.png diff --git a/browser/base/content/test/social/opengraph/og_invalid_url.html b/browser/base/content/test/social/opengraph/og_invalid_url.html new file mode 100644 index 000000000..ad1dae2be --- /dev/null +++ b/browser/base/content/test/social/opengraph/og_invalid_url.html @@ -0,0 +1,11 @@ +<html xmlns:og="http://ogp.me/ns#"> +<head> + <meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/> + <meta property="og:site_name" content="Evil chrome delivering website"/> + <meta property="og:description" + content="A test corpus file for open graph tags passing a bad url"/> +</head> +<body> + Open Graph Test Page +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/opengraph.html b/browser/base/content/test/social/opengraph/opengraph.html new file mode 100644 index 000000000..50b7703b8 --- /dev/null +++ b/browser/base/content/test/social/opengraph/opengraph.html @@ -0,0 +1,13 @@ +<html xmlns:og="http://ogp.me/ns#"> +<head> + <meta property="og:title" content=">This is my title<"/> + <meta property="og:url" content="https://www.mozilla.org"/> + <meta property="og:image" content="https://www.mozilla.org/favicon.png"/> + <meta property="og:site_name" content=">My simple test page<"/> + <meta property="og:description" + content="A test corpus file for open graph tags we care about"/> +</head> +<body> + Open Graph Test Page +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shortlink_linkrel.html b/browser/base/content/test/social/opengraph/shortlink_linkrel.html new file mode 100644 index 000000000..54c40c376 --- /dev/null +++ b/browser/base/content/test/social/opengraph/shortlink_linkrel.html @@ -0,0 +1,10 @@ +<html> +<head> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link rel="shortlink" href="http://imshort/p/abcde" /> +</head> +<body> + link[rel='shortlink'] +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shorturl_link.html b/browser/base/content/test/social/opengraph/shorturl_link.html new file mode 100644 index 000000000..667122cea --- /dev/null +++ b/browser/base/content/test/social/opengraph/shorturl_link.html @@ -0,0 +1,10 @@ +<html> +<head> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" /> +</head> +<body> + link id="shorturl" +</body> +</html> diff --git a/browser/base/content/test/social/opengraph/shorturl_linkrel.html b/browser/base/content/test/social/opengraph/shorturl_linkrel.html new file mode 100644 index 000000000..36533528e --- /dev/null +++ b/browser/base/content/test/social/opengraph/shorturl_linkrel.html @@ -0,0 +1,25 @@ +<html> +<head> + <title>Test Image</title> + + <meta name="description" content="Iron man in a tutu" /> + <meta name="title" content="Test Image" /> + + <meta name="medium" content="image" /> + <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" /> + <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" /> + <link id="shorturl" href="http://imshort/p/abcde" /> + + <meta property="og:title" content="TestImage" /> + <meta property="og:type" content="photos:photo" /> + <meta property="og:url" content="http://www.example.com/photos/56789/" /> + <meta property="og:site_name" content="My Photo Site" /> + <meta property="og:description" content="Iron man in a tutu" /> + <meta property="og:image" content="http://example.com/1234/56789.jpg" /> + <meta property="og:image:width" content="480" /> + <meta property="og:image:height" content="640" /> +</head> +<body> + link[rel='shorturl'] +</body> +</html> diff --git a/browser/base/content/test/social/share.html b/browser/base/content/test/social/share.html new file mode 100644 index 000000000..55cba9844 --- /dev/null +++ b/browser/base/content/test/social/share.html @@ -0,0 +1,9 @@ +<html> + <head> + <meta charset="utf-8"> + </head> + <body onload="document.getElementById('testclose').focus()"> + <p>This is a test social share window.</p> + <input id="testclose"/> + </body> +</html> diff --git a/browser/base/content/test/social/share_activate.html b/browser/base/content/test/social/share_activate.html new file mode 100644 index 000000000..69707e705 --- /dev/null +++ b/browser/base/content/test/social/share_activate.html @@ -0,0 +1,35 @@ +<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", + "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> diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html new file mode 100644 index 000000000..78da597a1 --- /dev/null +++ b/browser/base/content/test/social/social_activate.html @@ -0,0 +1,41 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Activation test</title> +</head> +<script> +// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun +var data = { + // currently required + "name": "Demo Social Service", + "iconURL": "chrome://branding/content/icon16.png", + "icon32URL": "chrome://branding/content/favicon32.png", + "icon64URL": "chrome://branding/content/icon64.png", + + // at least one of these must be defined + "shareURL": "/browser/browser/base/content/test/social/social_share.html", + "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html", + + // should be available for display purposes + "description": "A short paragraph about this provider", + "author": "Shane Caraveo, Mozilla", + + // optional + "version": "1.0" +} + +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)">Activate The Demo Provider</button> + +</body> +</html> diff --git a/browser/base/content/test/social/social_activate_basic.html b/browser/base/content/test/social/social_activate_basic.html new file mode 100644 index 000000000..78da597a1 --- /dev/null +++ b/browser/base/content/test/social/social_activate_basic.html @@ -0,0 +1,41 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Activation test</title> +</head> +<script> +// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun +var data = { + // currently required + "name": "Demo Social Service", + "iconURL": "chrome://branding/content/icon16.png", + "icon32URL": "chrome://branding/content/favicon32.png", + "icon64URL": "chrome://branding/content/icon64.png", + + // at least one of these must be defined + "shareURL": "/browser/browser/base/content/test/social/social_share.html", + "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html", + + // should be available for display purposes + "description": "A short paragraph about this provider", + "author": "Shane Caraveo, Mozilla", + + // optional + "version": "1.0" +} + +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)">Activate The Demo Provider</button> + +</body> +</html> diff --git a/browser/base/content/test/social/social_activate_iframe.html b/browser/base/content/test/social/social_activate_iframe.html new file mode 100644 index 000000000..bde884c9d --- /dev/null +++ b/browser/base/content/test/social/social_activate_iframe.html @@ -0,0 +1,11 @@ +<html> +<head> + <title>Activation iframe test</title> +</head> + +<body> + +<iframe src="social_activate_basic.html"/> + +</body> +</html> diff --git a/browser/base/content/test/social/social_crash_content_helper.js b/browser/base/content/test/social/social_crash_content_helper.js new file mode 100644 index 000000000..4698b6957 --- /dev/null +++ b/browser/base/content/test/social/social_crash_content_helper.js @@ -0,0 +1,31 @@ +/* Any copyright is dedicated to the Public Domain. +* http://creativecommons.org/publicdomain/zero/1.0/ */ + +var Cu = Components.utils; + +// Ideally we would use CrashTestUtils.jsm, but that's only available for +// xpcshell tests - so we just copy a ctypes crasher from it. +Cu.import("resource://gre/modules/ctypes.jsm"); +var crash = function() { // this will crash when called. + let zero = new ctypes.intptr_t(8); + let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + badptr.contents +}; + + +var TestHelper = { + init: function() { + addMessageListener("social-test:crash", this); + }, + + receiveMessage: function(msg) { + switch (msg.name) { + case "social-test:crash": + privateNoteIntentionalCrash(); + crash(); + break; + } + }, +} + +TestHelper.init(); diff --git a/browser/base/content/test/social/social_postActivation.html b/browser/base/content/test/social/social_postActivation.html new file mode 100644 index 000000000..e0a6acfdf --- /dev/null +++ b/browser/base/content/test/social/social_postActivation.html @@ -0,0 +1,12 @@ +<html> +<head> + <meta charset="utf-8"> + <title>Post-Activation test</title> +</head> + +<body> + +Post Activation landing page + +</body> +</html> |