diff options
Diffstat (limited to '')
18 files changed, 1406 insertions, 0 deletions
diff --git a/browser/base/content/test/plugins/.eslintrc.js b/browser/base/content/test/plugins/.eslintrc.js new file mode 100644 index 0000000000..1779fd7f1c --- /dev/null +++ b/browser/base/content/test/plugins/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/browser-test"], +}; diff --git a/browser/base/content/test/plugins/BlocklistTestProxy.jsm b/browser/base/content/test/plugins/BlocklistTestProxy.jsm new file mode 100644 index 0000000000..cd13352f4a --- /dev/null +++ b/browser/base/content/test/plugins/BlocklistTestProxy.jsm @@ -0,0 +1,88 @@ +var EXPORTED_SYMBOLS = ["BlocklistTestProxyChild"]; + +var Cm = Components.manager; + +const kBlocklistServiceUUID = "{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"; +const kBlocklistServiceContractID = "@mozilla.org/extensions/blocklist;1"; + +let existingBlocklistFactory = null; +try { + existingBlocklistFactory = Cm.getClassObject( + Cc[kBlocklistServiceContractID], + Ci.nsIFactory + ); +} catch (ex) {} + +const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm"); + +/* + * A lightweight blocklist proxy for testing purposes. + */ +var BlocklistProxy = { + _uuid: null, + + QueryInterface: ChromeUtils.generateQI([ + "nsIObserver", + "nsIBlocklistService", + "nsITimerCallback", + ]), + + init() { + if (!this._uuid) { + this._uuid = Cc["@mozilla.org/uuid-generator;1"] + .getService(Ci.nsIUUIDGenerator) + .generateUUID(); + Cm.nsIComponentRegistrar.registerFactory( + this._uuid, + "", + "@mozilla.org/extensions/blocklist;1", + this + ); + } + }, + + uninit() { + if (this._uuid) { + Cm.nsIComponentRegistrar.unregisterFactory(this._uuid, this); + if (existingBlocklistFactory) { + Cm.nsIComponentRegistrar.registerFactory( + Components.ID(kBlocklistServiceUUID), + "Blocklist Service", + "@mozilla.org/extensions/blocklist;1", + existingBlocklistFactory + ); + } + this._uuid = null; + } + }, + + notify(aTimer) {}, + + async getAddonBlocklistState(aAddon, aAppVersion, aToolkitVersion) { + await new Promise(r => setTimeout(r, 150)); + return 0; // STATE_NOT_BLOCKED + }, + + async getPluginBlocklistState(aPluginTag, aAppVersion, aToolkitVersion) { + await new Promise(r => setTimeout(r, 150)); + return 0; // STATE_NOT_BLOCKED + }, + + async getPluginBlockURL(aPluginTag) { + await new Promise(r => setTimeout(r, 150)); + return ""; + }, +}; + +class BlocklistTestProxyChild extends JSProcessActorChild { + constructor() { + super(); + BlocklistProxy.init(); + } + + receiveMessage(message) { + if (message.name == "unload") { + BlocklistProxy.uninit(); + } + } +} diff --git a/browser/base/content/test/plugins/browser.ini b/browser/base/content/test/plugins/browser.ini new file mode 100644 index 0000000000..61898167f7 --- /dev/null +++ b/browser/base/content/test/plugins/browser.ini @@ -0,0 +1,25 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +support-files = + BlocklistTestProxy.jsm + empty_file.html + head.js + plugin_bug797677.html + plugin_favorfallback.html + plugin_outsideScrollArea.html + plugin_simple_blank.swf + plugin_test.html + plugin_zoom.html + +[browser_bug797677.js] +[browser_CTP_favorfallback.js] +[browser_CTP_outsideScrollArea.js] +tags = blocklist +[browser_CTP_zoom.js] +tags = blocklist +[browser_enable_DRM_prompt.js] +skip-if = (os == 'win' && processor == 'aarch64') # bug 1533164 +[browser_private_browsing_eme_persistent_state.js] +[browser_globalplugin_crashinfobar.js] +skip-if = !crashreporter diff --git a/browser/base/content/test/plugins/browser_CTP_favorfallback.js b/browser/base/content/test/plugins/browser_CTP_favorfallback.js new file mode 100644 index 0000000000..ba3af78f57 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_favorfallback.js @@ -0,0 +1,104 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); +var gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + +add_task(async function() { + registerCleanupFunction(function() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("plugins.favorfallback.mode"); + Services.prefs.clearUserPref("plugins.favorfallback.rules"); + }); +}); + +add_task(async function() { + Services.prefs.setCharPref("plugins.favorfallback.mode", "follow-ctp"); +}); + +/* The expected behavior of each testcase is documented with its markup + * in plugin_favorfallback.html. + * + * - "name" is the name of the testcase in the test file. + * - "rule" is how the plugins.favorfallback.rules must be configured + * for this testcase. + */ +const testcases = [ + { + name: "video", + rule: "video", + }, + + { + name: "nosrc", + rule: "nosrc", + }, + + { + name: "embed", + rule: "embed,true", + }, + + { + name: "adobelink", + rule: "adobelink,true", + }, + + { + name: "installinstructions", + rule: "installinstructions,true", + }, +]; + +add_task(async function() { + for (let testcase of Object.values(testcases)) { + info(`Running testcase ${testcase.name}`); + + Services.prefs.setCharPref("plugins.favorfallback.rules", testcase.rule); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + `${gTestRoot}plugin_favorfallback.html?testcase=${testcase.name}` + ); + + await SpecialPowers.spawn( + tab.linkedBrowser, + [testcase.name], + async function testPlugins(name) { + let testcaseDiv = content.document.getElementById(`testcase_${name}`); + let ctpPlugins = testcaseDiv.querySelectorAll(".expected_ctp"); + + for (let ctpPlugin of ctpPlugins) { + ok( + ctpPlugin instanceof Ci.nsIObjectLoadingContent, + "This is a plugin object" + ); + is( + ctpPlugin.pluginFallbackType, + Ci.nsIObjectLoadingContent.PLUGIN_ALTERNATE, + "Plugins always use alternate content" + ); + } + + let fallbackPlugins = testcaseDiv.querySelectorAll( + ".expected_fallback" + ); + + for (let fallbackPlugin of fallbackPlugins) { + ok( + fallbackPlugin instanceof Ci.nsIObjectLoadingContent, + "This is a plugin object" + ); + is( + fallbackPlugin.pluginFallbackType, + Ci.nsIObjectLoadingContent.PLUGIN_ALTERNATE, + "Plugin fallback content was used" + ); + } + } + ); + + BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js new file mode 100644 index 0000000000..ed68a27688 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_outsideScrollArea.js @@ -0,0 +1,122 @@ +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); +var gTestBrowser = null; +var gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + +add_task(async function() { + registerCleanupFunction(function() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(async function() { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + gTestBrowser = gBrowser.selectedBrowser; +}); + +// Test that the plugin "blockall" overlay is always present but hidden, +// regardless of whether the overlay is fully, partially, or not in the +// viewport. + +// fully in viewport +add_task(async function() { + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_outsideScrollArea.html" + ); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let doc = content.document; + let p = doc.createElement("embed"); + + p.setAttribute("id", "test"); + p.setAttribute("type", "application/x-shockwave-flash"); + p.style.left = "0"; + p.style.bottom = "200px"; + + doc.getElementById("container").appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + await promiseUpdatePluginBindings(gTestBrowser); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let plugin = content.document.getElementById("test"); + let overlay = plugin.openOrClosedShadowRoot.getElementById("main"); + Assert.ok(overlay); + Assert.ok(!overlay.getAttribute("visible")); + Assert.ok(overlay.getAttribute("blockall") == "blockall"); + }); +}); + +// partially in viewport +add_task(async function() { + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_outsideScrollArea.html" + ); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let doc = content.document; + let p = doc.createElement("embed"); + + p.setAttribute("id", "test"); + p.setAttribute("type", "application/x-shockwave-flash"); + p.style.left = "0"; + p.style.bottom = "-410px"; + + doc.getElementById("container").appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + await promiseUpdatePluginBindings(gTestBrowser); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let plugin = content.document.getElementById("test"); + let overlay = plugin.openOrClosedShadowRoot.getElementById("main"); + Assert.ok(overlay); + Assert.ok(!overlay.getAttribute("visible")); + Assert.ok(overlay.getAttribute("blockall") == "blockall"); + }); +}); + +// not in viewport +add_task(async function() { + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_outsideScrollArea.html" + ); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let doc = content.document; + let p = doc.createElement("embed"); + + p.setAttribute("id", "test"); + p.setAttribute("type", "application/x-shockwave-flash"); + p.style.left = "-600px"; + p.style.bottom = "0"; + + doc.getElementById("container").appendChild(p); + }); + + // Work around for delayed PluginBindingAttached + await promiseUpdatePluginBindings(gTestBrowser); + + await SpecialPowers.spawn(gTestBrowser, [], async function() { + let plugin = content.document.getElementById("test"); + let overlay = plugin.openOrClosedShadowRoot.getElementById("main"); + Assert.ok(overlay); + Assert.ok(!overlay.getAttribute("visible")); + Assert.ok(overlay.getAttribute("blockall") == "blockall"); + }); +}); diff --git a/browser/base/content/test/plugins/browser_CTP_zoom.js b/browser/base/content/test/plugins/browser_CTP_zoom.js new file mode 100644 index 0000000000..eee2ec1837 --- /dev/null +++ b/browser/base/content/test/plugins/browser_CTP_zoom.js @@ -0,0 +1,61 @@ +"use strict"; + +var rootDir = getRootDirectory(gTestPath); +const gTestRoot = rootDir.replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +var gTestBrowser = null; + +add_task(async function() { + registerCleanupFunction(function() { + clearAllPluginPermissions(); + Services.prefs.clearUserPref("extensions.blocklist.suppressUI"); + FullZoom.reset(); // must be called before closing the tab we zoomed! + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); +}); + +add_task(async function() { + Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + gTestBrowser = gBrowser.selectedBrowser; + + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_zoom.html" + ); + + // Work around for delayed PluginBindingAttached + await promiseUpdatePluginBindings(gTestBrowser); +}); + +// Enlarges the zoom level 4 times and tests that the overlay is +// visible after each enlargement. +add_task(async function() { + for (let count = 0; count < 4; count++) { + FullZoom.enlarge(); + + // Reload the page + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_zoom.html" + ); + await promiseUpdatePluginBindings(gTestBrowser); + await SpecialPowers.spawn(gTestBrowser, [{ count }], async function(args) { + let doc = content.document; + let plugin = doc.getElementById("test"); + let overlay = plugin.openOrClosedShadowRoot.getElementById("main"); + Assert.ok( + overlay && + !overlay.classList.contains("visible") && + overlay.getAttribute("blockall") == "blockall", + "Overlay should be present for zoom change count " + args.count + ); + }); + } +}); diff --git a/browser/base/content/test/plugins/browser_bug797677.js b/browser/base/content/test/plugins/browser_bug797677.js new file mode 100644 index 0000000000..4ba565a1a5 --- /dev/null +++ b/browser/base/content/test/plugins/browser_bug797677.js @@ -0,0 +1,45 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); +var gTestBrowser = null; +var gConsoleErrors = 0; + +add_task(async function() { + registerCleanupFunction(function() { + Services.console.unregisterListener(errorListener); + gBrowser.removeCurrentTab(); + window.focus(); + gTestBrowser = null; + }); + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + gTestBrowser = gBrowser.selectedBrowser; + + let errorListener = { + observe(aMessage) { + if (aMessage.message.includes("NS_ERROR_FAILURE")) { + gConsoleErrors++; + } + }, + }; + Services.console.registerListener(errorListener); + + await promiseTabLoadEvent( + gBrowser.selectedTab, + gTestRoot + "plugin_bug797677.html" + ); + + let pluginInfo = await promiseForPluginInfo("plugin"); + is( + pluginInfo.pluginFallbackType, + Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, + "plugin should not have been found." + ); + + await SpecialPowers.spawn(gTestBrowser, [], function() { + let plugin = content.document.getElementById("plugin"); + ok(plugin, "plugin should be in the page"); + }); + is(gConsoleErrors, 0, "should have no console errors"); +}); diff --git a/browser/base/content/test/plugins/browser_enable_DRM_prompt.js b/browser/base/content/test/plugins/browser_enable_DRM_prompt.js new file mode 100644 index 0000000000..be0c779657 --- /dev/null +++ b/browser/base/content/test/plugins/browser_enable_DRM_prompt.js @@ -0,0 +1,228 @@ +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "empty_file.html"; + +/* + * Register cleanup function to reset prefs after other tasks have run. + */ + +add_task(async function() { + // Note: SpecialPowers.pushPrefEnv has problems with the "Enable DRM" + // button on the notification box toggling the prefs. So manually + // set/unset the prefs the UI we're testing toggles. + let emeWasEnabled = Services.prefs.getBoolPref("media.eme.enabled", false); + let cdmWasEnabled = Services.prefs.getBoolPref( + "media.gmp-widevinecdm.enabled", + false + ); + + // Restore the preferences to their pre-test state on test finish. + registerCleanupFunction(function() { + // Unlock incase lock test threw and didn't unlock. + Services.prefs.unlockPref("media.eme.enabled"); + Services.prefs.setBoolPref("media.eme.enabled", emeWasEnabled); + Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", cdmWasEnabled); + }); +}); + +/* + * Bug 1366167 - Tests that the "Enable DRM" prompt shows if EME is requested while EME is disabled. + */ + +add_task(async function test_drm_prompt_shows_for_toplevel() { + await BrowserTestUtils.withNewTab(TEST_URL, async function(browser) { + // Turn off EME and Widevine CDM. + Services.prefs.setBoolPref("media.eme.enabled", false); + Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", false); + + // Have content request access to Widevine, UI should drop down to + // prompt user to enable DRM. + let result = await SpecialPowers.spawn(browser, [], async function() { + try { + let config = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + }, + ]; + await content.navigator.requestMediaKeySystemAccess( + "com.widevine.alpha", + config + ); + } catch (ex) { + return { rejected: true }; + } + return { rejected: false }; + }); + is( + result.rejected, + true, + "EME request should be denied because EME disabled." + ); + + // Verify the UI prompt showed. + let box = gBrowser.getNotificationBox(browser); + let notification = box.currentNotification; + + ok(notification, "Notification should be visible"); + is( + notification.getAttribute("value"), + "drmContentDisabled", + "Should be showing the right notification" + ); + + // Verify the "Enable DRM" button is there. + let buttons = notification.querySelectorAll(".notification-button"); + is(buttons.length, 1, "Should have one button."); + + // Prepare a Promise that should resolve when the "Enable DRM" button's + // page reload completes. + let refreshPromise = BrowserTestUtils.browserLoaded(browser); + buttons[0].click(); + + // Wait for the reload to complete. + await refreshPromise; + + // Verify clicking the "Enable DRM" button enabled DRM. + let enabled = Services.prefs.getBoolPref("media.eme.enabled", true); + is( + enabled, + true, + "EME should be enabled after click on 'Enable DRM' button" + ); + }); +}); + +add_task(async function test_eme_locked() { + await BrowserTestUtils.withNewTab(TEST_URL, async function(browser) { + // Turn off EME and Widevine CDM. + Services.prefs.setBoolPref("media.eme.enabled", false); + Services.prefs.lockPref("media.eme.enabled"); + + // Have content request access to Widevine, UI should drop down to + // prompt user to enable DRM. + let result = await SpecialPowers.spawn(browser, [], async function() { + try { + let config = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + }, + ]; + await content.navigator.requestMediaKeySystemAccess( + "com.widevine.alpha", + config + ); + } catch (ex) { + return { rejected: true }; + } + return { rejected: false }; + }); + is( + result.rejected, + true, + "EME request should be denied because EME disabled." + ); + + // Verify the UI prompt did not show. + let box = gBrowser.getNotificationBox(browser); + let notification = box.currentNotification; + + is( + notification, + null, + "Notification should not be displayed since pref is locked" + ); + + // Unlock the pref for any tests that follow. + Services.prefs.unlockPref("media.eme.enabled"); + }); +}); + +/* + * Bug 1642465 - Ensure cross origin frames requesting access prompt in the same way as same origin. + */ + +add_task(async function test_drm_prompt_shows_for_cross_origin_iframe() { + await BrowserTestUtils.withNewTab(TEST_URL, async function(browser) { + // Turn off EME and Widevine CDM. + Services.prefs.setBoolPref("media.eme.enabled", false); + Services.prefs.setBoolPref("media.gmp-widevinecdm.enabled", false); + + // Have content request access to Widevine, UI should drop down to + // prompt user to enable DRM. + const CROSS_ORIGIN_URL = TEST_URL.replace("example.com", "example.org"); + let result = await SpecialPowers.spawn( + browser, + [CROSS_ORIGIN_URL], + async function(crossOriginUrl) { + let frame = content.document.createElement("iframe"); + frame.src = crossOriginUrl; + await new Promise(resolve => { + frame.addEventListener("load", () => { + resolve(); + }); + content.document.body.appendChild(frame); + }); + + return content.SpecialPowers.spawn(frame, [], async function() { + try { + let config = [ + { + initDataTypes: ["webm"], + videoCapabilities: [ + { contentType: 'video/webm; codecs="vp9"' }, + ], + }, + ]; + await content.navigator.requestMediaKeySystemAccess( + "com.widevine.alpha", + config + ); + } catch (ex) { + return { rejected: true }; + } + return { rejected: false }; + }); + } + ); + is( + result.rejected, + true, + "EME request should be denied because EME disabled." + ); + + // Verify the UI prompt showed. + let box = gBrowser.getNotificationBox(browser); + let notification = box.currentNotification; + + ok(notification, "Notification should be visible"); + is( + notification.getAttribute("value"), + "drmContentDisabled", + "Should be showing the right notification" + ); + + // Verify the "Enable DRM" button is there. + let buttons = notification.querySelectorAll(".notification-button"); + is(buttons.length, 1, "Should have one button."); + + // Prepare a Promise that should resolve when the "Enable DRM" button's + // page reload completes. + let refreshPromise = BrowserTestUtils.browserLoaded(browser); + buttons[0].click(); + + // Wait for the reload to complete. + await refreshPromise; + + // Verify clicking the "Enable DRM" button enabled DRM. + let enabled = Services.prefs.getBoolPref("media.eme.enabled", true); + is( + enabled, + true, + "EME should be enabled after click on 'Enable DRM' button" + ); + }); +}); diff --git a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js new file mode 100644 index 0000000000..97cb9db618 --- /dev/null +++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js @@ -0,0 +1,63 @@ +"use strict"; + +let { PluginManager } = ChromeUtils.import( + "resource:///actors/PluginParent.jsm" +); + +/** + * Test that the notification bar for crashed GMPs works. + */ +add_task(async function() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function(browser) { + // Ensure the parent has heard before the client. + // In practice, this is always true for GMP crashes (but not for NPAPI ones!) + let props = Cc["@mozilla.org/hash-property-bag;1"].createInstance( + Ci.nsIWritablePropertyBag2 + ); + props.setPropertyAsUint32("pluginID", 1); + props.setPropertyAsACString("pluginName", "GlobalTestPlugin"); + props.setPropertyAsACString("pluginDumpID", "1234"); + Services.obs.notifyObservers(props, "gmp-plugin-crash"); + + await SpecialPowers.spawn(browser, [], async function() { + const GMP_CRASH_EVENT = { + pluginID: 1, + pluginName: "GlobalTestPlugin", + submittedCrashReport: false, + bubbles: true, + cancelable: true, + gmpPlugin: true, + }; + + let crashEvent = new content.PluginCrashedEvent( + "PluginCrashed", + GMP_CRASH_EVENT + ); + content.dispatchEvent(crashEvent); + }); + + let notification = await waitForNotificationBar( + "plugin-crashed", + browser + ); + + let notificationBox = gBrowser.getNotificationBox(browser); + ok(notification, "Infobar was shown."); + is( + notification.priority, + notificationBox.PRIORITY_WARNING_MEDIUM, + "Correct priority." + ); + is( + notification.messageText.textContent, + "The GlobalTestPlugin plugin has crashed.", + "Correct message." + ); + } + ); +}); diff --git a/browser/base/content/test/plugins/browser_private_browsing_eme_persistent_state.js b/browser/base/content/test/plugins/browser_private_browsing_eme_persistent_state.js new file mode 100644 index 0000000000..9a0b91119b --- /dev/null +++ b/browser/base/content/test/plugins/browser_private_browsing_eme_persistent_state.js @@ -0,0 +1,59 @@ +/* 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/. */ + +/* + * This test ensures that navigator.requestMediaKeySystemAccess() requests + * to run EME with persistent state are rejected in private browsing windows. + * Bug 1334111. + */ + +const TEST_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + "https://example.com" + ) + "empty_file.html"; + +async function isEmePersistentStateSupported(mode) { + let win = await BrowserTestUtils.openNewBrowserWindow(mode); + let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL); + let persistentStateSupported = await SpecialPowers.spawn( + tab.linkedBrowser, + [], + async function() { + try { + let config = [ + { + initDataTypes: ["webm"], + videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }], + persistentState: "required", + }, + ]; + await content.navigator.requestMediaKeySystemAccess( + "org.w3.clearkey", + config + ); + } catch (ex) { + return false; + } + return true; + } + ); + + await BrowserTestUtils.closeWindow(win); + + return persistentStateSupported; +} + +add_task(async function test() { + is( + await isEmePersistentStateSupported({ private: true }), + false, + "EME persistentState should *NOT* be supported in private browsing window." + ); + is( + await isEmePersistentStateSupported({ private: false }), + true, + "EME persistentState *SHOULD* be supported in non private browsing window." + ); +}); diff --git a/browser/base/content/test/plugins/empty_file.html b/browser/base/content/test/plugins/empty_file.html new file mode 100644 index 0000000000..af8440ac16 --- /dev/null +++ b/browser/base/content/test/plugins/empty_file.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + </head> + <body> + This page is intentionally left blank. + </body> +</html> diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js new file mode 100644 index 0000000000..d00bd4a446 --- /dev/null +++ b/browser/base/content/test/plugins/head.js @@ -0,0 +1,452 @@ +var { XPCOMUtils } = ChromeUtils.import( + "resource://gre/modules/XPCOMUtils.jsm" +); + +ChromeUtils.defineModuleGetter( + this, + "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm" +); +ChromeUtils.defineModuleGetter( + this, + "PromiseUtils", + "resource://gre/modules/PromiseUtils.jsm" +); + +XPCOMUtils.defineLazyServiceGetters(this, { + uuidGen: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"], +}); + +// Various tests in this directory may define gTestBrowser, to use as the +// default browser under test in some of the functions below. +/* global gTestBrowser:true */ + +/** + * Waits a specified number of miliseconds. + * + * Usage: + * let wait = yield waitForMs(2000); + * ok(wait, "2 seconds should now have elapsed"); + * + * @param aMs the number of miliseconds to wait for + * @returns a Promise that resolves to true after the time has elapsed + */ +function waitForMs(aMs) { + return new Promise(resolve => { + setTimeout(done, aMs); + function done() { + resolve(true); + } + }); +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param tab + * The tab to load into. + * @param [optional] url + * The url to load, or the current url. + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url) { + info("Wait tab event: load"); + + function handle(loadedUrl) { + if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { + info(`Skipping spurious load event for ${loadedUrl}`); + return false; + } + + info("Tab event received: load"); + return true; + } + + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); + + if (url) { + BrowserTestUtils.loadURI(tab.linkedBrowser, url); + } + + return loaded; +} + +function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) { + let tries = 0; + let maxTries = aTries || 100; // 100 tries + let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds + let interval = setInterval(function() { + if (tries >= maxTries) { + ok(false, errorMsg); + moveOn(); + } + let conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, maxWait); + let moveOn = function() { + clearInterval(interval); + nextTest(); + }; +} + +// Waits for a conditional function defined by the caller to return true. +function promiseForCondition(aConditionFn, aMessage, aTries, aWait) { + return new Promise(resolve => { + waitForCondition( + aConditionFn, + resolve, + aMessage || "Condition didn't pass.", + aTries, + aWait + ); + }); +} + +// Returns the chrome side nsIPluginTag for this plugin +function getTestPlugin(aName) { + let pluginName = aName || "Test Plug-in"; + let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); + let tags = ph.getPluginTags(); + + // Find the test plugin + for (let i = 0; i < tags.length; i++) { + if (tags[i].name == pluginName) { + return tags[i]; + } + } + ok(false, "Unable to find plugin"); + return null; +} + +// Set the 'enabledState' on the nsIPluginTag stored in the main or chrome +// process. +function setTestPluginEnabledState(newEnabledState, pluginName) { + let name = pluginName || "Test Plug-in"; + let plugin = getTestPlugin(name); + plugin.enabledState = newEnabledState; +} + +// Get the 'enabledState' on the nsIPluginTag stored in the main or chrome +// process. +function getTestPluginEnabledState(pluginName) { + let name = pluginName || "Test Plug-in"; + let plugin = getTestPlugin(name); + return plugin.enabledState; +} + +// Returns a promise for nsIObjectLoadingContent props data. +function promiseForPluginInfo(aId, aBrowser) { + let browser = aBrowser || gTestBrowser; + return SpecialPowers.spawn(browser, [aId], async function(contentId) { + let plugin = content.document.getElementById(contentId); + if (!(plugin instanceof Ci.nsIObjectLoadingContent)) { + throw new Error("no plugin found"); + } + return { + pluginFallbackType: plugin.pluginFallbackType, + activated: plugin.activated, + hasRunningPlugin: plugin.hasRunningPlugin, + displayedType: plugin.displayedType, + }; + }); +} + +// Return a promise and call the plugin's playPlugin() method. +function promisePlayObject(aId, aBrowser) { + let browser = aBrowser || gTestBrowser; + return SpecialPowers.spawn(browser, [aId], async function(contentId) { + content.document.getElementById(contentId).playPlugin(); + }); +} + +function promiseCrashObject(aId, aBrowser) { + let browser = aBrowser || gTestBrowser; + return SpecialPowers.spawn(browser, [aId], async function(contentId) { + let plugin = content.document.getElementById(contentId); + Cu.waiveXrays(plugin).crash(); + }); +} + +// Return a promise and call the plugin's getObjectValue() method. +function promiseObjectValueResult(aId, aBrowser) { + let browser = aBrowser || gTestBrowser; + return SpecialPowers.spawn(browser, [aId], async function(contentId) { + let plugin = content.document.getElementById(contentId); + return Cu.waiveXrays(plugin).getObjectValue(); + }); +} + +// Return a promise and reload the target plugin in the page +function promiseReloadPlugin(aId, aBrowser) { + let browser = aBrowser || gTestBrowser; + return SpecialPowers.spawn(browser, [aId], async function(contentId) { + let plugin = content.document.getElementById(contentId); + // eslint-disable-next-line no-self-assign + plugin.src = plugin.src; + }); +} + +// after a test is done using the plugin doorhanger, we should just clear +// any permissions that may have crept in +function clearAllPluginPermissions() { + for (let perm of Services.perms.all) { + if (perm.type.startsWith("plugin")) { + info( + "removing permission:" + perm.principal.origin + " " + perm.type + "\n" + ); + Services.perms.removePermission(perm); + } + } +} + +// Ported from AddonTestUtils.jsm +let JSONBlocklistWrapper = { + /** + * Load the data from the specified files into the *real* blocklist providers. + * Loads using loadBlocklistRawData, which will treat this as an update. + * + * @param {nsIFile} dir + * The directory in which the files live. + * @param {string} prefix + * a prefix for the files which ought to be loaded. + * This method will suffix -extensions.json and -plugins.json + * to the prefix it is given, and attempt to load both. + * Insofar as either exists, their data will be dumped into + * the respective store, and the respective update handlers + * will be called. + */ + async loadBlocklistData(url) { + const fullURL = `${url}-plugins.json`; + let jsonObj; + try { + jsonObj = await (await fetch(fullURL)).json(); + } catch (ex) { + ok(false, ex); + } + info(`Loaded ${fullURL}`); + + return this.loadBlocklistRawData({ plugins: jsonObj }); + }, + + /** + * Load the following data into the *real* blocklist providers. + * While `overrideBlocklist` replaces the blocklist entirely with a mock + * that returns dummy data, this method instead loads data into the actual + * blocklist, fires update methods as would happen if this data came from + * an actual blocklist update, etc. + * + * @param {object} data + * An object that can optionally have `extensions` and/or `plugins` + * properties, each being an array of blocklist items. + * This code only uses plugin blocks, that can look something like: + * + * { + * "matchFilename": "libnptest\\.so|nptest\\.dll|Test\\.plugin", + * "versionRange": [ + * { + * "severity": "0", + * "vulnerabilityStatus": "1" + * } + * ], + * "blockID": "p9999" + * } + * + */ + async loadBlocklistRawData(data) { + const bsPass = ChromeUtils.import( + "resource://gre/modules/Blocklist.jsm", + null + ); + const blocklistMapping = { + extensions: bsPass.ExtensionBlocklistRS, + plugins: bsPass.PluginBlocklistRS, + }; + + for (const [dataProp, blocklistObj] of Object.entries(blocklistMapping)) { + let newData = data[dataProp]; + if (!newData) { + continue; + } + if (!Array.isArray(newData)) { + throw new Error( + "Expected an array of new items to put in the " + + dataProp + + " blocklist!" + ); + } + for (let item of newData) { + if (!item.id) { + item.id = uuidGen.generateUUID().number.slice(1, -1); + } + if (!item.last_modified) { + item.last_modified = Date.now(); + } + } + await blocklistObj.ensureInitialized(); + let db = await blocklistObj._client.db; + await db.importChanges({}, 42, newData, { clear: true }); + // We manually call _onUpdate... which is evil, but at the moment kinto doesn't have + // a better abstraction unless you want to mock your own http server to do the update. + await blocklistObj._onUpdate(); + } + }, +}; + +// An async helper that insures a new blocklist is loaded (in both +// processes if applicable). +async function asyncSetAndUpdateBlocklist(aURL, aBrowser) { + let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false; + let localPromise = TestUtils.topicObserved("plugin-blocklist-updated"); + info("*** loading blocklist: " + aURL); + await JSONBlocklistWrapper.loadBlocklistData(aURL); + info("*** waiting on local load"); + await localPromise; + if (doTestRemote) { + info("*** waiting on remote load"); + // Ensure content has been updated with the blocklist + await SpecialPowers.spawn(aBrowser, [], () => {}); + } + info("*** blocklist loaded."); +} + +// Insure there's a popup notification present. This test does not indicate +// open state. aBrowser can be undefined. +function promisePopupNotification(aName, aBrowser) { + return new Promise(resolve => { + waitForCondition( + () => PopupNotifications.getNotification(aName, aBrowser), + () => { + ok( + !!PopupNotifications.getNotification(aName, aBrowser), + aName + " notification appeared" + ); + + resolve(); + }, + "timeout waiting for popup notification " + aName + ); + }); +} + +/** + * Allows setting focus on a window, and waiting for that window to achieve + * focus. + * + * @param aWindow + * The window to focus and wait for. + * + * @return {Promise} + * @resolves When the window is focused. + * @rejects Never. + */ +function promiseWaitForFocus(aWindow) { + return new Promise(resolve => { + waitForFocus(resolve, aWindow); + }); +} + +/** + * Returns a Promise that resolves when a notification bar + * for a browser is shown. Alternatively, for old-style callers, + * can automatically call a callback before it resolves. + * + * @param notificationID + * The ID of the notification to look for. + * @param browser + * The browser to check for the notification bar. + * @param callback (optional) + * A function to be called just before the Promise resolves. + * + * @return Promise + */ +function waitForNotificationBar(notificationID, browser, callback) { + return new Promise((resolve, reject) => { + let notification; + let notificationBox = gBrowser.getNotificationBox(browser); + waitForCondition( + () => + (notification = notificationBox.getNotificationWithValue( + notificationID + )), + () => { + ok( + notification, + `Successfully got the ${notificationID} notification bar` + ); + if (callback) { + callback(notification); + } + resolve(notification); + }, + `Waited too long for the ${notificationID} notification bar` + ); + }); +} + +function promiseForNotificationBar(notificationID, browser) { + return new Promise(resolve => { + waitForNotificationBar(notificationID, browser, resolve); + }); +} + +/** + * Reshow a notification and call a callback when it is reshown. + * @param notification + * The notification to reshow + * @param callback + * A function to be called when the notification has been reshown + */ +function waitForNotificationShown(notification, callback) { + if (PopupNotifications.panel.state == "open") { + executeSoon(callback); + return; + } + PopupNotifications.panel.addEventListener( + "popupshown", + function(e) { + callback(); + }, + { once: true } + ); + notification.reshow(); +} + +function promiseForNotificationShown(notification) { + return new Promise(resolve => { + waitForNotificationShown(notification, resolve); + }); +} + +/** + * Due to layout being async, "PluginBindAttached" may trigger later. This + * returns a Promise that resolves once we've forced a layout flush, which + * triggers the PluginBindAttached event to fire. This trick only works if + * there is some sort of plugin in the page. + * @param browser + * The browser to force plugin bindings in. + * @return Promise + */ +function promiseUpdatePluginBindings(browser) { + return SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + let elems = doc.getElementsByTagName("embed"); + if (!elems || elems.length < 1) { + elems = doc.getElementsByTagName("object"); + } + if (elems && elems.length) { + elems[0].clientTop; + } + }); +} diff --git a/browser/base/content/test/plugins/plugin_bug797677.html b/browser/base/content/test/plugins/plugin_bug797677.html new file mode 100644 index 0000000000..1545f36475 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_bug797677.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body><embed id="plugin" type="9000"></embed></body> +</html> diff --git a/browser/base/content/test/plugins/plugin_favorfallback.html b/browser/base/content/test/plugins/plugin_favorfallback.html new file mode 100644 index 0000000000..6eaf154994 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_favorfallback.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<html> +<head><meta charset="utf-8"/></head> +<body> +<style> +.testcase { + display: none; +} +object { + width: 200px; + height: 200px; +} +</style> + +<!-- Tests that a <video> tag in the fallback content favors the fallback content --> +<div id="testcase_video" class="testcase"> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + Unexpected fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + <video></video> + Expected fallback + </object> +</div> + +<!-- Tests that an object with no src specified (no data="") favors the fallback content --> +<div id="testcase_nosrc" class="testcase"> + <!-- We must use an existing and valid file here because otherwise the failed load + triggers the plugin's alternate content, indepedent of the favor-fallback code path --> + <object class="expected_ctp" type="application/x-shockwave-flash-test" data="plugin_simple_blank.swf"> + Unexpected fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + Expected fallback + </object> +</div> + +<!-- Tests that an <embed> tag in the fallback content forces the plugin content, + when fallback is defaulting to true --> +<div id="testcase_embed" class="testcase"> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + <embed></embed> + Unexpected fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + Expected fallback + </object> +</div> + +<!-- Tests that links to adobe.com inside the fallback content forces the plugin content, + when fallback is defaulting to true --> +<div id="testcase_adobelink" class="testcase"> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + <a href="https://www.adobe.com">Go to adobe.com</a> + Unexpected fallback + </object> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + <a href="https://adobe.com">Go to adobe.com</a> + Unexpected fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + Expected fallback + </object> +</div> + +<!-- Tests that instructions to download or install flash inside the fallback content + forces the plugin content, when fallback is defaulting to true --> +<div id="testcase_installinstructions" class="testcase"> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + Install -- Unexpected fallback + </object> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + Flash -- Unexpected fallback + </object> + <object class="expected_ctp" type="application/x-shockwave-flash-test"> + Download -- Unexpected fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + <!-- Tests that the words Install, Flash or Download do not trigger + this behavior if it's just inside a comment, and not part of + the text content --> + Expected Fallback + </object> + <object class="expected_fallback" type="application/x-shockwave-flash-test"> + Expected fallback + </object> +</div> + +<script> + let queryString = location.search; + let match = /^\?testcase=([a-z]+)$/.exec(queryString); + let testcase = match[1]; + document.getElementById(`testcase_${testcase}`).style.display = "block"; +</script> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_outsideScrollArea.html b/browser/base/content/test/plugins/plugin_outsideScrollArea.html new file mode 100644 index 0000000000..c6ef50d5db --- /dev/null +++ b/browser/base/content/test/plugins/plugin_outsideScrollArea.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<style type="text/css"> +#container { + position: fixed; + top: 0; + bottom: 0; + width: 100%; + height: 100%; + background: blue; +} + +#test { + width: 400px; + height: 400px; + position: absolute; +} +</style> +</head> +<body> + <div id="container"></div> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_simple_blank.swf b/browser/base/content/test/plugins/plugin_simple_blank.swf Binary files differnew file mode 100644 index 0000000000..b846387eb8 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_simple_blank.swf diff --git a/browser/base/content/test/plugins/plugin_test.html b/browser/base/content/test/plugins/plugin_test.html new file mode 100644 index 0000000000..3d4f43e6a5 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_test.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<embed id="test" style="width: 300px; height: 300px" type="application/x-test"> +</body> +</html> diff --git a/browser/base/content/test/plugins/plugin_zoom.html b/browser/base/content/test/plugins/plugin_zoom.html new file mode 100644 index 0000000000..f9e5986581 --- /dev/null +++ b/browser/base/content/test/plugins/plugin_zoom.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<!-- The odd width and height are here to trigger bug 972237. --> +<embed id="test" style="width: 99.789%; height: 99.123%" type="application/x-test"> +</body> +</html> |