diff options
Diffstat (limited to 'dom/plugins/test')
121 files changed, 9676 insertions, 0 deletions
diff --git a/dom/plugins/test/crashtests/110650-1.html b/dom/plugins/test/crashtests/110650-1.html new file mode 100644 index 0000000000..9826227b03 --- /dev/null +++ b/dom/plugins/test/crashtests/110650-1.html @@ -0,0 +1,11 @@ +<HTML> +<HEAD> +<TITLE>123246 testcase</TITLE> +</HEAD> +<BODY> +<object align="right" width=100> +</object> +</BODY > +</HTML> + + diff --git a/dom/plugins/test/crashtests/41276-1.html b/dom/plugins/test/crashtests/41276-1.html new file mode 100644 index 0000000000..ba35c34fa0 --- /dev/null +++ b/dom/plugins/test/crashtests/41276-1.html @@ -0,0 +1,28 @@ +<HTML><HEAD><TITLE>Plugin Limit</TITLE></HEAD>
+<BODY>
+
+Mozilla has a hardcoded limit of 10 simultaneously embedded plugins.<br>
+If that limit is exceeded, plugin instances are prematurely destroyed (see the empty boxes below).<br>
+Leave or reload a page that has a prematurely destroyed plugin and mozilla will crash.<br>
+Sometimes, just loading this page will cause mozilla to crash.<br>
+
+
+<br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player">
+<embed TYPE=audio/x-pn-realaudio-plugin WIDTH=300 HEIGHT=75 CONTROLS="Default" AUTOSTART="FALSE" CONSOLE="RA_Player"><br>
+<br>
+</BODY></HTML>
diff --git a/dom/plugins/test/crashtests/48856-1.html b/dom/plugins/test/crashtests/48856-1.html new file mode 100644 index 0000000000..cd0de2ab94 --- /dev/null +++ b/dom/plugins/test/crashtests/48856-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/REC-html40/loose.dtd"> +<HTML> + <HEAD> + <TITLE>Mozilla Bug 48856</TITLE> + </HEAD> + <BODY> + <EMBED></EMBED> + </BODY> +</HTML> diff --git a/dom/plugins/test/crashtests/539897-1.html b/dom/plugins/test/crashtests/539897-1.html new file mode 100644 index 0000000000..f280e62e6e --- /dev/null +++ b/dom/plugins/test/crashtests/539897-1.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('p'); + plugin.reinitWidget(); + plugin.reinitWidget(); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + var plugin = document.getElementById('p'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="p" type="application/x-test" wmode="window"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/540114-1.html b/dom/plugins/test/crashtests/540114-1.html new file mode 100644 index 0000000000..8243649aa3 --- /dev/null +++ b/dom/plugins/test/crashtests/540114-1.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script type="text/javascript"> +function crashplugin() { + var plugin = document.getElementById('removeme'); + var flush_reflow = plugin.offsetHeight; // this may not be necessary + document.body.removeChild(plugin); + // Give the plugin time to crash + setTimeout(function() { document.documentElement.removeAttribute('class') }, + 1000); +} + +function getTestCases() { + return [ + { testPassed: + (function () { + // Assuming the same process is used for removeme and checkme + var plugin = document.getElementById('checkme'); + try { + plugin.getPaintCount(); + return true; + } catch (e) { + return false; + } + }), + testDescription: + (function () { + return "plugin should not crash"; + }) + } + ]; +} +</script> +</head> +<body onload="crashplugin();"> +<embed id="checkme" type="application/x-test"/> +<embed id="removeme" type="application/x-test" wmode="window" cleanupwidget="false"/> +</body> +</html> diff --git a/dom/plugins/test/crashtests/570884.html b/dom/plugins/test/crashtests/570884.html new file mode 100644 index 0000000000..7af5cdba53 --- /dev/null +++ b/dom/plugins/test/crashtests/570884.html @@ -0,0 +1,8 @@ +<body> +<embed type="application/x-test" width="20"></embed> +<embed type="application/x-test" width="10"></embed> +<embed type="application/x-test" width="0"></embed> +<embed type="application/x-test" width="20" height="20"></embed> +<embed type="application/x-test" width="10" height="10"></embed> +<embed type="application/x-test" width="0" height="0"></embed> +</body> diff --git a/dom/plugins/test/crashtests/598862.html b/dom/plugins/test/crashtests/598862.html new file mode 100644 index 0000000000..02ffb05428 --- /dev/null +++ b/dom/plugins/test/crashtests/598862.html @@ -0,0 +1,77 @@ +<!DOCTYPE HTML> +<html class="reftest-wait"> + <head> + <script> +var unusedScreenX; +function XSync() { + unusedScreenX = window.screenX; +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function cleanup() { + try { + document.getElementById("plugin").crash(); + } catch (dontcare) { + } + window.setTimeout(finish, 100); +} + +function scrollOfDeath() { + // Add a listener for the MozAfterPaint after the scrollTo below. + // If we don't crash during the scroll, we'll get the + // MozAfterPaint. + window.addEventListener("MozAfterPaint", cleanup); + window.scrollTo(0, 50); +} + +// +// The sequence of events we expect is +// +// load (including initial paints of plugin that are cached) +// destroy X resources +// [X server has time to observe resource destruction] +// scrollTo +// [repaint using cached plugin surface: BOOM if buggy] +// MozAfterPaint +// cleanup +// +// However, this test is fundamentally nondeterministic. There are +// two main "failure" modes +// (1) X server doesn't have time to observer resource destruction +// before paint-after-scroll +// (2) plugin's crash notification arrives before +// paint-after-scroll +// Both result in spurious passes. +// +// This test is anal about cleanup because it must be pretty sure that +// the plugin subprocess is gone before starting the next test. We +// double-check that the plugin is gone by the time we're done by +// trying to crash it again, after we expect it to have crashed already. +// +function runTest() { + // Have the plugin throw away its X resources, one of which is + // probably a drawable to which we hold a reference + document.getElementById("plugin").destroySharedGfxStuff(); + + // Do something that's (hopefully) equivalent to an XSync() to allow + // the resource destruction to propagate to the server + XSync(); + + // Set up a scroll to happen soon + window.setTimeout(scrollOfDeath, 100); +} + +window.addEventListener("MozReftestInvalidate", runTest); + </script> + </head> + + <body style="width: 400px; height: 10000px;"> + <embed id="plugin" type="application/x-test" + style="position:absolute; + top:100px; left:50px; width:200px; height:200px;"> + </embed> + </body> +</html> diff --git a/dom/plugins/test/crashtests/626602-1.html b/dom/plugins/test/crashtests/626602-1.html new file mode 100644 index 0000000000..0a878bbd1d --- /dev/null +++ b/dom/plugins/test/crashtests/626602-1.html @@ -0,0 +1,109 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script type="text/javascript"> +var plugin, div, canvas; +function start() { + plugin = document.getElementById("four"); + div = document.getElementById("three"); + canvas = document.getElementById("two"); + paintCanvas(); + + requestAnimationFrame(moveSomething); +} + +function paintCanvas() { + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + +var i = 0, numLoops = 20; +var pluginIn = true, divIn = true, canvasIn = true; +function moveSomething() { + var didSomething = (0 === (i % 2)) ? moveSomethingOut() : moveSomethingIn(); + if (!didSomething && ++i >= numLoops) { + return finish(); + } + + requestAnimationFrame(moveSomething); +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +function moveSomethingOut() { + if (pluginIn) { + plugin.style.left = "400px"; + pluginIn = false; + } else if (divIn) { + div.style.left = "400px"; + divIn = false; + } else if (canvasIn) { + canvas.style.left = "400px"; + canvasIn = false; + } else { + return false; + } + return true; +} + +function moveSomethingIn() { + if (!pluginIn) { + plugin.style.left = "100px"; + pluginIn = true; + } else if (!divIn) { + div.style.left = "100px"; + divIn = true; + } else if (!canvasIn) { + canvas.style.left = "100px"; + canvasIn = true; + } else { + return false; + } + return true; +} + +function reset() { + +} + </script> +</style> +</head> +<body onload="start();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/crashtests/752340.html b/dom/plugins/test/crashtests/752340.html new file mode 100644 index 0000000000..c4c8c464f5 --- /dev/null +++ b/dom/plugins/test/crashtests/752340.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML> +<html> +<head> + <script type="text/javascript"> + // Failures in this file can manifest as ###!!! ASSERTION: scope has non-empty map: '0 == mWrappedNativeMap->Count()' + // followed by an Assertion failure: allocated() crash during the next GC. + // It can also manifest as a leak. + function breakthings() { + var e = document.createElement("embed"); + var i = document.getElementById("i"); + i.contentDocument.body.appendChild(e); + i.src = "about:blank"; + } + </script> +</head> +<body onload="javascript:breakthings();"> +<iframe id="i" /> +</body> +</html> diff --git a/dom/plugins/test/crashtests/843086.xhtml b/dom/plugins/test/crashtests/843086.xhtml new file mode 100644 index 0000000000..a88e2193fc --- /dev/null +++ b/dom/plugins/test/crashtests/843086.xhtml @@ -0,0 +1 @@ +<applet xmlns="http://www.w3.org/1999/xhtml" /> diff --git a/dom/plugins/test/crashtests/crashtests.list b/dom/plugins/test/crashtests/crashtests.list new file mode 100644 index 0000000000..4cc18e13a0 --- /dev/null +++ b/dom/plugins/test/crashtests/crashtests.list @@ -0,0 +1,14 @@ +HTTP load 41276-1.html +HTTP load 48856-1.html +HTTP load 110650-1.html +skip-if(!haveTestPlugin) HTTP script 539897-1.html +asserts-if(winWidget&&browserIsRemote,0-1) skip-if(!haveTestPlugin) HTTP script 540114-1.html +skip-if(!haveTestPlugin) HTTP load 570884.html +# This test relies on the reading of screenX/Y forcing a round trip to +# the X server, which is a bad assumption for <browser remote>. +# Plugin arch is going to change anyway with OOP content so skipping +# this test for now is OK. +skip-if(!haveTestPlugin||http.platform!="X11") HTTP load 598862.html +skip-if(!haveTestPlugin) HTTP load 626602-1.html +HTTP load 752340.html +HTTP load 843086.xhtml diff --git a/dom/plugins/test/mochitest/.eslintrc.js b/dom/plugins/test/mochitest/.eslintrc.js new file mode 100644 index 0000000000..317abe7b48 --- /dev/null +++ b/dom/plugins/test/mochitest/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + extends: [ + "plugin:mozilla/browser-test", + "plugin:mozilla/chrome-test", + "plugin:mozilla/mochitest-test", + ], +}; diff --git a/dom/plugins/test/mochitest/307-xo-redirect.sjs b/dom/plugins/test/mochitest/307-xo-redirect.sjs new file mode 100644 index 0000000000..b880978cea --- /dev/null +++ b/dom/plugins/test/mochitest/307-xo-redirect.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) +{ + response.setStatusLine(request.httpVersion, 307, "Moved temporarily"); + response.setHeader("Location", "http://example.org/tests/dom/plugins/test/mochitest/loremipsum.txt"); + response.setHeader("Content-Type", "text/html"); +} diff --git a/dom/plugins/test/mochitest/block_all_plugins.html b/dom/plugins/test/mochitest/block_all_plugins.html new file mode 100644 index 0000000000..3ccdda1373 --- /dev/null +++ b/dom/plugins/test/mochitest/block_all_plugins.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <object id="object" type="application/x-shockwave-flash"></object> + <embed id="embed" type="application/x-shockwave-flash"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/browser.ini b/dom/plugins/test/mochitest/browser.ini new file mode 100644 index 0000000000..32aaf9b576 --- /dev/null +++ b/dom/plugins/test/mochitest/browser.ini @@ -0,0 +1,17 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +support-files = + block_all_plugins.html + head.js + plugin_test.html + plugin_subframe_test.html + plugin_no_scroll_div.html + +[browser_blockallplugins.js] +[browser_bug1163570.js] +skip-if = true # Bug 1249878 +[browser_tabswitchbetweenplugins.js] +skip-if = true #Bug 1538425 +[browser_pluginscroll.js] +skip-if = (true || !e10s || os != "win") # Bug 1213631 diff --git a/dom/plugins/test/mochitest/browser_blockallplugins.js b/dom/plugins/test/mochitest/browser_blockallplugins.js new file mode 100644 index 0000000000..e847f2cd23 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_blockallplugins.js @@ -0,0 +1,66 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +add_task(async function() { + registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); + window.focus(); + }); +}); + +// simple tab load helper, pilfered from browser plugin tests +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; +} + +add_task(async function() { + let pluginTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + await promiseTabLoadEvent(pluginTab, gTestRoot + "block_all_plugins.html"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + let doc = content.document; + + let objectElt = doc.getElementById("object"); + Assert.ok(!!objectElt, "object should exist"); + Assert.ok( + objectElt instanceof Ci.nsIObjectLoadingContent, + "object should be an nsIObjectLoadingContent" + ); + Assert.ok( + objectElt.pluginFallbackType == + Ci.nsIObjectLoadingContent.PLUGIN_BLOCK_ALL, + "object should be blocked" + ); + + let embedElt = doc.getElementById("embed"); + Assert.ok(!!embedElt, "embed should exist"); + Assert.ok( + embedElt instanceof Ci.nsIObjectLoadingContent, + "embed should be an nsIObjectLoadingContent" + ); + Assert.ok( + embedElt.pluginFallbackType == + Ci.nsIObjectLoadingContent.PLUGIN_BLOCK_ALL, + "embed should be blocked" + ); + }); +}); diff --git a/dom/plugins/test/mochitest/browser_bug1163570.js b/dom/plugins/test/mochitest/browser_bug1163570.js new file mode 100644 index 0000000000..6a77728c77 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_bug1163570.js @@ -0,0 +1,118 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +// simple tab load helper, pilfered from browser plugin tests +function promiseTabLoad(tab, url, eventType = "load") { + return new Promise(resolve => { + function handle(event) { + if ( + event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url) + ) { + return; + } + tab.linkedBrowser.removeEventListener(eventType, handle, true); + resolve(event); + } + + tab.linkedBrowser.addEventListener(eventType, handle, true, true); + if (url) { + tab.linkedBrowser.loadURI(url); + } + }); +} + +// dom event listener helper +function promiseWaitForEvent( + object, + eventName, + capturing = false, + chrome = false +) { + return new Promise(resolve => { + function listener(event) { + object.removeEventListener(eventName, listener, capturing, chrome); + resolve(event); + } + object.addEventListener(eventName, listener, capturing, chrome); + }); +} + +add_task(async function() { + registerCleanupFunction(function() { + window.focus(); + }); +}); + +add_task(async function() { + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser)); + let prefTab = BrowserTestUtils.addTab(gBrowser); + + await promiseTabLoad(pluginTab, gTestRoot + "plugin_test.html"); + await promiseTabLoad(prefTab, "about:preferences"); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok(!!plugin, "plugin is loaded"); + }); + + let ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + gBrowser.selectedTab = prefTab; + await ppromise; + + // We're going to switch tabs using actual mouse clicks, which helps + // reproduce this bug. + let tabStripContainer = document.getElementById("tabbrowser-tabs"); + + // diagnosis if front end layout changes + info("-> " + tabStripContainer.tagName); // tabs + info("-> " + tabStripContainer.firstChild.tagName); // tab + info("-> " + tabStripContainer.childNodes[0].label); // test harness tab + info("-> " + tabStripContainer.childNodes[1].label); // plugin tab + info("-> " + tabStripContainer.childNodes[2].label); // preferences tab + + for (let iteration = 0; iteration < 5; iteration++) { + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter( + tabStripContainer.childNodes[1], + {}, + window + ); + await ppromise; + + await SpecialPowers.spawn(pluginTab.linkedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok( + XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is visible" + ); + }); + + ppromise = promiseWaitForEvent(window, "MozAfterPaint"); + EventUtils.synthesizeMouseAtCenter( + tabStripContainer.childNodes[2], + {}, + window + ); + await ppromise; + + await SpecialPowers.spawn(pluginTab.linkedBrowser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + Assert.ok( + !XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(), + "plugin is hidden" + ); + }); + } + + gBrowser.removeTab(prefTab); + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_pluginscroll.js b/dom/plugins/test/mochitest/browser_pluginscroll.js new file mode 100644 index 0000000000..a55651b1cc --- /dev/null +++ b/dom/plugins/test/mochitest/browser_pluginscroll.js @@ -0,0 +1,417 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +const { Preferences } = ChromeUtils.import( + "resource://gre/modules/Preferences.jsm" +); + +/** + * tests for plugin windows and scroll + */ + +function coordinatesRelativeToWindow(aX, aY, aElement) { + var targetWindow = aElement.ownerGlobal; + var scale = targetWindow.devicePixelRatio; + var rect = aElement.getBoundingClientRect(); + return { + x: targetWindow.mozInnerScreenX + (rect.left + aX) * scale, + y: targetWindow.mozInnerScreenY + (rect.top + aY) * scale, + }; +} + +var apzEnabled = + Services.appinfo.fissionAutostart || + Preferences.get("layers.async-pan-zoom.enabled", false); +var pluginHideEnabled = Preferences.get( + "gfx.e10s.hide-plugins-for-scroll", + true +); + +add_task(async function() { + registerCleanupFunction(function() { + setTestPluginEnabledState( + Ci.nsIPluginTag.STATE_CLICKTOPLAY, + "Test Plug-in" + ); + }); +}); + +add_task(async function() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["general.smoothScroll", true], + ["general.smoothScroll.other", true], + ["general.smoothScroll.mouseWheel", true], + ["general.smoothScroll.other.durationMaxMS", 2000], + ["general.smoothScroll.other.durationMinMS", 1999], + ["general.smoothScroll.mouseWheel.durationMaxMS", 2000], + ["general.smoothScroll.mouseWheel.durationMinMS", 1999], + ], + }); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a top level document. + */ + +add_task(async function() { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow( + 10, + 10, + gBrowser.selectedBrowser + ); + utils.sendNativeMouseScrollEvent( + screenCoords.x, + screenCoords.y, + nativeId, + 0, + -50, + 0, + 0, + 0, + gBrowser.selectedBrowser + ); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test plugin visibility when scrolling with scroll wheel and apz in a sub document. + */ + +add_task(async function() { + let result; + + if (!apzEnabled) { + ok(true, "nothing to test, need apz"); + return; + } + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_subframe_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + let nativeId = nativeVerticalWheelEventMsg(); + let utils = SpecialPowers.getDOMWindowUtils(window); + let screenCoords = coordinatesRelativeToWindow( + 10, + 10, + gBrowser.selectedBrowser + ); + utils.sendNativeMouseScrollEvent( + screenCoords.x, + screenCoords.y, + nativeId, + 0, + -50, + 0, + 0, + 0, + gBrowser.selectedBrowser + ); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a top level document. + * This circumvents apz and relies on dom scroll, which is what we want to target + * for this test. + */ + +add_task(async function() { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("KEY_End"); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("KEY_Home"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); + +/* + * test visibility when scrolling with keyboard shortcuts for a sub document. + */ + +add_task(async function() { + let result; + + if (!pluginHideEnabled) { + ok(true, "nothing to test, need gfx.e10s.hide-plugins-for-scroll"); + return; + } + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let pluginTab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_subframe_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin is loaded"); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + EventUtils.synthesizeKey("KEY_End"); + + await waitScrollStart(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, false, "plugin is hidden"); + + EventUtils.synthesizeKey("KEY_Home"); + + await waitScrollFinish(gBrowser.selectedBrowser); + + result = await SpecialPowers.spawn( + pluginTab.linkedBrowser, + [], + async function() { + let doc = content.document.getElementById("subframe").contentDocument; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + } + ); + is(result, true, "plugin is visible"); + + gBrowser.removeTab(pluginTab); +}); diff --git a/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js new file mode 100644 index 0000000000..c6b7e10796 --- /dev/null +++ b/dom/plugins/test/mochitest/browser_tabswitchbetweenplugins.js @@ -0,0 +1,141 @@ +var gTestRoot = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://127.0.0.1:8888/" +); + +function waitForPluginVisibility(browser, shouldBeVisible, errorMessage) { + return new Promise((resolve, reject) => { + let windowUtils = window.windowUtils; + let lastTransactionId = windowUtils.lastTransactionId; + let listener = async event => { + let visibility = await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return XPCNativeWrapper.unwrap(plugin).nativeWidgetIsVisible(); + }); + + if (visibility == shouldBeVisible) { + window.removeEventListener("MozAfterPaint", listener); + resolve(); + } else if (event && event.transactionId > lastTransactionId) { + // We want to allow for one failed check since we call listener + // directly, but if we get a MozAfterPaint notification and we + // still don't have the correct visibility, that's likely a + // problem. + reject(new Error("MozAfterPaint had a mismatched plugin visibility")); + } + }; + window.addEventListener("MozAfterPaint", listener); + listener(null); + }); +} + +// tests that we get plugin updates when we flip between tabs that +// have the same plugin in the same position in the page. + +add_task(async function() { + let result, tabSwitchedPromise; + + setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in"); + + let testTab = gBrowser.selectedTab; + let pluginTab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + let pluginTab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + gTestRoot + "plugin_test.html" + ); + + result = await SpecialPowers.spawn( + pluginTab1.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin1 is loaded"); + + result = await SpecialPowers.spawn( + pluginTab2.linkedBrowser, + [], + async function() { + let doc = content.document; + let plugin = doc.getElementById("testplugin"); + return !!plugin; + } + ); + is(result, true, "plugin2 is loaded"); + + // plugin tab 2 should be selected + is(gBrowser.selectedTab == pluginTab2, true, "plugin2 is selected"); + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + true, + "plugin2 should be visible" + ); + + // select plugin1 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab1; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + true, + "plugin1 should be visible" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + false, + "plugin2 should be hidden" + ); + + // select plugin2 tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = pluginTab2; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + true, + "plugin2 should be visible" + ); + + // select test tab + tabSwitchedPromise = waitTabSwitched(); + gBrowser.selectedTab = testTab; + await tabSwitchedPromise; + + await waitForPluginVisibility( + pluginTab1.linkedBrowser, + false, + "plugin1 should be hidden" + ); + + await waitForPluginVisibility( + pluginTab2.linkedBrowser, + false, + "plugin2 should be hidden" + ); + + gBrowser.removeTab(pluginTab1); + gBrowser.removeTab(pluginTab2); +}); diff --git a/dom/plugins/test/mochitest/file_authident.js b/dom/plugins/test/mochitest/file_authident.js new file mode 100644 index 0000000000..d067cff223 --- /dev/null +++ b/dom/plugins/test/mochitest/file_authident.js @@ -0,0 +1,14 @@ +var am = Cc["@mozilla.org/network/http-auth-manager;1"].getService( + Ci.nsIHttpAuthManager +); +am.setAuthIdentity( + "http", + "mochi.test", + 8888, + "basic", + "testrealm", + "", + "mochi.test", + "user1", + "password1" +); diff --git a/dom/plugins/test/mochitest/file_bug771202.html b/dom/plugins/test/mochitest/file_bug771202.html new file mode 100644 index 0000000000..935be65b25 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug771202.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body> + <embed id="pluginElement" type="application/x-test" width="200" height="200"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/file_bug863792.html b/dom/plugins/test/mochitest/file_bug863792.html new file mode 100644 index 0000000000..4a6889a563 --- /dev/null +++ b/dom/plugins/test/mochitest/file_bug863792.html @@ -0,0 +1,43 @@ +<!doctype html> +<html> +<head> + <title>File for Bug 863792</title> + + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <base href="chrome://browser/content/"> +</head> +<body> +<script type="application/javascript"> + +// A plugin that removes itself from the document and inactivates said document +// inside NPP_New. We should not leak the instance. See also test_bug854082 + +var outerwindow = window; +var i = document.createElement("iframe"); +i.width = 500; +i.height = 500; +var ob = document.body; +document.body.appendChild(i); +i.addEventListener("load", function loaded() { + var id = i.contentDocument; + var e = id.createElement("embed"); + e.width = 200; + e.height = 200; + e.type = "application/x-test"; + e.__defineSetter__("pluginFoundElement", function() { + window.console.log("pluginFoundElement"); + e.style.display = "none"; + e.clientTop; + i.removeEventListener("load", loaded); + ob.removeChild(i); + id.body.clientTop; + id.body.removeChild(e); + }); + id.body.appendChild(e); + e.clientTop; + e = id = i = ob = null; + SpecialPowers.forceCC(); SpecialPowers.forceGC(); +}); +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/head.js b/dom/plugins/test/mochitest/head.js new file mode 100644 index 0000000000..b9242f2932 --- /dev/null +++ b/dom/plugins/test/mochitest/head.js @@ -0,0 +1,147 @@ +/** + * Waits for a tab switch. + */ +function waitTabSwitched() { + return new Promise(resolve => { + gBrowser.addEventListener( + "TabSwitchDone", + function() { + executeSoon(resolve); + }, + { once: 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); + } + }); +} + +/** + * Platform string helper for nativeVerticalWheelEventMsg + */ +function getPlatform() { + if (navigator.platform.indexOf("Win") == 0) { + return "windows"; + } + if (navigator.platform.indexOf("Mac") == 0) { + return "mac"; + } + if (navigator.platform.indexOf("Linux") == 0) { + return "linux"; + } + return "unknown"; +} + +/** + * Returns a native wheel scroll event id for dom window + * uitls sendNativeMouseScrollEvent. + */ +function nativeVerticalWheelEventMsg() { + switch (getPlatform()) { + case "windows": + return 0x020a; // WM_MOUSEWHEEL + case "mac": + return 0; // value is unused, can be anything + case "linux": + return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway + } + throw new Error( + "Native wheel events not supported on platform " + getPlatform() + ); +} + +/** + * Waits for the first dom "scroll" event. + */ +function waitScrollStart(aTarget) { + return new Promise((resolve, reject) => { + aTarget.addEventListener( + "scroll", + function(event) { + resolve(event); + }, + { capture: true, once: true } + ); + }); +} + +/** + * Waits for the last dom "scroll" event which generally indicates + * a scroll operation is complete. To detect this the helper waits + * 1 second intervals checking for scroll events from aTarget. If + * a scroll event is not received during that time, it considers + * the scroll operation complete. Not super accurate, be careful. + */ +function waitScrollFinish(aTarget) { + return new Promise((resolve, reject) => { + let recent = false; + let count = 0; + function listener(event) { + recent = true; + } + aTarget.addEventListener("scroll", listener, true); + setInterval(function() { + // one second passed and we didn't receive a scroll event. + if (!recent) { + aTarget.removeEventListener("scroll", listener, true); + resolve(); + return; + } + recent = false; + // ten seconds + if (count > 10) { + aTarget.removeEventListener("scroll", listener, true); + reject(); + } + }, 1000); + }); +} + +/** + * Set a plugin activation state. See nsIPluginTag for + * supported states. Affected plugin default to the first + * test plugin. + */ +function setTestPluginEnabledState(aState, aPluginName) { + let name = aPluginName || "Test Plug-in"; + let resolved = false; + SpecialPowers.setTestPluginEnabledState(aState, name).then(() => { + resolved = true; + }); + SpecialPowers.Services.tm.spinEventLoopUntil(() => resolved); +} + +/** + * Returns the chrome side nsIPluginTag for this plugin, helper for + * setTestPluginEnabledState. + */ +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; +} diff --git a/dom/plugins/test/mochitest/large-pic.jpg b/dom/plugins/test/mochitest/large-pic.jpg Binary files differnew file mode 100644 index 0000000000..b167f6b9ba --- /dev/null +++ b/dom/plugins/test/mochitest/large-pic.jpg diff --git a/dom/plugins/test/mochitest/loremipsum.txt b/dom/plugins/test/mochitest/loremipsum.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest b/dom/plugins/test/mochitest/loremipsum.xtest new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum.xtest^headers^ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ new file mode 100644 index 0000000000..8dd7784af4 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum.xtest^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-test diff --git a/dom/plugins/test/mochitest/loremipsum_file.txt b/dom/plugins/test/mochitest/loremipsum_file.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_file.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt b/dom/plugins/test/mochitest/loremipsum_nocache.txt new file mode 100644 index 0000000000..c5becca596 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt @@ -0,0 +1,11 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
+
+Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
+
+Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.
+
+Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut laboreet dolore magna aliquyam erat.
+
+Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ new file mode 100644 index 0000000000..12a01c4a22 --- /dev/null +++ b/dom/plugins/test/mochitest/loremipsum_nocache.txt^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store
diff --git a/dom/plugins/test/mochitest/mixed_case_mime.sjs b/dom/plugins/test/mochitest/mixed_case_mime.sjs new file mode 100644 index 0000000000..1901bb74d8 --- /dev/null +++ b/dom/plugins/test/mochitest/mixed_case_mime.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) +{ + response.processAsync(); + response.setHeader("Content-Type", "image/pNG", false); + + response.write("Hello world.\n"); + response.finish(); +} diff --git a/dom/plugins/test/mochitest/mochitest.ini b/dom/plugins/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..79ed62167a --- /dev/null +++ b/dom/plugins/test/mochitest/mochitest.ini @@ -0,0 +1,45 @@ +[DEFAULT] +prefs = + plugin.load_flash_only=false +skip-if = headless # crash on shutdown, no other failures +support-files = + 307-xo-redirect.sjs + file_authident.js + file_bug771202.html + file_bug863792.html + large-pic.jpg + loremipsum.txt + loremipsum.xtest + loremipsum.xtest^headers^ + loremipsum_file.txt + loremipsum_nocache.txt + loremipsum_nocache.txt^headers^ + mixed_case_mime.sjs + neverending.sjs + npruntime_identifiers_subpage.html + plugin-stream-referer.sjs + plugin_window.html + pluginstream.js + post.sjs + plugin-utils.js + +[test_hanging.html] +skip-if = !crashreporter || e10s +[test_mixed_case_mime.html] +skip-if = (processor == 'aarch64' && os == 'win') +reason = Plugins are not supported on Windows/AArch64 +[test_plugin_fallback_focus.html] +[test_plugin_scroll_painting.html] +skip-if = true # Bug 596491 +[test_pluginstream_geturl.html] +skip-if = true # Bug 1267432 +[test_pluginstream_geturlnotify.html] +skip-if = true # Bug 1267432 +[test_positioning.html] +skip-if = true # disabled due to oddness, perhaps scrolling of the mochitest window? +[test_queryContentsScaleFactor.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_queryContentsScaleFactorWindowed.html] +skip-if = (toolkit != "cocoa") || (os != "win") +[test_refresh_navigator_plugins.html] +skip-if = e10s # Bug 1090576 diff --git a/dom/plugins/test/mochitest/neverending.sjs b/dom/plugins/test/mochitest/neverending.sjs new file mode 100644 index 0000000000..1576ce344c --- /dev/null +++ b/dom/plugins/test/mochitest/neverending.sjs @@ -0,0 +1,16 @@ +var timer = null; // declare timer outside to prevent premature GC
+function handleRequest(request, response)
+{
+ response.processAsync();
+ response.setHeader("Content-Type", "text/plain", false);
+
+ for (var i = 0; i < 1000; ++i)
+ response.write("Hello... ");
+
+ timer = Components.classes["@mozilla.org/timer;1"]
+ .createInstance(Components.interfaces.nsITimer);
+ timer.initWithCallback(function() {
+ response.write("world.\n");
+ response.finish();
+ }, 10 * 1000 /* 10 secs */, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html new file mode 100644 index 0000000000..38c62e017b --- /dev/null +++ b/dom/plugins/test/mochitest/npruntime_identifiers_subpage.html @@ -0,0 +1,4 @@ +<html> + <body> + <embed id="plugin1" type="application/x-test" width="400" height="100"> + </embed> diff --git a/dom/plugins/test/mochitest/plugin-stream-referer.sjs b/dom/plugins/test/mochitest/plugin-stream-referer.sjs new file mode 100644 index 0000000000..a1c9692c95 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-stream-referer.sjs @@ -0,0 +1,12 @@ +function handleRequest(request, response) +{ + response.setHeader('Content-Type', 'text/plain', false); + response.setHeader('Cache-Control', 'no-cache', false); + response.setHeader('Content-Type', 'application/x-test', false); + if (request.hasHeader('Referer')) { + response.write('Referer found: ' + request.getHeader('Referer')); + } + else { + response.write('No Referer found'); + } +} diff --git a/dom/plugins/test/mochitest/plugin-utils.js b/dom/plugins/test/mochitest/plugin-utils.js new file mode 100644 index 0000000000..1d35d2b049 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin-utils.js @@ -0,0 +1,147 @@ +function paintCountIs(plugin, expected, msg) { + var count = plugin.getPaintCount(); + var realExpected = expected; + ++realExpected; // extra paint at startup for all async-rendering plugins + ok( + realExpected == count, + msg + + " (expected " + + expected + + " independent paints, expected " + + realExpected + + " logged paints, got " + + count + + " actual paints)" + ); +} + +function getTestPlugin(pluginName) { + var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"].getService( + SpecialPowers.Ci.nsIPluginHost + ); + var tags = ph.getPluginTags(); + var name = pluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + ok(false, "Could not find plugin tag with plugin name '" + name + "'"); + return null; +} + +// call this to set the test plugin(s) initially expected enabled state. +// it will automatically be reset to it's previous value after the test +// ends +function setTestPluginEnabledState(newEnabledState, pluginName) { + var oldEnabledState = SpecialPowers.setTestPluginEnabledState( + newEnabledState, + pluginName + ); + var plugin = getTestPlugin(pluginName); + // Run a nested event loop to wait for the preference change to + // propagate to the child. Yuck! + SpecialPowers.Services.tm.spinEventLoopUntil(() => { + return plugin.enabledState == newEnabledState; + }); + SimpleTest.registerCleanupFunction(async function() { + return SpecialPowers.setTestPluginEnabledState( + await oldEnabledState, + pluginName + ); + }); +} + +function crashAndGetCrashServiceRecord(crashMethodName, callback) { + var crashMan = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm") + .Services.crashmanager; + + // First, clear the crash record store. + info("Waiting for pruneOldCrashes"); + var future = new Date(Date.now() + 1000 * 60 * 60 * 24); + crashMan.pruneOldCrashes(future).then( + function() { + var iframe = document.getElementById("iframe1"); + var p = iframe.contentDocument.getElementById("plugin1"); + + var crashDateMS = Date.now(); + try { + p[crashMethodName](); + ok(false, "p." + crashMethodName + "() should throw an exception"); + } catch (e) { + ok(true, "p." + crashMethodName + "() should throw an exception"); + } + + // The crash record store is written and read back asyncly, so poll for + // the new record. + function tryGetCrash() { + info("Waiting for getCrashes"); + crashMan.getCrashes().then( + SpecialPowers.wrapCallback(function(crashes) { + if (crashes.length) { + is(crashes.length, 1, "There should be only one record"); + var crash = SpecialPowers.wrap(crashes[0]); + ok(!!crash.id, "Record should have an ID"); + ok(!!crash.crashDate, "Record should have a crash date"); + var dateMS = crash.crashDate.valueOf(); + var twoMin = 1000 * 60 * 2; + ok( + crashDateMS - twoMin <= dateMS && + dateMS <= crashDateMS + twoMin, + "Record's crash date should be nowish: " + + "now=" + + crashDateMS + + " recordDate=" + + dateMS + ); + callback(crashMan, crash); + } else { + setTimeout(tryGetCrash, 1000); + } + }), + function(err) { + ok(false, "Error getting crashes: " + err); + SimpleTest.finish(); + } + ); + } + setTimeout(tryGetCrash, 1000); + }, + function() { + ok(false, "pruneOldCrashes error"); + SimpleTest.finish(); + } + ); +} + +/** + * Returns a promise which resolves on `mozFullScreenChange`. + */ +function promiseFullScreenChange() { + return new Promise(resolve => { + document.addEventListener( + "fullscreenchange", + function(e) { + resolve(); + }, + { once: true } + ); + }); +} + +/** + * Crashes target plugin. Returns a promise; resolves on successful crash, + * rejects otherwise. + * @param plugin Target plugin to attempt to crash. + */ +function crashPlugin(plugin) { + return new Promise((resolve, reject) => { + try { + plugin.crash(); + reject(); + } catch (e) { + resolve(); + } + }); +} diff --git a/dom/plugins/test/mochitest/plugin_no_scroll_div.html b/dom/plugins/test/mochitest/plugin_no_scroll_div.html new file mode 100644 index 0000000000..b28f6d6ffb --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_no_scroll_div.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:5px; left:5px; width:500px; height:250px"> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_subframe_test.html b/dom/plugins/test/mochitest/plugin_subframe_test.html new file mode 100644 index 0000000000..598521d57e --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_subframe_test.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <iframe id="subframe" style="width:510px; height:260px;" src="plugin_no_scroll_div.html"></iframe> + <div style="display:block; height:3000px;"></div> +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_test.html b/dom/plugins/test/mochitest/plugin_test.html new file mode 100644 index 0000000000..88b70e8ee6 --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_test.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> + <embed id="testplugin" type="application/x-test" drawmode="solid" color="ff00ff00" wmode="window" + style="position:absolute; top:50px; left:50px; width:500px; height:250px"> +<div style="display:block; height:3000px;"></div> + +<iframe id="subf" src="about:blank" width="300" height="300"></iframe> + +<a href="about:blank" id="aboutlink">Navigate to about:blank</a> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/plugin_window.html b/dom/plugins/test/mochitest/plugin_window.html new file mode 100644 index 0000000000..d3a298e89c --- /dev/null +++ b/dom/plugins/test/mochitest/plugin_window.html @@ -0,0 +1,23 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>NPAPI Stream Tests</title> +</head> +<body onload="doTest()"> +<p id="display"></p> +<div id="content" style="display: none"> +This is a plugin test window. +</div> +<div id="test"> +<script class="testbody" type="text/javascript"> + +function doTest() { + window.opener.continueTest(); +} + +</script> +</div> +</body> + +</html> + diff --git a/dom/plugins/test/mochitest/pluginstream.js b/dom/plugins/test/mochitest/pluginstream.js new file mode 100644 index 0000000000..c4ab769d51 --- /dev/null +++ b/dom/plugins/test/mochitest/pluginstream.js @@ -0,0 +1,46 @@ +SimpleTest.waitForExplicitFinish(); + +function frameLoaded(finishWhenCalled = true, lastObject = false) { + var testframe = document.getElementById("testframe"); + function getNode(list) { + if (list.length === 0) { + return undefined; + } + return lastObject ? list[list.length - 1] : list[0]; + } + var embed = getNode(document.getElementsByTagName("embed")); + if (undefined === embed) { + embed = getNode(document.getElementsByTagName("object")); + } + + // In the file:// URI case, this ends up being cross-origin. + // Skip these checks in that case. + if (testframe.contentDocument) { + var content = testframe.contentDocument.body.innerHTML; + if (!content.length) { + return; + } + + var filename = + embed.getAttribute("src") || + embed.getAttribute("geturl") || + embed.getAttribute("geturlnotify") || + embed.getAttribute("data"); + + var req = new XMLHttpRequest(); + req.open("GET", filename, false); + req.overrideMimeType("text/plain; charset=x-user-defined"); + req.send(null); + is(req.status, 200, "bad XMLHttpRequest status"); + is( + content, + req.responseText.replace(/\r\n/g, "\n"), + "content doesn't match" + ); + } + + is(embed.getError(), "pass", "plugin reported error"); + if (finishWhenCalled) { + SimpleTest.finish(); + } +} diff --git a/dom/plugins/test/mochitest/post.sjs b/dom/plugins/test/mochitest/post.sjs new file mode 100644 index 0000000000..b391dbdd81 --- /dev/null +++ b/dom/plugins/test/mochitest/post.sjs @@ -0,0 +1,17 @@ +const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response)
+{
+ var body = "";
+ var bodyStream = new BinaryInputStream(request.bodyInputStream);
+ var bytes = [], avail = 0;
+ while ((avail = bodyStream.available()) > 0)
+ body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail));
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(body);
+}
+
diff --git a/dom/plugins/test/mochitest/test_hanging.html b/dom/plugins/test/mochitest/test_hanging.html new file mode 100644 index 0000000000..b6dd1b12a7 --- /dev/null +++ b/dom/plugins/test/mochitest/test_hanging.html @@ -0,0 +1,59 @@ +<head> + <title>Plugin hanging</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + +<body> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + window.frameLoaded = function frameLoaded_toCrash() { + SimpleTest.expectChildProcessCrash(); + + // the default timeout is annoying high for mochitest runs + var timeoutPref = "dom.ipc.plugins.timeoutSecs"; + SpecialPowers.setIntPref(timeoutPref, 5); + + var iframe = document.getElementById("iframe1"); + var p = iframe.contentDocument.getElementById("plugin1"); + + p.setColor("FFFF00FF"); + + try { + p.hang(); + ok(false, "p.hang() should throw an exception"); + } catch (e) { + ok(true, "p.hang() should throw an exception"); + } + + try { + p.setColor("FFFF0000"); + ok(false, "p.setColor should throw after the plugin crashes"); + } catch (e) { + ok(true, "p.setColor should throw after the plugin crashes"); + } + + window.frameLoaded = function reloaded() { + var p1 = iframe.contentDocument.getElementById("plugin1"); + try { + p1.setColor("FF00FF00"); + ok(true, "Reloading worked"); + } catch (e) { + ok(false, "Reloading didn't give us a usable plugin"); + } + + try { + SpecialPowers.clearUserPref(timeoutPref); + } catch (e) { + ok(false, "Couldn't reset timeout pref"); + } + + SimpleTest.finish(); + }; + + iframe.contentWindow.location.reload(); + }; + + </script> + <iframe id="iframe1" src="crashing_subpage.html" width="600" height="600"></iframe> diff --git a/dom/plugins/test/mochitest/test_mixed_case_mime.html b/dom/plugins/test/mochitest/test_mixed_case_mime.html new file mode 100644 index 0000000000..85e5f19ac9 --- /dev/null +++ b/dom/plugins/test/mochitest/test_mixed_case_mime.html @@ -0,0 +1,25 @@ +<body> +<head> + <title>Test mixed case mimetype for plugins</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +<script> + SimpleTest.expectAssertions(0, 1); + + SimpleTest.waitForExplicitFinish(); + + function frameLoaded() { + var contentDocument = document.getElementById("testframe").contentDocument; + ok(contentDocument.body.innerHTML.length > 0, "Frame content shouldn't be empty."); + ok(contentDocument.images.length > 0, "Frame content should have an image."); + SimpleTest.finish(); + } +</script> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()" src="mixed_case_mime.sjs"></iframe> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_fallback_focus.html b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html new file mode 100644 index 0000000000..e89abb44df --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_fallback_focus.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test that plugins reject focus</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<div id="content"> + <object id="obj_elt" type="application/x-shockwave-flash"></object> + <object tabindex='0' id="obj_elt_with_idx" type="application/x-shockwave-flash"></object> + <embed id="embed_elt" type="application/x-shockwave-flash"></embed> +</div> +<script type="application/javascript"> +var objElt = document.getElementById('obj_elt'); +var objEltWithIdx = document.getElementById('obj_elt_with_idx'); +var embedElt = document.getElementById('embed_elt'); + +function checkHasFocus(expected, typeOfElts, elt) { + ok((document.activeElement == elt) == expected, + typeOfElts + " element should " + (expected ? "" : "not ") + "accept focus"); +} + +function checkNoneHasFocus(typeOfElts) { + checkHasFocus(false, typeOfElts + " <object>", objElt); + checkHasFocus(false, typeOfElts + " <object> with tabindex", objEltWithIdx); + checkHasFocus(false, typeOfElts + " <embed>", embedElt); +} + +function checkFocusable(expected, typeOfElts, elt) { + elt.focus(); + checkHasFocus(expected, typeOfElts, elt); +} + +// As plugins, object and embed elements are not given focus +ok(objElt != null, "object element should exist"); +ok(objEltWithIdx != null, "object element with tabindex should exist"); +ok(embedElt != null, "embed element should exist"); + +// As plugins, obj/embed_elt can not take focus +checkNoneHasFocus("plugin"); + +// Switch obj/embed_elt attributes from plugin to image +objElt.data = "large-pic.jpg"; +objElt.width = 100; +objElt.height = 100; +objElt.type = "image/jpg"; +objEltWithIdx.data = "large-pic.jpg"; +objEltWithIdx.width = 100; +objEltWithIdx.height = 100; +objEltWithIdx.type = "image/jpg"; +embedElt.src = "large-pic.jpg"; +embedElt.width = 100; +embedElt.height = 100; +embedElt.type = "image/jpg"; + +// As images, obj/embed_elt can take focus as image +// object image elements require a tabindex to accept focus. +// embed elements must be reparented before new type is recognized. +checkFocusable(false, "<object> image", objElt); +checkFocusable(true, "<object> image with tabindex", objEltWithIdx); +checkFocusable(false, "<embed> plugin with image attribs before reparenting", embedElt); +embedElt.parentNode.appendChild(embedElt); +checkFocusable(true, "<embed> image", embedElt); + +// Switch obj/embed_elt attributes from image to plugin +objElt.type = "application/x-shockwave-flash"; +embedElt.type = "application/x-shockwave-flash"; + +// embed elements must be reparented before new type is recognized. +checkFocusable(true, "<embed> image with plugin attribs", embedElt); +embedElt.parentNode.appendChild(embedElt); +checkNoneHasFocus("plugin"); +</script> +</body> +</html> + + diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html new file mode 100644 index 0000000000..5fdd2a7b6c --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_invalidation.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for plugin child widgets not being invalidated by scrolling</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="initialize()"> +<script type="application/javascript"> +setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, + "Test Plug-in"); +</script> + +<p id="display"> + <iframe id="i" src="plugin_scroll_invalidation.html" + width="50" height="50" scrolling="no"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var scrolling; +var scrolling_plugins = []; +var paint_waiter; +var last_paint_counts; + +function initialize() { + scrolling = document.getElementById("i").contentWindow; + scrolling_plugins = scrolling.document.querySelectorAll("embed.scrolling"); + paint_waiter = scrolling.document.getElementById("paint-waiter"); + + scrolling.scrollTo(50, 45); + + is(paint_waiter.getPaintCount(), 0, "zero-sized plugin not painted"); + + waitForPaint(scrollAround); +} + +function scrollAround() { + var paints = getPaintCounts(); + + for (var i = 0; i < paints.length; ++i) { + isnot(paints[i], 0, "embed " + scrolling_plugins[i].id + " is painted"); + } + + last_paint_counts = paints; + + scrolling.scrollBy(-5, 5); + scrolling.scrollBy(5, 5); + scrolling.scrollBy(5, -5); + scrolling.scrollBy(-5, -5); + + scrolling.scrollTo(45, 45); + scrolling.scrollBy(10, 0); + scrolling.scrollBy(0, 10); + scrolling.scrollBy(-10, 0); + scrolling.scrollBy(0, -10); + + waitForPaint(done); +} + +function done() { + var paints = getPaintCounts(); + for (var i = 0; i < paints.length; ++i) { + is(paints[i], last_paint_counts[i], "embed " + scrolling_plugins[i].id + " is not painted on scroll"); + } + SimpleTest.finish(); +} + +// Waits for the paint_waiter plugin to be repainted and then +// calls 'func' to continue. +function waitForPaint(func) { + paint_waiter.last_paint_count = paint_waiter.getPaintCount(); + + paint_waiter.style.left = scrolling.scrollX + "px"; + paint_waiter.style.top = scrolling.scrollY + "px"; + + // Fiddle with the style in a way that should force some repainting + paint_waiter.style.width = + (paint_waiter.getBoundingClientRect().width + 1) + "px"; + paint_waiter.style.height = "1px"; + + function waitForPaintHelper() { + if (paint_waiter.getPaintCount() != paint_waiter.last_paint_count) { + setTimeout(func, 0); + return; + } + setTimeout(waitForPaintHelper, 0); + } + waitForPaintHelper(); +} + +function getPaintCounts() { + var result = []; + for (var i = 0; i < scrolling_plugins.length; ++i) { + result[i] = scrolling_plugins[i].getPaintCount(); + } + return result; +} + +</script> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_plugin_scroll_painting.html b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html new file mode 100644 index 0000000000..1041b948da --- /dev/null +++ b/dom/plugins/test/mochitest/test_plugin_scroll_painting.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that scrolling a windowless plugin doesn't force us to repaint it</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="runTest()"> +<p id="display"></p> + <embed id="plugin" type="application/x-test" style="width:50px; height:10px; margin-top:20px;"></embed> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); + +var container = document.documentElement; +container.scrollTop = 0; +var plugin = document.getElementById("plugin"); +var pluginTop; +var beforeScrollPaintCount; + +function waitForScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForScroll, 0); + return; + } + + is(plugin.getPaintCount(), beforeScrollPaintCount, "plugin should not paint due to scrolling"); + SimpleTest.finish(); +} + +function waitForInitialScroll() { + if (plugin.getEdge(1) >= pluginTop) { + setTimeout(waitForInitialScroll, 0); + return; + } + + pluginTop = plugin.getEdge(1); + beforeScrollPaintCount = plugin.getPaintCount(); + container.scrollTop = 20; + waitForScroll(); +} + +function runTest() { + document.body.offsetTop; + pluginTop = plugin.getEdge(1); + container.scrollTop = 10; + waitForInitialScroll(); +} +</script> + +<div style="height:4000px;"></div> + +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturl.html b/dom/plugins/test/mochitest/test_pluginstream_geturl.html new file mode 100644 index 0000000000..fe69427a42 --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturl.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPN_GetURL NPStream Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The plugin reports that data can be sent to + - it in 1024-byte chunks, and the stream is initiated by a call to + - NPN_GetURL. + --> + <embed geturl="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> +
\ No newline at end of file diff --git a/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html new file mode 100644 index 0000000000..ee4c2b119d --- /dev/null +++ b/dom/plugins/test/mochitest/test_pluginstream_geturlnotify.html @@ -0,0 +1,30 @@ +<body> +<head> + <title>NPAPI NPN_GetURLNotify Test</title> + <script type="text/javascript" + src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" + src="pluginstream.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <script type="text/javascript"> + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css" /> +</head> +<body> + <p id="display"></p> + + <iframe id="testframe" name="testframe" onload="frameLoaded()"></iframe> + + <!-- + - The stream is requested by + - the plugin using NPN_GetURLNotify, and the plugin does not send the + - stream back to the browser until NPP_URLNotify is called. + --> + <embed geturlnotify="loremipsum.txt" streammode="normal" + streamchunksize="1024" frame="testframe" + id="embedtest" style="width: 400px; height: 100px;" + type="application/x-test"></embed> + </body> + </html> diff --git a/dom/plugins/test/mochitest/test_positioning.html b/dom/plugins/test/mochitest/test_positioning.html new file mode 100644 index 0000000000..b73f4b06fd --- /dev/null +++ b/dom/plugins/test/mochitest/test_positioning.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test whether windowless plugins receive correct visible/invisible notifications.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + + <style type="text/css"> + body { + height: 10000px; + } + </style> + +<body onload="startTest()"> + <p id="display"></p> + + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + var p = null; + + function startTest() { + p = document.getElementById("theplugin"); + + // Wait for the plugin to have painted once + var interval = setInterval(function() { + if (!p.getPaintCount()) + return; + + clearInterval(interval); + doScroll(); + }, 100); + } + + const kScrollAmount = 1000; + var startY; + + function doScroll() { + let y = p.getWindowPosition()[1]; + startY = y; + + scrollBy(0, kScrollAmount); + setTimeout(checkScroll, 500); + } + + function checkScroll() { + let y = p.getWindowPosition()[1]; + + is(y, startY - kScrollAmount, "Window should be informed of its new position."); + SimpleTest.finish(); + } + </script> + + <embed id="theplugin" type="application/x-test" width="200" height="200"></embed> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html new file mode 100644 index 0000000000..565c1494f4 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactor.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html new file mode 100644 index 0000000000..8db018fd66 --- /dev/null +++ b/dom/plugins/test/mochitest/test_queryContentsScaleFactorWindowed.html @@ -0,0 +1,31 @@ +<html> +<head> + <title>NPAPI NPNVcontentsScaleFactor Test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="utils.js"></script> +</head> + +<body onload="runTests()"> + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + function runTests() { + var pluginElement = document.getElementById("plugin"); + var contentsScaleFactor; + var exceptionThrown = false; + try { + contentsScaleFactor = pluginElement.queryContentsScaleFactor(); + } catch (e) { + exceptionThrown = true; + } + is(exceptionThrown, false, "Exception thrown getting contents scale factor."); + is(isNaN(contentsScaleFactor), false, "Invalid return getting contents scale factor"); + ok(true, "Got Scale Factor of " + contentsScaleFactor); + SimpleTest.finish(); + } + </script> + + <embed id="plugin" type="application/x-test" width="400" height="400" wmode="window"></embed> +</body> +</html> diff --git a/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html new file mode 100644 index 0000000000..fc54370a0b --- /dev/null +++ b/dom/plugins/test/mochitest/test_refresh_navigator_plugins.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<!-- bug 820708 --> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Refreshing navigator.plugins (bug 820708)</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" + href="/tests/SimpleTest/test.css"> + </head> + <body> + <p id="display"></p> + <script class="testbody" type="application/javascript"> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + ok("Test Plug-in" in navigator.plugins, "testplugin should be present"); + ok("application/x-test" in navigator.mimeTypes, "testplugin MIME should be present"); + + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_DISABLED); + ok(!("Test Plug-in" in navigator.plugins), "testplugin should not be present"); + ok(!("application/x-test" in navigator.mimeTypes), "testplugin MIME should not be present"); + + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + ok("Test Plug-in" in navigator.plugins, "testplugin should be present again"); + ok("application/x-test" in navigator.mimeTypes, "testplugin MIME should be present again"); + SimpleTest.finish(); + </script> + </body> +</html> diff --git a/dom/plugins/test/moz.build b/dom/plugins/test/moz.build new file mode 100644 index 0000000000..7f40fb3cbe --- /dev/null +++ b/dom/plugins/test/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["testplugin"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("gtk", "cocoa", "windows"): + MOCHITEST_MANIFESTS += ["mochitest/mochitest.ini"] + BROWSER_CHROME_MANIFESTS += ["mochitest/browser.ini"] diff --git a/dom/plugins/test/reftest/border-padding-1-ref.html b/dom/plugins/test/reftest/border-padding-1-ref.html new file mode 100644 index 0000000000..1a33644ac4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; background:lime;"></div> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-1.html b/dom/plugins/test/reftest/border-padding-1.html new file mode 100644 index 0000000000..6fa2446f40 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-1.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('p1')"> +<object type="application/x-test" drawmode="solid" color="ff00ff00" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" id="p1"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2-ref.html b/dom/plugins/test/reftest/border-padding-2-ref.html new file mode 100644 index 0000000000..ae92da4032 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <object style="margin:3px 1px; height:186px; width:182px; display:block;" + type="application/x-test" + id="plugin"> + </object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-2.html b/dom/plugins/test/reftest/border-padding-2.html new file mode 100644 index 0000000000..6a39d2d819 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin')"> +<object type="application/x-test" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;" + id="plugin"> +</object> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3-ref.html b/dom/plugins/test/reftest/border-padding-3-ref.html new file mode 100644 index 0000000000..5c7bb74564 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<body style="margin:0"> +<div style="width:184px; height:192px; margin:90px 80px; outline:5px dashed blue; + border:dotted black; border-width:4px 8px 4px 8px; + background:cyan;"> + <div style="margin:3px 1px; height:186px; width:182px; background:rgb(255,128,255);"></object> +</div> +</body> +</html> diff --git a/dom/plugins/test/reftest/border-padding-3.html b/dom/plugins/test/reftest/border-padding-3.html new file mode 100644 index 0000000000..4d240a7eb4 --- /dev/null +++ b/dom/plugins/test/reftest/border-padding-3.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body style="margin:0" onLoad="forceLoadPlugin('plugin', true)"> +<object type="application/x-test" id="plugin" + drawmode="solid" color="00000000" + style="width:200px; height:200px; display:block; margin:90px 80px; + outline:5px dashed blue; + background:cyan; + border:dotted black; border-width:4px 8px 4px 8px; + padding:3px 1px;"> +</object> +<script> +var prevPaintCount = 0; +function doTestWait() { + if (document.getElementById("plugin").getPaintCount() != prevPaintCount) { + document.documentElement.removeAttribute('class'); + } else { + setTimeout(doTestWait, 0); + } +} + +function doTest() { + prevPaintCount = document.getElementById("plugin").getPaintCount(); + document.getElementById("plugin").setColor("FFFF80FF"); + setTimeout(doTestWait, 0); + +} +window.addEventListener("MozReftestInvalidate", doTest); +</script> +</body> +</html> diff --git a/dom/plugins/test/reftest/div-alpha-opacity.html b/dom/plugins/test/reftest/div-alpha-opacity.html new file mode 100644 index 0000000000..fec913b640 --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-opacity.html @@ -0,0 +1,28 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + border:2px solid blue; + background-color: rgb(160,160,160); + opacity:0.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-alpha-zindex.html b/dom/plugins/test/reftest/div-alpha-zindex.html new file mode 100644 index 0000000000..e4672b913b --- /dev/null +++ b/dom/plugins/test/reftest/div-alpha-zindex.html @@ -0,0 +1,27 @@ +<!doctype html> +<html> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:400px; height:400px; + background-color: rgb(0,255,0); + opacity:0.6; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +</head> +<body> +<div id="two"></div> +<div id="one"></div> +</body> +</html> + diff --git a/dom/plugins/test/reftest/div-sanity.html b/dom/plugins/test/reftest/div-sanity.html new file mode 100644 index 0000000000..9ffa539191 --- /dev/null +++ b/dom/plugins/test/reftest/div-sanity.html @@ -0,0 +1,17 @@ +<!doctype html> +<html><head> +<title>div boxes</title> +<style> +div { + width: 400px; + height: 400px; + display: inline-block; +} +</style> +</head> +<body> +<div style="background-color: #FF0000;"></div> <!-- red --> +<div style="background-color: #00FF00;"></div> <!-- green --> +<div style="background-color: #0000FF;"></div> <!-- blue --> +<div style="background-color: #999999;"></div> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-alpha-opacity.html b/dom/plugins/test/reftest/plugin-alpha-opacity.html new file mode 100644 index 0000000000..2db6cc4de3 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-opacity.html @@ -0,0 +1,29 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + width:404px; height:404px; + border:2px solid blue; + opacity:.8; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="FFa0a0a0"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-alpha-zindex.html b/dom/plugins/test/reftest/plugin-alpha-zindex.html new file mode 100644 index 0000000000..ead9b6f4ce --- /dev/null +++ b/dom/plugins/test/reftest/plugin-alpha-zindex.html @@ -0,0 +1,26 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> +<div id="two"></div> +<embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00" id="p1"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-background-1-step.html b/dom/plugins/test/reftest/plugin-background-1-step.html new file mode 100644 index 0000000000..9498633b41 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-1-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 1; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-10-step.html b/dom/plugins/test/reftest/plugin-background-10-step.html new file mode 100644 index 0000000000..7a0824a565 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-10-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 10; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-2-step.html b/dom/plugins/test/reftest/plugin-background-2-step.html new file mode 100644 index 0000000000..cc186a5f29 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-2-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 2; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-5-step.html b/dom/plugins/test/reftest/plugin-background-5-step.html new file mode 100644 index 0000000000..2630719c88 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-5-step.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> + <script type="text/javascript"> +var NUM_STEPS = 5; + </script> + <script type="text/javascript" src="plugin-background.js"></script> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background-ref.html b/dom/plugins/test/reftest/plugin-background-ref.html new file mode 100644 index 0000000000..651fdecef5 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background-ref.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<body> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <div id="plugin"></div> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.css b/dom/plugins/test/reftest/plugin-background.css new file mode 100644 index 0000000000..f6b251214d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.css @@ -0,0 +1,61 @@ +div { + position: absolute; +} +#bad { + left:220px; top:0px; + z-index: 0; +} +#good { + left:0px; top:0px; + width:220px; height:220px; + /* Core Animation alpha blending rounding differs + from the Core Graphics blending, adjust with care */ + background-color: rgba(0,255,0, 0.51); + z-index: 0; +} + +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#topbar { + left:0px; top:0px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} +#leftbar { + left:0px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#rightbar { + left:200px; top:0px; + width:20px; height:220px; + background-color: rgb(0,0,0); + z-index: 2; +} +#bottombar { + left:0px; top:200px; + width:220px; height:20px; + background-color: rgb(0,0,0); + z-index: 2; +} + +div#plugin { + position: absolute; + left:1px; top:1px; + width:199px; height:199px; + background-color: rgba(0,0,255, 0.2); + z-index: 1; +} + +embed#plugin { + position: absolute; + left:1px; top:1px; + z-index: 1; +} + diff --git a/dom/plugins/test/reftest/plugin-background.html b/dom/plugins/test/reftest/plugin-background.html new file mode 100644 index 0000000000..4cd1e3f538 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <link rel="stylesheet" type="text/css" href="plugin-background.css"></link> +</head> +<script src="utils.js"> +</script> +<body onLoad="forceLoadPlugin('plugin')"> + <div id="bad">Test some plugin stuff.</div> + <div id="good"></div> + + <embed id="plugin" type="application/x-test" width="199" height="199" + drawmode="solid" color="330000FF"></embed> + + <div id="topbar"></div> + <div id="leftbar"></div> + <div id="rightbar"></div> + <div id="bottombar"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/plugin-background.js b/dom/plugins/test/reftest/plugin-background.js new file mode 100644 index 0000000000..8c6d28572d --- /dev/null +++ b/dom/plugins/test/reftest/plugin-background.js @@ -0,0 +1,73 @@ +// The including script sets this for us +//var NUM_STEPS; + +var plugin; +var left = 1, top = 1, width = 199, height = 199; +function movePluginTo(x, y, w, h) { + left = x; top = y; width = w; height = h; + plugin.width = w; + plugin.height = h; + plugin.style.left = left + "px"; + plugin.style.top = top + "px"; +} +function deltaInBounds(dx,dy, dw,dh) { + var l = dx + left; + var r = l + width + dw; + var t = dy + top; + var b = t + height + dh; + return (0 <= l && l <= 20 && + 0 <= t && t <= 20 && + 200 <= r && r <= 220 && + 200 <= b && b <= 220); +} + +function start() { + window.removeEventListener("MozReftestInvalidate", start); + + window.addEventListener("MozAfterPaint", step); + window.addEventListener("MozPaintWaitFinished", step); + + plugin = document.getElementById("plugin"); + + movePluginTo(0,0, 200,200); +} + +var steps = 0; +var which = "move"; // or "grow" +var dx = 1, dy = 1, dw = 1, dh = 1; +function step() { + if (++steps >= NUM_STEPS) { + window.removeEventListener("MozAfterPaint", step); + window.removeEventListener("MozPaintWaitFinished", step); + return finish(); + } + + var didSomething = false; + if (which == "grow") { + if (deltaInBounds(0,0, dw,dh)) { + movePluginTo(left,top, width+dw, height+dh); + didSomething = true; + } else { + dw = -dw; dh = -dh; + } + } else { + // "move" + if (deltaInBounds(dx,dy, 0,0)) { + movePluginTo(left+dx,top+dy, width, height); + didSomething = true; + } else { + dx = -dx; dy = -dy; + } + } + which = (which == "grow") ? "move" : "grow"; + + if (!didSomething) { + step(); + } +} + +function finish() { + document.documentElement.removeAttribute("class"); +} + +window.addEventListener("MozReftestInvalidate", start); diff --git a/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html new file mode 100644 index 0000000000..e339dd2669 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-busy-alpha-zindex.html @@ -0,0 +1,56 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:4; +} +#two { + position:absolute; + top:100px; left:100px; + background-color:rgb(0,0,0,0); + z-index:3; +} +#three { + position:absolute; + left:100px; top:100px; + width:200px; height:200px; + background-color: rgb(255,0,0); + opacity:0.6; + z-index:2; +} +#four { + position:absolute; + top:100px; left:100px; + z-index:1; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgba(255,0,0, 0.6)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin(['one', 'four']); + } + </script> +</style> +</head> +<body onload="doTest();"> + <embed id="four" type="application/x-test" width="200" height="200" + drawmode="solid" color="FFFF0000"></embed> + <div id="three"></div> + <canvas id="two" width="200" height="200"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" + drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html new file mode 100644 index 0000000000..517099d1b1 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-canvas-alpha-zindex.html @@ -0,0 +1,41 @@ +<!doctype html> +<html class="reftest-wait"> +<head> + <style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:100px; left:100px; +// Set these using the canvas API +// width:200px; height:200px; +// background-color: rgb(255,0,0); + z-index:0; +} + </style> + <script src="utils.js" type="text/javascript"> + </script> + <script type="text/javascript"> +function paintCanvas() { + var canvas = document.getElementById("two"); + var ctx = canvas.getContext("2d"); + ctx.fillStyle = "rgb(255,0,0)"; + ctx.fillRect(0,0, 200,200); +} + + function doTest() { + paintCanvas(); + forceLoadPlugin('one'); + } + </script> +</style> +</head> +<body onload="doTest()"> + <canvas width="200" height="200" id="two"></canvas> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/plugin-sanity.html b/dom/plugins/test/reftest/plugin-sanity.html new file mode 100644 index 0000000000..4f9c30eee4 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-sanity.html @@ -0,0 +1,13 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<title>Plugin boxes</title> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2', 'p3', 'p4'])"> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FFFF0000" id="p1"></embed> <!-- red --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF00FF00" id="p2"></embed> <!-- green --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF0000FF" id="p3"></embed> <!-- blue --> +<embed type="application/x-test" width="400" height="400" drawmode="solid" color="FF999999" id="p4"></embed> <!-- gray --> +</body></html> diff --git a/dom/plugins/test/reftest/plugin-transform-1-ref.html b/dom/plugins/test/reftest/plugin-transform-1-ref.html new file mode 100644 index 0000000000..259a78b41b --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-1.html b/dom/plugins/test/reftest/plugin-transform-1.html new file mode 100644 index 0000000000..19f6e8c20f --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-1.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p')">
+<embed type="application/x-test" style="-moz-transform:scale(1)" id="p"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2-ref.html b/dom/plugins/test/reftest/plugin-transform-2-ref.html new file mode 100644 index 0000000000..93a3924d7e --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2-ref.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML>
+<html>
+<body>
+<div style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); background:rgb(0,255,0)"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-2.html b/dom/plugins/test/reftest/plugin-transform-2.html new file mode 100644 index 0000000000..7f48640c19 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-2.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('one')">
+<embed type="application/x-test" drawmode="solid" color="FF00FF00"
+ style="width:100px; height:100px; -moz-transform-origin:top left;
+ -moz-transform:scale(2); display:block"
+ id="one"></embed>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html new file mode 100644 index 0000000000..52fda4bcf9 --- /dev/null +++ b/dom/plugins/test/reftest/plugin-transform-alpha-zindex.html @@ -0,0 +1,28 @@ +<!doctype html> +<html class="reftest-wait"> +<head> +<style type="text/css"> +#one { + position:absolute; + left:0px; top:0px; + z-index:1; +} +#two { + position:absolute; + top:0px; left:0px; + width:200px; height:200px; + z-index:0; + background-color: rgb(255,0,0); + -moz-transform-origin: 0 0; + -moz-transform: translate(100px,100px); +} +</style> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('one')"> + <div id="two"></div> + <embed id="one" type="application/x-test" width="400" height="400" drawmode="solid" color="9900FF00"></embed> +</body> +</html> + diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html new file mode 100644 index 0000000000..fafec34f43 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1-ref.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-1.html b/dom/plugins/test/reftest/pluginproblemui-direction-1.html new file mode 100644 index 0000000000..9888850dc9 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-1.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html new file mode 100644 index 0000000000..e807b86b5b --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2-ref.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="ltr" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/pluginproblemui-direction-2.html b/dom/plugins/test/reftest/pluginproblemui-direction-2.html new file mode 100644 index 0000000000..95b358e372 --- /dev/null +++ b/dom/plugins/test/reftest/pluginproblemui-direction-2.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-expect-process-crash reftest-wait"> + <head> + <title>Plugin Problem UI directionality test</title> + </head> + <body dir="rtl" style="text-align: left;"> + <!-- a variant of pluginproblemui-direction-1.html that covers up + the spot where we get random variation with d2d, so that we + can still test it with d2d --> + <div style="position: absolute; width: 1px; height: 1px; background: red; z-index: 1; left: 401px; top: 19px;"></div> + <embed type="application/x-test" width="400" height="400" id="crashme"></embed> + <script type="text/javascript"> + var plugin = document.getElementById("crashme"); + function checkForCrashUI() { + if (getComputedStyle(plugin, null).MozBinding != "none") { + document.documentElement.classList.remove("reftest-wait"); + clearInterval(interval); + } + } + + var interval = setInterval(checkForCrashUI, 100); + setTimeout(function() { plugin.crash(); }, 0); + </script> + </body> +</html> diff --git a/dom/plugins/test/reftest/reftest.list b/dom/plugins/test/reftest/reftest.list new file mode 100644 index 0000000000..2db57bab37 --- /dev/null +++ b/dom/plugins/test/reftest/reftest.list @@ -0,0 +1,26 @@ +# basic sanity checking +random-if(!haveTestPlugin) HTTP != plugin-sanity.html about:blank +fails-if(!haveTestPlugin) HTTP == plugin-sanity.html div-sanity.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-164000) HTTP == plugin-alpha-opacity.html div-alpha-opacity.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832 +# fuzzy because of anti-aliasing in dashed border +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-1.html border-padding-1-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) HTTP == border-padding-2.html border-padding-2-ref.html # bug 629430 +fuzzy(0-16,0-256) random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin||Android) HTTP == border-padding-3.html border-padding-3-ref.html # bug 629430 # bug 773482 +# The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced. +#random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367 +#random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) HTTP == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-canvas-alpha-zindex.html div-alpha-zindex.html +fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-transform-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-160000) HTTP == plugin-busy-alpha-zindex.html div-alpha-zindex.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-1-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-2-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-5-step.html plugin-background-ref.html +random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) skip-if(!haveTestPlugin) fuzzy-if(skiaContent&&haveTestPlugin,0-1,0-32400) HTTP == plugin-background-10-step.html plugin-background-ref.html +random-if(!haveTestPlugin) HTTP == plugin-transform-1.html plugin-transform-1-ref.html +fails-if(!haveTestPlugin) HTTP == plugin-transform-2.html plugin-transform-2-ref.html +skip-if(!haveTestPlugin) HTTP == shrink-1.html shrink-1-ref.html +pref(dom.mozPaintCount.enabled,true) skip-if(!haveTestPlugin) HTTP == update-1.html update-1-ref.html +skip-if(!haveTestPlugin) HTTP == windowless-layers.html windowless-layers-ref.html diff --git a/dom/plugins/test/reftest/shrink-1-ref.html b/dom/plugins/test/reftest/shrink-1-ref.html new file mode 100644 index 0000000000..0906fe5789 --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + width="50px" height="40px"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/shrink-1.html b/dom/plugins/test/reftest/shrink-1.html new file mode 100644 index 0000000000..a277e1afaa --- /dev/null +++ b/dom/plugins/test/reftest/shrink-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <script src="utils.js"> + </script> + <script> +function doShrink() +{ + var plugin = document.getElementById("plugin"); + plugin.setSlowPaint(true); + plugin.width = "50"; + plugin.height = "40"; + + document.documentElement.removeAttribute("class"); +} + +document.addEventListener("MozReftestInvalidate", doShrink); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + width="300" height="500"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1-ref.html b/dom/plugins/test/reftest/update-1-ref.html new file mode 100644 index 0000000000..7303d19840 --- /dev/null +++ b/dom/plugins/test/reftest/update-1-ref.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('plugin')"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FFFF0000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/update-1.html b/dom/plugins/test/reftest/update-1.html new file mode 100644 index 0000000000..cff85bc9f0 --- /dev/null +++ b/dom/plugins/test/reftest/update-1.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <title>Test for bugs 807728 and 810426</title> + <script src="utils.js"> + </script> + <script> +function start() +{ + document.removeEventListener("MozReftestInvalidate", start); + + var plugin = document.getElementById("plugin"); + var color = "FF000000"; + var color_count = 0; + var last_paint_count = 0; + // Enough paints to test reusing a surface after it has been + // moved from front to back buffer. + // FIXME: Stop using mozPaintCount for this test. + // Can't make it a chrome:// url because it needs http, thus no + // SpecialPowers :/ + var final_paint_count = window.mozPaintCount + 10; + var final_color = "FFFF0000"; + + // Using mozPaintCount to track when content has been updated as an + // indication that the browser has received updates, instead of + // plugin.getPaintCount() which tracks when the plugin sends updates or + // MozAfterPaint events which track OS paints. Not using + // MozPaintWaitFinished because invalidation causes no geometry changes. + function wait_for_paints() { + var paint_count = window.mozPaintCount; + if (paint_count >= final_paint_count && color == final_color) { + document.documentElement.removeAttribute("class"); + return; + } + if (paint_count != last_paint_count) { + last_paint_count = paint_count; + if (paint_count + 1 >= final_paint_count) { + color = final_color; + // Wait for the paint with the final color + final_paint_count = paint_count + 1; + } else { + ++color_count; + color = "FF00000" + color_count; + } + plugin.setColor(color); + } + setTimeout(wait_for_paints, 0); + } + + wait_for_paints(); +} + +// MozReftestInvalidate is delivered after initial painting has settled. +document.addEventListener("MozReftestInvalidate", start); + </script> +</head> +<body onLoad="forceLoadPlugin('plugin', true)"> + <embed id="plugin" type="application/x-test" + drawmode="solid" color="FF000000" width="30" height="50"> + </embed> +</body> +</html> diff --git a/dom/plugins/test/reftest/utils.js b/dom/plugins/test/reftest/utils.js new file mode 100644 index 0000000000..f046089afc --- /dev/null +++ b/dom/plugins/test/reftest/utils.js @@ -0,0 +1,18 @@ +function forceLoadPluginElement(id) { + var e = document.getElementById(id); + var found = e.pluginFoundElement; +} + +function forceLoadPlugin(ids, skipRemoveAttribute) { + if (Array.isArray(ids)) { + ids.forEach(function(element, index, array) { + forceLoadPluginElement(element); + }); + } else { + forceLoadPluginElement(ids); + } + if (skipRemoveAttribute) { + return; + } + document.documentElement.removeAttribute("class"); +} diff --git a/dom/plugins/test/reftest/windowless-clipping-1-ref.html b/dom/plugins/test/reftest/windowless-clipping-1-ref.html new file mode 100644 index 0000000000..e59ecb79b2 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1-ref.html @@ -0,0 +1,14 @@ +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> +<div style="width:100px; height:100px; overflow:auto;"> + <div style="width:100px; height:100px; overflow:hidden;"> + <embed type="application/x-test" style="width:200px;" id="p1"></embed> + </div> +</div> +<div style="width:100px; height:100px; background-color:lime;"></div> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-clipping-1.html b/dom/plugins/test/reftest/windowless-clipping-1.html new file mode 100644 index 0000000000..dc1c25ac10 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-clipping-1.html @@ -0,0 +1,15 @@ +<html class="reftest-wait">
+<head>
+<script src="utils.js">
+</script>
+</head>
+<body onLoad="forceLoadPlugin('p1')">
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"></embed>
+</div>
+<div style="width:100px; height:100px; overflow:hidden;">
+ <embed type="application/x-test" style="width:200px;"
+ drawmode="solid" color="ff00ff00" id="p1"></embed>
+</div>
+</body>
+</html>
diff --git a/dom/plugins/test/reftest/windowless-layers-ref.html b/dom/plugins/test/reftest/windowless-layers-ref.html new file mode 100644 index 0000000000..765527b68f --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin('p1')"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> +</body> +</html> diff --git a/dom/plugins/test/reftest/windowless-layers.html b/dom/plugins/test/reftest/windowless-layers.html new file mode 100644 index 0000000000..9e24c13a68 --- /dev/null +++ b/dom/plugins/test/reftest/windowless-layers.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script src="utils.js"> +</script> +</head> +<body onLoad="forceLoadPlugin(['p1', 'p2'])"> + <div style="width:200px; height:200px; overflow:hidden; position:absolute; z-index:1"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p1"> + </div> + <div style="width:200px; height:100px; overflow:hidden; position:absolute; z-index:2"> + <embed type="application/x-test" style="width:200px; height:200px;" id="p2"> + </div> +</body> +</html> diff --git a/dom/plugins/test/testplugin/Info.plist b/dom/plugins/test/testplugin/Info.plist new file mode 100644 index 0000000000..dc6aa5cec3 --- /dev/null +++ b/dom/plugins/test/testplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnptest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.TestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>TEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Plug-in for testing purposes.™ (हिन्दी 中文 العربية)</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>tst</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Test ™ mimetype</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/README b/dom/plugins/test/testplugin/README new file mode 100644 index 0000000000..993de360ce --- /dev/null +++ b/dom/plugins/test/testplugin/README @@ -0,0 +1,424 @@ += Instructions for using the test plugin = + +== MIME type == + +The test plugin registers itself for the MIME type "application/x-test". + +== Event Model == + +* getEventModel() +Returns the NPAPI event model in use. On platforms without event models, +simply returns 0; + +== Rendering == + +By default, the plugin fills its rectangle with gray, with a black border, and +renders the user-agent string (obtained from NPN_UserAgent) centered in black. +This rendering method is not supported for the async drawing models. + +The test plugin supports the following parameters: + +* drawmode="solid" +The plugin will draw a solid color instead of the default rendering described +above. The default solid color is completely transparent black (i.e., nothing). +This should be specified when using one of the async models. + +* asyncmodel="bitmap" +The plugin will use the NPAPI Async Bitmap drawing model extension. On +unsupported platforms this will fallback to non-async rendering. + +* asyncmodel="dxgi" +The plugin will use the NPAPI Async DXGI drawing model extension. Only +supported on Windows Vista or higher. On unsupported platforms this will +fallback to non-async rendering. + +* color +This specifies the color to use for drawmode="solid". The value should be 8 hex +digits, 2 per channel in AARRGGBB format. + +== Generic API Tests == + +* setUndefinedValueTest +Attempts to set the value of an undefined variable (0x0) via NPN_SetValue, +returns true if it succeeds and false if it doesn't. It should never succeed. + +* .getReflector() +Hands back an object which reflects properties as values, e.g. + .getReflector().foo = 'foo' + .getReflector()['foo'] = 'foo' + .getReflector()[1] = 1 + +* .getNPNVdocumentOrigin() +Returns the origin string retrieved from the browser by a NPNVdocumentOrigin +variable request. Does not cache the value, gets it from the browser every time. + +== NPN_ConvertPoint testing == + +* convertPointX(sourceSpace, sourceX, sourceY, destSpace) +* convertPointY(sourceSpace, sourceX, sourceY, destSpace) +The plugin uses NPN_ConvertPoint to convert sourceX and sourceY from the source +to dest space and returns the X or Y result based on the call. + +== NPCocoaEventWindowFocusChanged == + +* getTopLevelWindowActivationState() +Returns the activation state for the top-level window as set by the last +NPCocoaEventWindowFocusChanged event. Returns true for active, false for +inactive, and throws an exception if the state is unknown (uninitialized). + +* getTopLevelWindowActivationEventCount() +Returns the number of NPCocoaEventWindowFocusChanged events received by +the instance. + +== Focus Tests == + +* getFocusState() +Returns the plugin's focus state. Returns true for focused, false for unfocused, +and throws an exception if the state is unknown (uninitialized). This does not +necessarily correspond to actual input focus - this corresponds to focus as +defined by the NPAPI event model in use. + +* getFocusEventCount() +Returns the number of focus events received by the instance. + +== NPRuntime testing == + +The test plugin object supports the following scriptable methods: + +* identifierToStringTest(ident) +Converts a string, int32 or double parameter 'ident' to an NPIdentifier and +then to a string, which is returned. + +* npnEvaluateTest(script) +Calls NPN_Evaluate on the 'script' argument, which is a string containing +some script to be executed. Returns the result of the evaluation. + +* npnInvokeTest(method, expected, args...) +Causes the plugin to call the specified script method using NPN_Invoke, +passing it 1 or more arguments specified in args. The return value of this +call is compared against 'expected', and if they match, npnInvokeTest will +return true. Otherwise, it will return false. + +* npnInvokeDefaultTest(object, argument) +Causes the plugin to call NPN_InvokeDefault on the specified object, +with the specified argument. Returns the result of the invocation. + +* getError() +If an error has occurred during the last stream or npruntime function, +this will return a string error message, otherwise it returns "pass". + +* throwExceptionNextInvoke() +Sets a flag which causes the next call to a scriptable method to throw +one or more exceptions. If no parameters are passed to the next +scriptable method call, it will cause a generic exception to be thrown. +Otherwise there will be one exception thrown per argument, with the argument +used as the exception message. Example: + + plugin.throwExceptionNextInvoke(); + plugin.npnInvokeTest("first exception message", "second exception message"); + +* () - default method +Returns a string consisting of the plugin name, concatenated with any +arguments passed to the method. + +* .crash() - Crashes the plugin + +* getObjectValue() - Returns a custom plugin-implemented scriptable object. +* checkObjectValue(obj) - Returns true if the object from getObjectValue() is + the same object passed into this function. It should return true when + the object is passed to the same plugin instance, and false when passed + to other plugin instances, see bug 532246 and + test_multipleinstanceobjects.html. + +* callOnDestroy(fn) - Calls `fn` when the plugin instance is being destroyed + +* getAuthInfo(protocol, host, port, scheme, realm) - a wrapper for +NPN_GetAuthenticationInfo(). Returns a string "username|password" for +the specified auth criteria, or throws an exception if no data is +available. + +* timerTest(callback) - initiates tests of NPN_ScheduleTimer & +NPN_UnscheduleTimer. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* asyncCallbackTest(callback) - initiates tests of +NPN_PluginThreadAsyncCall. When finished, calls the script callback +with a boolean value, indicating whether the tests were successful. + +* paintscript="..." content attribute +If the "paintscript" attribute is set on the plugin element during plugin +initialization, then every time the plugin paints it gets the contents of that +attribute and evaluates it as a script in the context of the plugin's DOM +window. This is useful for testing evil plugin code that might, for example, +modify the DOM during painting. + +== Private browsing == + +The test plugin object supports the following scriptable methods: + +* queryPrivateModeState +Returns the value of NPN_GetValue(NPNVprivateModeBool). + +* lastReportedPrivateModeState +Returns the last value set by NPP_SetValue(NPNVprivateModeBool). + +== Windowed/windowless mode == + +The test plugin is windowless by default. + +The test plugin supports the following parameter: + +* wmode="window" +The plugin will be given a native widget on platforms where we support this +(Windows and X). + +The test plugin object supports the following scriptable method: + +* hasWidget() +Returns true if the plugin has an associated widget. This will return true if +wmode="window" was specified and the platform supports windowed plugins. + +== Plugin invalidation == + +* setColor(colorString) +Sets the color used for drawmode="solid" and invalidates the plugin area. +This calls NPN_Invalidate, even for windowed plugins, since that should work +for windowed plugins too (Silverlight depends on it). + +* getPaintCount() +Returns the number of times this plugin instance has processed a paint request. +This can be used to detect whether painting has happened in a plugin's +window. + +* getWidthAtLastPaint() +Returns the window width that was current when the plugin last painted. + +* setInvalidateDuringPaint(value) +When value is true, every time the plugin paints, it will invalidate +itself *during the paint* using NPN_Invalidate. + +* setSlowPaint(value) +When value is true, the instance will sleep briefly during paint. + +== Plugin geometry == + +The test plugin supports the following scriptable methods: + +* getEdge(edge) +Returns the integer screen pixel coordinate of an edge of the plugin's +area: +-- edge=0: returns left edge coordinate +-- edge=1: returns top edge coordinate +-- edge=2: returns right edge coordinate +-- edge=3: returns bottom edge coordinate +The coordinates are relative to the top-left corner of the top-level window +containing the plugin, including the window decorations. Therefore: +-- On Mac, they're relative to the top-left corner of the toplevel Cocoa +window. +-- On Windows, they're relative to the top-left corner of the toplevel HWND's +non-client area. +-- On GTK2, they're relative to the top-left corner of the toplevel window's +window manager frame. +This means they can be added to Gecko's window.screenX/screenY (if DPI is set +to 96) to get screen coordinates. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. + +* getClipRegionRectCount() +Returns the number of rectangles in the plugin's clip region. +For plugins with widgets, the clip region is computed as the intersection of the +clip region for the widget (if the platform does not support clip regions +on native widgets, this would just be the widget's rectangle) with the +clip regions of all ancestor widgets which would clip this widget. +On the platforms that support window-mode plugins (Windows/GTK2), this only +works for window-mode plugins. It will throw an error for windowless plugins. +On Mac, all plugins have a clip region containing just a single clip +rectangle only. So if you request wmode="window" but the plugin reports +!hasWidget, you can assume that complex clip regions are not supported. + +* getClipRegionRectEdge(i, edge) +Returns the integer screen pixel coordinate of an edge of a rectangle from the +plugin's clip region. If i is less than zero or greater than or equal to +getClipRegionRectCount(), this will throw an error. The coordinates are +the same as for getEdge. See getClipRegionRectCount() above for +notes on platform plugin limitations. + +== Keyboard events == + +* getLastKeyText() +Returns the text which was inputted by last keyboard events. This is cleared at +every keydown event. +NOTE: Currently, this is implemented only on Windows. + +== Mouse events == + +The test plugin supports the following scriptable methods: + +* getLastMouseX() +Returns the X coordinate of the last mouse event (move, button up, or +button down), relative to the left edge of the plugin, or -1 if no mouse +event has been received. + +* getLastMouseX() +Returns the Y coordinate of the last mouse event (move, button up, or +button down), relative to the top edge of the plugin, or -1 if no mouse +event has been received. + +== Instance lifecycle == + +The test plugin supports the following scriptable methods: + +* startWatchingInstanceCount() +Marks all currently running instances as "ignored". Throws an exception if +there is already a watch (startWatchingInstanceCount has already been +called on some instance without a corresponding stopWatchingInstanceCount). + +* getInstanceCount() +Returns the count of currently running instances that are not ignored. +Throws an exception if there is no watch. + +* stopWatchingInstanceCount() +Stops watching. Throws an exception if there is no watch. + +== NPAPI Timers == + +* unscheduleAllTimers() +Instructs the plugin instance to cancel all timers created via +NPN_ScheduleTimer. + +== Stream Functionality == + +The test plugin enables a variety of NPAPI streaming tests, which are +initiated by passing a variety of attributes to the <embed> element which +causes the plugin to be initialized. The plugin stream test code is +designed to receive a stream from the browser (by specifying a "src", +"geturl", or "geturlnotify" attribute), and then (if a "frame" attribute +is specified) send the data from that stream back to the browser in another +stream, whereupon it will be displayed in the specified frame. If some +error occurs during stream processing, an error message will appear in the +frame instead of the stream data. If no "frame" attribute is present, a +stream can still be received by the plugin, but the plugin will do nothing +with it. + +The attributes which control stream tests are: + +"streamchunksize": the number of bytes the plugin reports it can accept + in calls to NPP_WriteReady. Defaults to 1,024. + +"src": a url. If specified, the browser will call NPP_NewStream for + this url as soon as the plugin is initialized. + +"geturl": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURL. + +"geturlnotify": a url. If specified, the plugin will request this url + from the browser when the plugin is initialized, via a call to + NPN_GetURLNotify. The plugin passes some "notifyData" to + NPN_GetURLNotify, which it verifies is present in the call to + NPP_URLNotify. If the "notifyData" does not match, an error + will be displayed in the test frame (if any), instead of the stream + data. + +"frame": the name of a frame in the same HTML document as the <embed> + element which instantiated the plugin. For any of the preceding three + attributes, a stream is received by the plugin via calls to NPP_NewStream, + NPP_WriteReady, NPP_Write, and NPP_DestroyStream. When NPP_DestroyStream + is called (or NPP_UrlNotify, in the case of "geturlnotify"), and a + "frame" attribute is present, the data from the stream is converted into a + data: url, and sent back to the browser in another stream via a call to + NPN_GetURL, whereupon it should be displayed in the specified frame. + +"posturl": a url. After the plugin receives a stream, and NPP_DestroyStream + is called, if "posturl" is specified, the plugin will post the contents + of the stream to the specified url via NPN_PostURL. See "postmode" for + additional details. + +"postmode": either "frame" or "stream". If "frame", and a "frame" attribute + is present, the plugin will pass the frame name to calls to NPN_PostURL, + so that the HTTP response from that operation will be displayed in the + specified frame. If "stream", the HTTP response is delivered to the plugin + via calls to NPP_NewStream etc, and if a "frame" attribute is present, the + contents of that stream will be passed back to the browser and displayed + in the specified frame via NPN_GetURL. + +"newstream": if "true", then any stream which is sent to a frame in the browser + is sent via calls to NPN_NewStream and NPN_Write. Doing so will cause + the browser to store the stream data in a file, and set the frame's + location to the corresponding file:// url. + +"functiontofail": one of "npp_newstream", "npp_write", "npp_destroystream". + When specified, the given function will return an error code (-1 for + NPP_Write, or else the value of the "failurecode" attribute) the first time + it is called by the browser. + +"failurecode": one of the NPError constants. Used to specify the error + that will be returned by the "functiontofail". + +* streamTest(url, doPost, postData, writeCallback, notifyCallback, redirectCallback, allowRedirects, postFile = false) +This will test how NPN_GetURLNotify and NPN_PostURLNotify behave when they are +called with arbitrary (malformed) URLs. The function will return `true` if +NPN_[Get/Post]URLNotify succeeds, and `false` if it fails. +@url url to request +@param doPost whether to call NPN_PostURLNotify +@param postData null, or a string to send a postdata +@writeCallback will be called when data is received for the stream +@notifyCallback will be called when the urlnotify is received with the notify result +@redirectCallback will be called from urlredirectnotify if a redirect is attempted +@allowRedirects boolean value indicating whether or not to allow redirects +@postFile boolean optional, defaults to false, set to true if postData contains a filename + +* postFileToURLTest(url) +Calls NPN_PostURL/NPN_PostURLNotify to make a POST request to the URL with +request from postFile. +The function will return `0` if NPN_PostURL/NPN_PostURLNotify succeeds, and +the error code if it fails. +@param url string, url to request + +* setPluginWantsAllStreams(wantsAllStreams) +Set the value returned by the plugin for NPPVpluginWantsAllNetworkStreams. + +== Internal consistency == + +* doInternalConsistencyCheck() +Does internal consistency checking, returning an empty string if everything is +OK, otherwise returning some kind of error string. On Windows, in windowed +mode, this checks that the position of the plugin's internal child +window has not been disturbed relative to the plugin window. + +== Windows native widget message behaviour == + +* Mouse events are handled (saving the last mouse event coordinate) and not +passed to the overridden windowproc. + +* WM_MOUSEWHEEL events are handled and not passed to the parent window or the +overridden windowproc. + +* WM_MOUSEACTIVATE events are handled by calling SetFocus on the plugin's +widget, if the plugin is windowed. If it's not windowed they're passed to +the overriden windowproc (but hopefully never sent by the browser anyway). + +== FPU Control == + +x86-only on some OSes: + +* The .enableFPExceptions() method will enable floating-point exceptions, + as evil plugins or extensions might do. + +== HiDPI Mode == + +* queryContentsScaleFactor() +Returns the contents scale factor. On platforms without support for this query +always returns 1.0 (a double value). Likewise on hardware without HiDPI mode +support. + +== Plugin audio channel support == + +* startAudioPlayback() +Simulates the plugin starting to play back audio. + +* stopAudioPlayback() +Simulates the plugin stopping to play back audio. + +* audioMuted() +Returns the last value set by NPP_SetValue(NPNVmuteAudioBool). diff --git a/dom/plugins/test/testplugin/flashplugin/Info.plist b/dom/plugins/test/testplugin/flashplugin/Info.plist new file mode 100644 index 0000000000..0e6168e686 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpswftest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.FlashTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>FLASHTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Shockwave Flash</string> + <key>WebPluginDescription</key> + <string>Flash plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-shockwave-flash-test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>swf</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Flash test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/flashplugin/moz.build b/dom/plugins/test/testplugin/flashplugin/moz.build new file mode 100644 index 0000000000..f66fb6eca4 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npswftest") + +relative_path = "flashplugin" +cocoa_name = "npswftest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.def b/dom/plugins/test/testplugin/flashplugin/nptest.def new file mode 100644 index 0000000000..3a62d05d95 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSWFTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/flashplugin/nptest.rc b/dom/plugins/test/testplugin/flashplugin/nptest.rc new file mode 100644 index 0000000000..e970d26091 --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Flash plug-in for testing purposes." + VALUE "FileExtents", "swf" + VALUE "FileOpenName", "Flash test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npswftest" + VALUE "MIMEType", "application/x-shockwave-flash-test" + VALUE "OriginalFilename", "npswftest.dll" + VALUE "ProductName", "Shockwave Flash" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp new file mode 100644 index 0000000000..31f4f6321f --- /dev/null +++ b/dom/plugins/test/testplugin/flashplugin/nptest_name.cpp @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const char* sPluginName = "Shockwave Flash"; +const char* sPluginDescription = "Flash plug-in for testing purposes."; +const char* sMimeDescription = + "application/x-shockwave-flash-test:swf:Flash test type"; diff --git a/dom/plugins/test/testplugin/moz.build b/dom/plugins/test/testplugin/moz.build new file mode 100644 index 0000000000..cb380e2db8 --- /dev/null +++ b/dom/plugins/test/testplugin/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ["secondplugin", "flashplugin"] + +SharedLibrary("nptest") + +relative_path = "." +cocoa_name = "Test" +include("testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/nptest.cpp b/dom/plugins/test/testplugin/nptest.cpp new file mode 100644 index 0000000000..2a9d20cdca --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.cpp @@ -0,0 +1,3282 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Dave Townsend <dtownsend@oxymoronical.com> + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest.h" +#include "nptest_utils.h" +#include "nptest_platform.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntentionalCrash.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <iostream> +#include <string> +#include <sstream> +#include <list> +#include <ctime> + +#ifdef XP_WIN +# include <process.h> +# include <float.h> +# include <windows.h> +# define getpid _getpid +# define strcasecmp _stricmp +#else +# include <unistd.h> +# include <pthread.h> +#endif + +using std::list; +using std::ostringstream; +using std::string; + +#define PLUGIN_VERSION "1.0.0.0" + +extern const char* sPluginName; +extern const char* sPluginDescription; +static char sPluginVersion[] = PLUGIN_VERSION; + +// +// Intentional crash +// + +int gCrashCount = 0; + +static void Crash() { + int* pi = nullptr; + *pi = 55; // Crash dereferencing null pointer + ++gCrashCount; +} + +static void IntentionalCrash() { + mozilla::NoteIntentionalCrash("plugin"); + Crash(); +} + +// +// static data +// + +static NPNetscapeFuncs* sBrowserFuncs = nullptr; +static NPClass sNPClass; + +// +// identifiers +// + +typedef bool (*ScriptableFunction)(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool hangPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stallPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getClipboardText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool callOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool reinitWidget(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool triggerXError(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getTopLevelWindowActivationState(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result); +static bool getFocusState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getEventModel(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getReflector(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result); +static bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool constructObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithData(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastKeyText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool echoString(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); +static bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +static const NPUTF8* sPluginMethodIdentifierNames[] = { + "npnEvaluateTest", + "npnInvokeTest", + "npnInvokeDefaultTest", + "setUndefinedValueTest", + "identifierToStringTest", + "timerTest", + "queryPrivateModeState", + "lastReportedPrivateModeState", + "hasWidget", + "getEdge", + "getClipRegionRectCount", + "getClipRegionRectEdge", + "startWatchingInstanceCount", + "getInstanceCount", + "stopWatchingInstanceCount", + "getLastMouseX", + "getLastMouseY", + "getPaintCount", + "resetPaintCount", + "getWidthAtLastPaint", + "setInvalidateDuringPaint", + "setSlowPaint", + "getError", + "doInternalConsistencyCheck", + "setColor", + "throwExceptionNextInvoke", + "convertPointX", + "convertPointY", + "streamTest", + "postFileToURLTest", + "setPluginWantsAllStreams", + "crash", + "crashOnDestroy", + "getObjectValue", + "checkObjectValue", + "enableFPExceptions", + "hang", + "stall", + "getClipboardText", + "callOnDestroy", + "reinitWidget", + "crashInNestedLoop", + "triggerXError", + "destroySharedGfxStuff", + "propertyAndMethod", + "getTopLevelWindowActivationState", + "getTopLevelWindowActivationEventCount", + "getFocusState", + "getFocusEventCount", + "getEventModel", + "getReflector", + "isVisible", + "getWindowPosition", + "constructObject", + "setSitesWithData", + "setSitesWithDataCapabilities", + "getLastKeyText", + "getNPNVdocumentOrigin", + "getMouseUpEventCount", + "queryContentsScaleFactor", + "queryCSSZoomFactorSetValue", + "queryCSSZoomFactorGetValue", + "echoString", + "startAudioPlayback", + "stopAudioPlayback", + "audioMuted", + "nativeWidgetIsVisible", + "getLastCompositionText", + "getInvokeDefaultObject", +}; +static NPIdentifier + sPluginMethodIdentifiers[MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames)]; +static const ScriptableFunction sPluginMethodFunctions[] = { + npnEvaluateTest, + npnInvokeTest, + npnInvokeDefaultTest, + setUndefinedValueTest, + identifierToStringTest, + timerTest, + queryPrivateModeState, + lastReportedPrivateModeState, + hasWidget, + getEdge, + getClipRegionRectCount, + getClipRegionRectEdge, + startWatchingInstanceCount, + getInstanceCount, + stopWatchingInstanceCount, + getLastMouseX, + getLastMouseY, + getPaintCount, + resetPaintCount, + getWidthAtLastPaint, + setInvalidateDuringPaint, + setSlowPaint, + getError, + doInternalConsistencyCheck, + setColor, + throwExceptionNextInvoke, + convertPointX, + convertPointY, + streamTest, + postFileToURLTest, + setPluginWantsAllStreams, + crashPlugin, + crashOnDestroy, + getObjectValue, + checkObjectValue, + enableFPExceptions, + hangPlugin, + stallPlugin, + getClipboardText, + callOnDestroy, + reinitWidget, + crashPluginInNestedLoop, + triggerXError, + destroySharedGfxStuff, + propertyAndMethod, + getTopLevelWindowActivationState, + getTopLevelWindowActivationEventCount, + getFocusState, + getFocusEventCount, + getEventModel, + getReflector, + isVisible, + getWindowPosition, + constructObject, + setSitesWithData, + setSitesWithDataCapabilities, + getLastKeyText, + getNPNVdocumentOrigin, + getMouseUpEventCount, + queryContentsScaleFactor, + queryCSSZoomFactorGetValue, + queryCSSZoomFactorSetValue, + echoString, + startAudioPlayback, + stopAudioPlayback, + getAudioMuted, + nativeWidgetIsVisible, + getLastCompositionText, + getInvokeDefaultObject, +}; + +static_assert(MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames) == + MOZ_ARRAY_LENGTH(sPluginMethodFunctions), + "Arrays should have the same size"); + +static const NPUTF8* sPluginPropertyIdentifierNames[] = {"propertyAndMethod"}; +static NPIdentifier sPluginPropertyIdentifiers[MOZ_ARRAY_LENGTH( + sPluginPropertyIdentifierNames)]; +static NPVariant + sPluginPropertyValues[MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames)]; + +struct URLNotifyData { + const char* cookie; + NPObject* writeCallback; + NPObject* notifyCallback; + NPObject* redirectCallback; + bool allowRedirects; + uint32_t size; + char* data; +}; + +static URLNotifyData kNotifyData = {"static-cookie", nullptr, nullptr, nullptr, + false, 0, nullptr}; + +static const char* SUCCESS_STRING = "pass"; + +static bool sIdentifiersInitialized = false; + +struct timerEvent { + int32_t timerIdReceive; + int32_t timerIdSchedule; + uint32_t timerInterval; + bool timerRepeat; + int32_t timerIdUnschedule; +}; +static timerEvent timerEvents[] = { + // clang-format off + {-1, 0, 200, false, -1}, + {0, 0, 400, false, -1}, + {0, 0, 200, true, -1}, + {0, 1, 400, true, -1}, + {0, -1, 0, false, 0}, + {1, -1, 0, false, -1}, + {1, -1, 0, false, 1}, + // clang-format on +}; +static uint32_t currentTimerEventCount = 0; +static uint32_t totalTimerEvents = sizeof(timerEvents) / sizeof(timerEvent); + +/** + * Incremented for every startWatchingInstanceCount. + */ +static int32_t sCurrentInstanceCountWatchGeneration = 0; +/** + * Tracks the number of instances created or destroyed since the last + * startWatchingInstanceCount. + */ +static int32_t sInstanceCount = 0; +/** + * True when we've had a startWatchingInstanceCount with no corresponding + * stopWatchingInstanceCount. + */ +static bool sWatchingInstanceCount = false; + +/** + * A list representing sites for which the plugin has stored data. See + * NPP_ClearSiteData and NPP_GetSitesWithData. + */ +struct siteData { + string site; + uint64_t flags; + uint64_t age; +}; +static list<siteData>* sSitesWithData; +static bool sClearByAgeSupported; + +static void initializeIdentifiers() { + if (!sIdentifiersInitialized) { + NPN_GetStringIdentifiers(sPluginMethodIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames), + sPluginMethodIdentifiers); + NPN_GetStringIdentifiers(sPluginPropertyIdentifierNames, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifierNames), + sPluginPropertyIdentifiers); + + sIdentifiersInitialized = true; + + // Check whether nullptr is handled in NPN_GetStringIdentifiers + NPIdentifier IDList[2]; + static char const* const kIDNames[2] = {nullptr, "setCookie"}; + NPN_GetStringIdentifiers(const_cast<const NPUTF8**>(kIDNames), 2, IDList); + } +} + +static void clearIdentifiers() { + memset(sPluginMethodIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers) * sizeof(NPIdentifier)); + memset(sPluginPropertyIdentifiers, 0, + MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers) * sizeof(NPIdentifier)); + + sIdentifiersInitialized = false; +} + +static void sendBufferToFrame(NPP instance) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + string outbuf; + if (!instanceData->npnNewStream) outbuf = "data:text/html,"; + const char* buf = reinterpret_cast<char*>(instanceData->streamBuf); + int32_t bufsize = instanceData->streamBufSize; + if (instanceData->err.str().length() > 0) { + outbuf.append(instanceData->err.str()); + } else if (bufsize > 0) { + outbuf.append(buf); + } else { + outbuf.append("Error: no data in buffer"); + } + + // Convert CRLF to LF, and escape most other non-alphanumeric chars. + for (size_t i = 0; i < outbuf.length(); i++) { + if (outbuf[i] == '\n') { + outbuf.replace(i, 1, "%0a"); + i += 2; + } else if (outbuf[i] == '\r') { + outbuf.replace(i, 1, ""); + i -= 1; + } else { + int ascii = outbuf[i]; + if (!((ascii >= ',' && ascii <= ';') || (ascii >= 'A' && ascii <= 'Z') || + (ascii >= 'a' && ascii <= 'z'))) { + char hex[10]; + sprintf(hex, "%%%x", ascii); + outbuf.replace(i, 1, hex); + i += 2; + } + } + } + + NPError err = + NPN_GetURL(instance, outbuf.c_str(), instanceData->frame.c_str()); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } +} + +static void XPSleep(unsigned int seconds) { +#ifdef XP_WIN + Sleep(1000 * seconds); +#else + sleep(seconds); +#endif +} + +TestFunction getFuncFromString(const char* funcname) { + FunctionTable funcTable[] = { + {FUNCTION_NPP_NEWSTREAM, "npp_newstream"}, + {FUNCTION_NPP_WRITEREADY, "npp_writeready"}, + {FUNCTION_NPP_WRITE, "npp_write"}, + {FUNCTION_NPP_DESTROYSTREAM, "npp_destroystream"}, + {FUNCTION_NPP_WRITE_RPC, "npp_write_rpc"}, + {FUNCTION_NONE, nullptr}}; + int32_t i = 0; + while (funcTable[i].funcName) { + if (!strcmp(funcname, funcTable[i].funcName)) return funcTable[i].funcId; + i++; + } + return FUNCTION_NONE; +} + +static void DuplicateNPVariant(NPVariant& aDest, const NPVariant& aSrc) { + if (NPVARIANT_IS_STRING(aSrc)) { + NPString src = NPVARIANT_TO_STRING(aSrc); + char* buf = new char[src.UTF8Length]; + strncpy(buf, src.UTF8Characters, src.UTF8Length); + STRINGN_TO_NPVARIANT(buf, src.UTF8Length, aDest); + } else if (NPVARIANT_IS_OBJECT(aSrc)) { + NPObject* obj = NPN_RetainObject(NPVARIANT_TO_OBJECT(aSrc)); + OBJECT_TO_NPVARIANT(obj, aDest); + } else { + aDest = aSrc; + } +} + +static bool bug813906(NPP npp, const char* const function, + const char* const url, const char* const frame) { + NPObject* windowObj = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &windowObj); + if (err != NPERR_NO_ERROR) { + return false; + } + + NPVariant result; + bool res = NPN_Invoke(npp, windowObj, NPN_GetStringIdentifier(function), + nullptr, 0, &result); + NPN_ReleaseObject(windowObj); + if (!res) { + return false; + } + + NPN_ReleaseVariantValue(&result); + + err = NPN_GetURL(npp, url, frame); + if (err != NPERR_NO_ERROR) { + err = NPN_GetURL(npp, "about:blank", frame); + return false; + } + + return true; +} + +void drawAsyncBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + uint32_t* pixelData = (uint32_t*)instanceData->backBuffer->bitmap.data; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + + unsigned char subpixels[4]; + memcpy(subpixels, &rgba, sizeof(subpixels)); + + subpixels[0] = uint8_t(float(subpixels[3] * subpixels[0]) / 0xFF); + subpixels[1] = uint8_t(float(subpixels[3] * subpixels[1]) / 0xFF); + subpixels[2] = uint8_t(float(subpixels[3] * subpixels[2]) / 0xFF); + uint32_t premultiplied; + memcpy(&premultiplied, subpixels, sizeof(premultiplied)); + + for (uint32_t* lastPixel = + pixelData + instanceData->backBuffer->size.width * + instanceData->backBuffer->size.height; + pixelData < lastPixel; ++pixelData) { + *pixelData = premultiplied; + } + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + NPAsyncSurface* oldFront = instanceData->frontBuffer; + instanceData->frontBuffer = instanceData->backBuffer; + instanceData->backBuffer = oldFront; +} + +// +// function signatures +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass); +void scriptableDeallocate(NPObject* npobj); +void scriptableInvalidate(NPObject* npobj); +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name); +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result); +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name); +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result); +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value); +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name); +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count); +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result); + +// +// npapi plugin functions +// + +#ifdef XP_UNIX +NP_EXPORT(char*) +NP_GetPluginVersion() { return sPluginVersion; } +#endif + +extern const char* sMimeDescription; + +#if defined(XP_UNIX) +NP_EXPORT(const char*) +NP_GetMIMEDescription() +#elif defined(XP_WIN) +const char* NP_GetMIMEDescription() +#endif +{ + return sMimeDescription; +} + +#ifdef XP_UNIX +NP_EXPORT(NPError) +NP_GetValue(void* future, NPPVariable aVariable, void* aValue) { + switch (aVariable) { + case NPPVpluginNameString: + *((const char**)aValue) = sPluginName; + break; + case NPPVpluginDescriptionString: + *((const char**)aValue) = sPluginDescription; + break; + default: + return NPERR_INVALID_PARAM; + } + return NPERR_NO_ERROR; +} +#endif + +static bool fillPluginFunctionTable(NPPluginFuncs* pFuncs) { + // Check the size of the provided structure based on the offset of the + // last member we need. + if (pFuncs->size < + (offsetof(NPPluginFuncs, getsiteswithdata) + sizeof(void*))) + return false; + + pFuncs->newp = NPP_New; + pFuncs->destroy = NPP_Destroy; + pFuncs->setwindow = NPP_SetWindow; + pFuncs->newstream = NPP_NewStream; + pFuncs->destroystream = NPP_DestroyStream; + pFuncs->writeready = NPP_WriteReady; + pFuncs->write = NPP_Write; + pFuncs->print = NPP_Print; + pFuncs->event = NPP_HandleEvent; + pFuncs->urlnotify = NPP_URLNotify; + pFuncs->getvalue = NPP_GetValue; + pFuncs->setvalue = NPP_SetValue; + pFuncs->urlredirectnotify = NPP_URLRedirectNotify; + pFuncs->clearsitedata = NPP_ClearSiteData; + pFuncs->getsiteswithdata = NPP_GetSitesWithData; + + return true; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_Initialize(NPNetscapeFuncs* bFuncs) +#elif defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs) +#endif +{ + sBrowserFuncs = bFuncs; + + initializeIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + } + + memset(&sNPClass, 0, sizeof(NPClass)); + sNPClass.structVersion = NP_CLASS_STRUCT_VERSION; + sNPClass.allocate = (NPAllocateFunctionPtr)scriptableAllocate; + sNPClass.deallocate = (NPDeallocateFunctionPtr)scriptableDeallocate; + sNPClass.invalidate = (NPInvalidateFunctionPtr)scriptableInvalidate; + sNPClass.hasMethod = (NPHasMethodFunctionPtr)scriptableHasMethod; + sNPClass.invoke = (NPInvokeFunctionPtr)scriptableInvoke; + sNPClass.invokeDefault = nullptr; + sNPClass.hasProperty = (NPHasPropertyFunctionPtr)scriptableHasProperty; + sNPClass.getProperty = (NPGetPropertyFunctionPtr)scriptableGetProperty; + sNPClass.setProperty = (NPSetPropertyFunctionPtr)scriptableSetProperty; + sNPClass.removeProperty = + (NPRemovePropertyFunctionPtr)scriptableRemoveProperty; + sNPClass.enumerate = (NPEnumerationFunctionPtr)scriptableEnumerate; + sNPClass.construct = (NPConstructFunctionPtr)scriptableConstruct; + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } +#endif + + return NPERR_NO_ERROR; +} + +#if defined(XP_MACOSX) +NP_EXPORT(NPError) +NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#elif defined(XP_WIN) +NPError OSCALL NP_GetEntryPoints(NPPluginFuncs* pFuncs) +#endif +#if defined(XP_MACOSX) || defined(XP_WIN) +{ + if (!fillPluginFunctionTable(pFuncs)) { + return NPERR_INVALID_FUNCTABLE_ERROR; + } + + return NPERR_NO_ERROR; +} +#endif + +#if defined(XP_UNIX) +NP_EXPORT(NPError) +NP_Shutdown() +#elif defined(XP_WIN) + NPError OSCALL NP_Shutdown() +#endif +{ + clearIdentifiers(); + + for (unsigned int i = 0; i < MOZ_ARRAY_LENGTH(sPluginPropertyValues); i++) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16_t mode, + int16_t argc, char* argn[], char* argv[], NPSavedData* saved) { + // Make sure our pdata field is nullptr at this point. If it isn't, that + // probably means the browser gave us uninitialized memory. + if (instance->pdata) { + printf("NPP_New called with non-NULL NPP->pdata pointer!\n"); + return NPERR_GENERIC_ERROR; + } + + // Make sure we can render this plugin + NPBool browserSupportsWindowless = false; + NPN_GetValue(instance, NPNVSupportsWindowless, &browserSupportsWindowless); + if (!browserSupportsWindowless && !pluginSupportsWindowMode()) { + printf( + "Windowless mode not supported by the browser, windowed mode not " + "supported by the plugin!\n"); + return NPERR_GENERIC_ERROR; + } + + // set up our our instance data + InstanceData* instanceData = new InstanceData; + instanceData->npp = instance; + instanceData->testFunction = FUNCTION_NONE; + instanceData->functionToFail = FUNCTION_NONE; + instanceData->failureCode = 0; + instanceData->callOnDestroy = nullptr; + instanceData->streamChunkSize = 1024; + instanceData->streamBuf = nullptr; + instanceData->streamBufSize = 0; + instanceData->throwOnNextInvoke = false; + instanceData->runScriptOnPaint = false; + instanceData->dontTouchElement = false; + instanceData->hasWidget = false; + instanceData->npnNewStream = false; + instanceData->invalidateDuringPaint = false; + instanceData->slowPaint = false; + instanceData->playingAudio = false; + instanceData->audioMuted = false; + instanceData->writeCount = 0; + instanceData->writeReadyCount = 0; + memset(&instanceData->window, 0, sizeof(instanceData->window)); + instanceData->crashOnDestroy = false; + instanceData->cleanupWidget = true; // only used by nptest_gtk + instanceData->topLevelWindowActivationState = ACTIVATION_STATE_UNKNOWN; + instanceData->topLevelWindowActivationEventCount = 0; + instanceData->focusState = ACTIVATION_STATE_UNKNOWN; + instanceData->focusEventCount = 0; + instanceData->eventModel = 0; + instanceData->wantsAllStreams = false; + instanceData->mouseUpEventCount = 0; + instanceData->bugMode = -1; + instanceData->asyncDrawing = AD_NONE; + instanceData->frontBuffer = nullptr; + instanceData->backBuffer = nullptr; + instanceData->placeholderWnd = nullptr; + instanceData->cssZoomFactor = 1.0; + instance->pdata = instanceData; + + TestNPObject* scriptableObject = + (TestNPObject*)NPN_CreateObject(instance, &sNPClass); + if (!scriptableObject) { + printf( + "NPN_CreateObject failed to create an object, can't create a plugin " + "instance\n"); + delete instanceData; + return NPERR_GENERIC_ERROR; + } + scriptableObject->npp = instance; + scriptableObject->drawMode = DM_DEFAULT; + scriptableObject->drawColor = 0; + instanceData->scriptableObject = scriptableObject; + + instanceData->instanceCountWatchGeneration = + sCurrentInstanceCountWatchGeneration; + + AsyncDrawing requestAsyncDrawing = AD_NONE; + + bool requestWindow = false; + bool alreadyHasSalign = false; + // handle extra params + for (int i = 0; i < argc; i++) { + if (strcmp(argn[i], "drawmode") == 0) { + if (strcmp(argv[i], "solid") == 0) + scriptableObject->drawMode = DM_SOLID_COLOR; + } else if (strcmp(argn[i], "color") == 0) { + scriptableObject->drawColor = parseHexColor(argv[i], strlen(argv[i])); + } else if (strcmp(argn[i], "wmode") == 0) { + if (strcmp(argv[i], "window") == 0) { + requestWindow = true; + } + } else if (strcmp(argn[i], "asyncmodel") == 0) { + if (strcmp(argv[i], "bitmap") == 0) { + requestAsyncDrawing = AD_BITMAP; + } else if (strcmp(argv[i], "dxgi") == 0) { + requestAsyncDrawing = AD_DXGI; + } + } + if (strcmp(argn[i], "streamchunksize") == 0) { + instanceData->streamChunkSize = atoi(argv[i]); + } + if (strcmp(argn[i], "failurecode") == 0) { + instanceData->failureCode = atoi(argv[i]); + } + if (strcmp(argn[i], "functiontofail") == 0) { + instanceData->functionToFail = getFuncFromString(argv[i]); + } + if (strcmp(argn[i], "geturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURL; + } + if (strcmp(argn[i], "posturl") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_POSTURL; + } + if (strcmp(argn[i], "geturlnotify") == 0) { + instanceData->testUrl = argv[i]; + instanceData->testFunction = FUNCTION_NPP_GETURLNOTIFY; + } + if (strcmp(argn[i], "postmode") == 0) { + if (strcmp(argv[i], "frame") == 0) { + instanceData->postMode = POSTMODE_FRAME; + } else if (strcmp(argv[i], "stream") == 0) { + instanceData->postMode = POSTMODE_STREAM; + } + } + if (strcmp(argn[i], "frame") == 0) { + instanceData->frame = argv[i]; + } + if (strcmp(argn[i], "newstream") == 0 && strcmp(argv[i], "true") == 0) { + instanceData->npnNewStream = true; + } + if (strcmp(argn[i], "newcrash") == 0) { + IntentionalCrash(); + } + if (strcmp(argn[i], "paintscript") == 0) { + instanceData->runScriptOnPaint = true; + } + + if (strcmp(argn[i], "donttouchelement") == 0) { + instanceData->dontTouchElement = true; + } + // "cleanupwidget" is only used with nptest_gtk, defaulting to true. It + // indicates whether the plugin should destroy its window in response to + // NPP_Destroy (or let the platform destroy the widget when the parent + // window gets destroyed). + if (strcmp(argn[i], "cleanupwidget") == 0 && + strcmp(argv[i], "false") == 0) { + instanceData->cleanupWidget = false; + } + if (strcmp(argn[i], "bugmode") == 0) { + instanceData->bugMode = atoi(argv[i]); + } + + // Bug 1307694 - There are two flash parameters that are order dependent for + // scaling/sizing the plugin. If they ever change from what is expected, it + // breaks flash on the web. In a test, if the scale tag ever happens + // with an salign before it, fail the plugin creation. + if (strcmp(argn[i], "scale") == 0) { + if (alreadyHasSalign) { + // If salign came before this parameter, error out now. + return NPERR_GENERIC_ERROR; + } + } + if (strcmp(argn[i], "salign") == 0) { + alreadyHasSalign = true; + } + } + + if (!browserSupportsWindowless || !pluginSupportsWindowlessMode()) { + requestWindow = true; + } else if (!pluginSupportsWindowMode()) { + requestWindow = false; + } + if (requestWindow) { + instanceData->hasWidget = true; + } else { + // NPPVpluginWindowBool should default to true, so we may as well + // test that by not setting it in the window case + NPN_SetValue(instance, NPPVpluginWindowBool, (void*)false); + } + + if (scriptableObject->drawMode == DM_SOLID_COLOR && + (scriptableObject->drawColor & 0xFF000000) != 0xFF000000) { + NPN_SetValue(instance, NPPVpluginTransparentBool, (void*)true); + } + + if (requestAsyncDrawing == AD_BITMAP) { + NPBool supportsAsyncBitmap = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncBitmapSurfaceBool, + &supportsAsyncBitmap) == NPERR_NO_ERROR) && + supportsAsyncBitmap) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncBitmapSurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_BITMAP; + } + } + } +#ifdef XP_WIN + else if (requestAsyncDrawing == AD_DXGI) { + NPBool supportsAsyncDXGI = false; + if ((NPN_GetValue(instance, NPNVsupportsAsyncWindowsDXGISurfaceBool, + &supportsAsyncDXGI) == NPERR_NO_ERROR) && + supportsAsyncDXGI) { + if (NPN_SetValue(instance, NPPVpluginDrawingModel, + (void*)NPDrawingModelAsyncWindowsDXGISurface) == + NPERR_NO_ERROR) { + instanceData->asyncDrawing = AD_DXGI; + } + } + } +#endif + + // If we can't get the right drawing mode, we fail, otherwise our tests might + // appear to be passing when they shouldn't. Real plugins should not do this. + if (instanceData->asyncDrawing != requestAsyncDrawing) { + return NPERR_GENERIC_ERROR; + } + + instanceData->lastReportedPrivateModeState = false; + instanceData->lastMouseX = instanceData->lastMouseY = -1; + instanceData->widthAtLastPaint = -1; + instanceData->paintCount = 0; + + // do platform-specific initialization + NPError err = pluginInstanceInit(instanceData); + if (err != NPERR_NO_ERROR) { + NPN_ReleaseObject(scriptableObject); + delete instanceData; + return err; + } + + NPVariant variantTrue; + BOOLEAN_TO_NPVARIANT(true, variantTrue); + NPObject* o = nullptr; + + // Set a property on NPNVPluginElementNPObject, unless the consumer explicitly + // opted out of this behavior. + if (!instanceData->dontTouchElement) { + err = NPN_GetValue(instance, NPNVPluginElementNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, + NPN_GetStringIdentifier("pluginFoundElement"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + } + + // Set a property on NPNVWindowNPObject + err = NPN_GetValue(instance, NPNVWindowNPObject, &o); + if (err == NPERR_NO_ERROR) { + NPN_SetProperty(instance, o, NPN_GetStringIdentifier("pluginFoundWindow"), + &variantTrue); + NPN_ReleaseObject(o); + o = nullptr; + } + + ++sInstanceCount; + + if (instanceData->testFunction == FUNCTION_NPP_GETURL) { + NPError err = NPN_GetURL(instance, instanceData->testUrl.c_str(), nullptr); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURL returned " << err; + } + } else if (instanceData->testFunction == FUNCTION_NPP_GETURLNOTIFY) { + NPError err = NPN_GetURLNotify(instance, instanceData->testUrl.c_str(), + nullptr, static_cast<void*>(&kNotifyData)); + if (err != NPERR_NO_ERROR) { + instanceData->err << "NPN_GetURLNotify returned " << err; + } + } + + if ((instanceData->bugMode == 813906) && instanceData->frame.length()) { + bug813906(instance, "f", "browser.xhtml", instanceData->frame.c_str()); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->crashOnDestroy) IntentionalCrash(); + + if (instanceData->callOnDestroy) { + NPVariant result; + NPN_InvokeDefault(instance, instanceData->callOnDestroy, nullptr, 0, + &result); + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(instanceData->callOnDestroy); + } + + if (instanceData->streamBuf) { + free(instanceData->streamBuf); + } + + if (instanceData->frontBuffer) { + NPN_SetCurrentAsyncSurface(instance, nullptr, nullptr); + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + + pluginInstanceShutdown(instanceData); + NPN_ReleaseObject(instanceData->scriptableObject); + + if (sCurrentInstanceCountWatchGeneration == + instanceData->instanceCountWatchGeneration) { + --sInstanceCount; + } + delete instanceData; + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->scriptableObject->drawMode == DM_DEFAULT && + (instanceData->window.width != window->width || + instanceData->window.height != window->height)) { + NPRect r; + r.left = r.top = 0; + r.right = window->width; + r.bottom = window->height; + NPN_InvalidateRect(instance, &r); + } + + void* oldWindow = instanceData->window.window; + pluginDoSetWindow(instanceData, window); + if (instanceData->hasWidget && oldWindow != instanceData->window.window) { + pluginWidgetInit(instanceData, oldWindow); + } + + if (instanceData->asyncDrawing != AD_NONE) { + if (instanceData->frontBuffer && + instanceData->frontBuffer->size.width >= 0 && + (uint32_t)instanceData->frontBuffer->size.width == window->width && + instanceData->frontBuffer->size.height >= 0 && + (uint32_t)instanceData->frontBuffer->size.height == window->height) { + return NPERR_NO_ERROR; + } + if (instanceData->frontBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->frontBuffer); + NPN_MemFree(instanceData->frontBuffer); + } + if (instanceData->backBuffer) { + NPN_FinalizeAsyncSurface(instance, instanceData->backBuffer); + NPN_MemFree(instanceData->backBuffer); + } + instanceData->frontBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + instanceData->backBuffer = + (NPAsyncSurface*)NPN_MemAlloc(sizeof(NPAsyncSurface)); + + NPSize size; + size.width = window->width; + size.height = window->height; + + memcpy(instanceData->backBuffer, instanceData->frontBuffer, + sizeof(NPAsyncSurface)); + + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->frontBuffer); + NPN_InitAsyncSurface(instance, &size, NPImageFormatBGRA32, nullptr, + instanceData->backBuffer); + +#if defined(XP_WIN) + if (instanceData->asyncDrawing == AD_DXGI) { + if (!setupDxgiSurfaces(instance, instanceData)) { + return NPERR_GENERIC_ERROR; + } + } +#endif + } + + if (instanceData->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(instanceData); + } +#if defined(XP_WIN) + else if (instanceData->asyncDrawing == AD_DXGI) { + drawDxgiBitmapColor(instanceData); + } +#endif + + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16_t* stype) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + if (stream->notifyData && + static_cast<URLNotifyData*>(stream->notifyData) != &kNotifyData) { + // stream from streamTest + *stype = NP_NORMAL; + } else { + *stype = NP_NORMAL; + + if (instanceData->streamBufSize) { + free(instanceData->streamBuf); + instanceData->streamBufSize = 0; + if (instanceData->testFunction == FUNCTION_NPP_POSTURL && + instanceData->postMode == POSTMODE_STREAM) { + instanceData->testFunction = FUNCTION_NPP_GETURL; + } else { + // We already got a stream and didn't ask for another one. + instanceData->err << "Received unexpected multiple NPP_NewStream"; + } + } + } + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_DestroyStream called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + if (instanceData->writeCount == 1) + instanceData->err << SUCCESS_STRING; + else + instanceData->err << "NPP_Write called after returning -1"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_DESTROYSTREAM && + instanceData->failureCode) { + instanceData->err << SUCCESS_STRING; + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + return instanceData->failureCode; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + if (nd && nd != &kNotifyData) { + return NPERR_NO_ERROR; + } + + if (instanceData->frame.length() > 0 && + instanceData->testFunction != FUNCTION_NPP_GETURLNOTIFY && + instanceData->testFunction != FUNCTION_NPP_POSTURL) { + sendBufferToFrame(instance); + } + if (instanceData->testFunction == FUNCTION_NPP_POSTURL) { + NPError err = NPN_PostURL( + instance, instanceData->testUrl.c_str(), + instanceData->postMode == POSTMODE_FRAME ? instanceData->frame.c_str() + : nullptr, + instanceData->streamBufSize, + reinterpret_cast<char*>(instanceData->streamBuf), false); + if (err != NPERR_NO_ERROR) + instanceData->err << "Error: NPN_PostURL returned error value " << err; + } + return NPERR_NO_ERROR; +} + +int32_t NPP_WriteReady(NPP instance, NPStream* stream) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeReadyCount++; + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_WriteReady called"; + } + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // return 0; + //} + + return instanceData->streamChunkSize; +} + +int32_t NPP_Write(NPP instance, NPStream* stream, int32_t offset, int32_t len, + void* buffer) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->writeCount++; + + // temporarily disabled per bug 519870 + // if (instanceData->writeReadyCount == 1) { + // instanceData->err << "NPP_Write called even though NPP_WriteReady " << + // "returned 0"; + //} + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE_RPC) { + // Make an RPC call and pretend to consume the data + NPObject* windowObject = nullptr; + NPN_GetValue(instance, NPNVWindowNPObject, &windowObject); + if (windowObject) NPN_ReleaseObject(windowObject); + + return len; + } + + if (instanceData->functionToFail == FUNCTION_NPP_NEWSTREAM) { + instanceData->err << "NPP_Write called"; + } + + if (instanceData->functionToFail == FUNCTION_NPP_WRITE) { + return -1; + } + + URLNotifyData* nd = static_cast<URLNotifyData*>(stream->notifyData); + + if (nd && nd->writeCallback) { + NPVariant args[1]; + STRINGN_TO_NPVARIANT(stream->url, strlen(stream->url), args[0]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->writeCallback, args, 1, &result); + NPN_ReleaseVariantValue(&result); + } + + if (nd && nd != &kNotifyData) { + uint32_t newsize = nd->size + len; + nd->data = (char*)realloc(nd->data, newsize); + memcpy(nd->data + nd->size, buffer, len); + nd->size = newsize; + return len; + } + + char* streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + if (offset + len <= instanceData->streamBufSize) { + if (memcmp(buffer, streamBuf + offset, len)) { + instanceData->err << "Error: data written doesn't match"; + } else { + printf("data matches!\n"); + } + } else { + if (instanceData->streamBufSize == 0) { + instanceData->streamBuf = malloc(len + 1); + streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + } else { + instanceData->streamBuf = + realloc(reinterpret_cast<char*>(instanceData->streamBuf), + instanceData->streamBufSize + len + 1); + streamBuf = reinterpret_cast<char*>(instanceData->streamBuf); + } + memcpy(streamBuf + instanceData->streamBufSize, buffer, len); + instanceData->streamBufSize = instanceData->streamBufSize + len; + streamBuf[instanceData->streamBufSize] = '\0'; + } + return len; +} + +void NPP_Print(NPP instance, NPPrint* platformPrint) {} + +int16_t NPP_HandleEvent(NPP instance, void* event) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + return pluginHandleEvent(instanceData, event); +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + URLNotifyData* ndata = static_cast<URLNotifyData*>(notifyData); + + if (&kNotifyData == ndata) { + if (instanceData->frame.length() > 0) { + sendBufferToFrame(instance); + } + } else if (!strcmp(ndata->cookie, "dynamic-cookie")) { + if (ndata->notifyCallback) { + NPVariant args[2]; + INT32_TO_NPVARIANT(reason, args[0]); + if (ndata->data) { + STRINGN_TO_NPVARIANT(ndata->data, ndata->size, args[1]); + } else { + STRINGN_TO_NPVARIANT("", 0, args[1]); + } + + NPVariant result; + NPN_InvokeDefault(instance, ndata->notifyCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + + // clean up the URLNotifyData + if (ndata->writeCallback) { + NPN_ReleaseObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_ReleaseObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_ReleaseObject(ndata->redirectCallback); + } + free(ndata->data); + delete ndata; + } else { + printf("ERROR! NPP_URLNotify called with wrong cookie\n"); + instanceData->err << "Error: NPP_URLNotify called with wrong cookie"; + } +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { + InstanceData* instanceData = (InstanceData*)instance->pdata; + if (variable == NPPVpluginScriptableNPObject) { + NPObject* object = instanceData->scriptableObject; + NPN_RetainObject(object); + *((NPObject**)value) = object; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginNeedsXEmbed) { + // Only relevant for X plugins + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->hasWidget; + return NPERR_NO_ERROR; + } + if (variable == NPPVpluginWantsAllNetworkStreams) { + // use 4-byte writes like some plugins may do + *(uint32_t*)value = instanceData->wantsAllStreams; + return NPERR_NO_ERROR; + } + + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { + if (variable == NPNVprivateModeBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->lastReportedPrivateModeState = + bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVmuteAudioBool) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->audioMuted = bool(*static_cast<NPBool*>(value)); + return NPERR_NO_ERROR; + } + if (variable == NPNVCSSZoomFactor) { + InstanceData* instanceData = (InstanceData*)(instance->pdata); + instanceData->cssZoomFactor = *static_cast<double*>(value); + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +void NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, + void* notifyData) { + if (notifyData) { + URLNotifyData* nd = static_cast<URLNotifyData*>(notifyData); + if (nd->redirectCallback) { + NPVariant args[2]; + STRINGN_TO_NPVARIANT(url, strlen(url), args[0]); + INT32_TO_NPVARIANT(status, args[1]); + + NPVariant result; + NPN_InvokeDefault(instance, nd->redirectCallback, args, 2, &result); + NPN_ReleaseVariantValue(&result); + } + NPN_URLRedirectResponse(instance, notifyData, nd->allowRedirects); + return; + } + NPN_URLRedirectResponse(instance, notifyData, true); +} + +NPError NPP_ClearSiteData(const char* site, uint64_t flags, uint64_t maxAge) { + if (!sSitesWithData) return NPERR_NO_ERROR; + + // Error condition: no support for clear-by-age + if (!sClearByAgeSupported && maxAge != uint64_t(int64_t(-1))) + return NPERR_TIME_RANGE_NOT_SUPPORTED; + + // Iterate over list and remove matches + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + while (iter != end) { + const siteData& data = *iter; + list<siteData>::iterator next = iter; + ++next; + if ((!site || data.site.compare(site) == 0) && + (flags == NP_CLEAR_ALL || data.flags & flags) && data.age <= maxAge) { + sSitesWithData->erase(iter); + } + iter = next; + } + + return NPERR_NO_ERROR; +} + +char** NPP_GetSitesWithData() { + int length = 0; + char** result; + + if (sSitesWithData) length = sSitesWithData->size(); + + // Allocate the maximum possible size the list could be. + result = static_cast<char**>(NPN_MemAlloc((length + 1) * sizeof(char*))); + result[length] = nullptr; + + if (length == 0) { + // Represent the no site data case as an array of length 1 with a nullptr + // entry. + return result; + } + + // Iterate the list of stored data, and build a list of strings. + list<string> sites; + { + list<siteData>::iterator iter = sSitesWithData->begin(); + list<siteData>::iterator end = sSitesWithData->end(); + for (; iter != end; ++iter) { + const siteData& data = *iter; + sites.push_back(data.site); + } + } + + // Remove duplicate strings. + sites.sort(); + sites.unique(); + + // Add strings to the result array, and null terminate. + { + int i = 0; + list<string>::iterator iter = sites.begin(); + list<string>::iterator end = sites.end(); + for (; iter != end; ++iter, ++i) { + const string& site = *iter; + result[i] = static_cast<char*>(NPN_MemAlloc(site.length() + 1)); + memcpy(result[i], site.c_str(), site.length() + 1); + } + } + result[sites.size()] = nullptr; + + return result; +} + +// +// npapi browser functions +// + +bool NPN_SetProperty(NPP instance, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value) { + return sBrowserFuncs->setproperty(instance, obj, propertyName, value); +} + +NPIdentifier NPN_GetIntIdentifier(int32_t intid) { + return sBrowserFuncs->getintidentifier(intid); +} + +NPIdentifier NPN_GetStringIdentifier(const NPUTF8* name) { + return sBrowserFuncs->getstringidentifier(name); +} + +void NPN_GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + return sBrowserFuncs->getstringidentifiers(names, nameCount, identifiers); +} + +bool NPN_IdentifierIsString(NPIdentifier identifier) { + return sBrowserFuncs->identifierisstring(identifier); +} + +NPUTF8* NPN_UTF8FromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->utf8fromidentifier(identifier); +} + +int32_t NPN_IntFromIdentifier(NPIdentifier identifier) { + return sBrowserFuncs->intfromidentifier(identifier); +} + +NPError NPN_GetValue(NPP instance, NPNVariable variable, void* value) { + return sBrowserFuncs->getvalue(instance, variable, value); +} + +NPError NPN_SetValue(NPP instance, NPPVariable variable, void* value) { + return sBrowserFuncs->setvalue(instance, variable, value); +} + +void NPN_InvalidateRect(NPP instance, NPRect* rect) { + sBrowserFuncs->invalidaterect(instance, rect); +} + +bool NPN_HasProperty(NPP instance, NPObject* obj, NPIdentifier propertyName) { + return sBrowserFuncs->hasproperty(instance, obj, propertyName); +} + +NPObject* NPN_CreateObject(NPP instance, NPClass* aClass) { + return sBrowserFuncs->createobject(instance, aClass); +} + +bool NPN_Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invoke(npp, obj, methodName, args, argCount, result); +} + +bool NPN_InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->invokeDefault(npp, obj, args, argCount, result); +} + +bool NPN_Construct(NPP npp, NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return sBrowserFuncs->construct(npp, npobj, args, argCount, result); +} + +const char* NPN_UserAgent(NPP instance) { + return sBrowserFuncs->uagent(instance); +} + +NPObject* NPN_RetainObject(NPObject* obj) { + return sBrowserFuncs->retainobject(obj); +} + +void NPN_ReleaseObject(NPObject* obj) { + return sBrowserFuncs->releaseobject(obj); +} + +void* NPN_MemAlloc(uint32_t size) { return sBrowserFuncs->memalloc(size); } + +char* NPN_StrDup(const char* str) { + return strcpy((char*)sBrowserFuncs->memalloc(strlen(str) + 1), str); +} + +void NPN_MemFree(void* ptr) { return sBrowserFuncs->memfree(ptr); } + +uint32_t NPN_ScheduleTimer(NPP instance, uint32_t interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32_t timerID)) { + return sBrowserFuncs->scheduletimer(instance, interval, repeat, timerFunc); +} + +void NPN_UnscheduleTimer(NPP instance, uint32_t timerID) { + return sBrowserFuncs->unscheduletimer(instance, timerID); +} + +void NPN_ReleaseVariantValue(NPVariant* variant) { + return sBrowserFuncs->releasevariantvalue(variant); +} + +NPError NPN_GetURLNotify(NPP instance, const char* url, const char* target, + void* notifyData) { + return sBrowserFuncs->geturlnotify(instance, url, target, notifyData); +} + +NPError NPN_GetURL(NPP instance, const char* url, const char* target) { + return sBrowserFuncs->geturl(instance, url, target); +} + +NPError NPN_PostURLNotify(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file, + void* notifyData) { + return sBrowserFuncs->posturlnotify(instance, url, target, len, buf, file, + notifyData); +} + +NPError NPN_PostURL(NPP instance, const char* url, const char* target, + uint32_t len, const char* buf, NPBool file) { + return sBrowserFuncs->posturl(instance, url, target, len, buf, file); +} + +bool NPN_Enumerate(NPP instance, NPObject* npobj, NPIdentifier** identifiers, + uint32_t* identifierCount) { + return sBrowserFuncs->enumerate(instance, npobj, identifiers, + identifierCount); +} + +bool NPN_GetProperty(NPP instance, NPObject* npobj, NPIdentifier propertyName, + NPVariant* result) { + return sBrowserFuncs->getproperty(instance, npobj, propertyName, result); +} + +bool NPN_Evaluate(NPP instance, NPObject* npobj, NPString* script, + NPVariant* result) { + return sBrowserFuncs->evaluate(instance, npobj, script, result); +} + +void NPN_SetException(NPObject* npobj, const NPUTF8* message) { + return sBrowserFuncs->setexception(npobj, message); +} + +NPBool NPN_ConvertPoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double* destX, + double* destY, NPCoordinateSpace destSpace) { + return sBrowserFuncs->convertpoint(instance, sourceX, sourceY, sourceSpace, + destX, destY, destSpace); +} + +NPError NPN_SetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, const char* value, uint32_t len) { + return sBrowserFuncs->setvalueforurl(instance, variable, url, value, len); +} + +NPError NPN_GetValueForURL(NPP instance, NPNURLVariable variable, + const char* url, char** value, uint32_t* len) { + return sBrowserFuncs->getvalueforurl(instance, variable, url, value, len); +} + +void NPN_URLRedirectResponse(NPP instance, void* notifyData, NPBool allow) { + return sBrowserFuncs->urlredirectresponse(instance, notifyData, allow); +} + +NPError NPN_InitAsyncSurface(NPP instance, NPSize* size, NPImageFormat format, + void* initData, NPAsyncSurface* surface) { + return sBrowserFuncs->initasyncsurface(instance, size, format, initData, + surface); +} + +NPError NPN_FinalizeAsyncSurface(NPP instance, NPAsyncSurface* surface) { + return sBrowserFuncs->finalizeasyncsurface(instance, surface); +} + +void NPN_SetCurrentAsyncSurface(NPP instance, NPAsyncSurface* surface, + NPRect* changed) { + sBrowserFuncs->setcurrentasyncsurface(instance, surface, changed); +} + +// +// npruntime object functions +// + +NPObject* scriptableAllocate(NPP npp, NPClass* aClass) { + TestNPObject* object = (TestNPObject*)NPN_MemAlloc(sizeof(TestNPObject)); + if (!object) return nullptr; + memset(object, 0, sizeof(TestNPObject)); + return object; +} + +void scriptableDeallocate(NPObject* npobj) { NPN_MemFree(npobj); } + +void scriptableInvalidate(NPObject* npobj) {} + +bool scriptableHasMethod(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) return true; + } + return false; +} + +bool scriptableInvoke(NPObject* npobj, NPIdentifier name, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->throwOnNextInvoke) { + id->throwOnNextInvoke = false; + if (argCount == 0) { + NPN_SetException(npobj, nullptr); + } else { + for (uint32_t i = 0; i < argCount; i++) { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + NPN_SetException(npobj, argstr->UTF8Characters); + } + } + return false; + } + + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginMethodIdentifiers)); i++) { + if (name == sPluginMethodIdentifiers[i]) + return sPluginMethodFunctions[i](npobj, args, argCount, result); + } + return false; +} + +bool scriptableHasProperty(NPObject* npobj, NPIdentifier name) { + if (NPN_IdentifierIsString(name)) { + NPUTF8* asUTF8 = NPN_UTF8FromIdentifier(name); + if (NPN_GetStringIdentifier(asUTF8) != name) { + Crash(); + } + NPN_MemFree(asUTF8); + } else { + if (NPN_GetIntIdentifier(NPN_IntFromIdentifier(name)) != name) { + Crash(); + } + } + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + return true; + } + } + return false; +} + +bool scriptableGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + DuplicateNPVariant(*result, sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableSetProperty(NPObject* npobj, NPIdentifier name, + const NPVariant* value) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + DuplicateNPVariant(sPluginPropertyValues[i], *value); + return true; + } + } + return false; +} + +bool scriptableRemoveProperty(NPObject* npobj, NPIdentifier name) { + for (int i = 0; i < int(MOZ_ARRAY_LENGTH(sPluginPropertyIdentifiers)); i++) { + if (name == sPluginPropertyIdentifiers[i]) { + NPN_ReleaseVariantValue(&sPluginPropertyValues[i]); + + // Avoid double frees (see test_propertyAndMethod.html, which deletes a + // property that doesn't exist). + VOID_TO_NPVARIANT(sPluginPropertyValues[i]); + return true; + } + } + return false; +} + +bool scriptableEnumerate(NPObject* npobj, NPIdentifier** identifier, + uint32_t* count) { + const int bufsize = + sizeof(NPIdentifier) * MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + NPIdentifier* ids = (NPIdentifier*)NPN_MemAlloc(bufsize); + if (!ids) return false; + + memcpy(ids, sPluginMethodIdentifiers, bufsize); + *identifier = ids; + *count = MOZ_ARRAY_LENGTH(sPluginMethodIdentifierNames); + return true; +} + +bool scriptableConstruct(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return false; +} + +// +// test functions +// + +static bool compareVariants(NPP instance, const NPVariant* var1, + const NPVariant* var2) { + bool success = true; + InstanceData* id = static_cast<InstanceData*>(instance->pdata); + if (var1->type != var2->type) { + id->err << "Variant types don't match; got " << var1->type << " expected " + << var2->type; + return false; + } + + // Cast var1->type from NPVariantType to int to avoid compiler warnings about + // not needing a default case when we have cases for every enum value. + switch (static_cast<int>(var1->type)) { + case NPVariantType_Int32: { + int32_t result = NPVARIANT_TO_INT32(*var1); + int32_t expected = NPVARIANT_TO_INT32(*var2); + if (result != expected) { + id->err << "Variant values don't match; got " << result << " expected " + << expected; + success = false; + } + break; + } + case NPVariantType_Double: { + double result = NPVARIANT_TO_DOUBLE(*var1); + double expected = NPVARIANT_TO_DOUBLE(*var2); + if (result != expected) { + id->err << "Variant values don't match (double)"; + success = false; + } + break; + } + case NPVariantType_Void: { + // void values are always equivalent + break; + } + case NPVariantType_Null: { + // null values are always equivalent + break; + } + case NPVariantType_Bool: { + bool result = NPVARIANT_TO_BOOLEAN(*var1); + bool expected = NPVARIANT_TO_BOOLEAN(*var2); + if (result != expected) { + id->err << "Variant values don't match (bool)"; + success = false; + } + break; + } + case NPVariantType_String: { + const NPString* result = &NPVARIANT_TO_STRING(*var1); + const NPString* expected = &NPVARIANT_TO_STRING(*var2); + if (strcmp(result->UTF8Characters, expected->UTF8Characters) || + strlen(result->UTF8Characters) != strlen(expected->UTF8Characters)) { + id->err << "Variant values don't match; got " << result->UTF8Characters + << " expected " << expected->UTF8Characters; + success = false; + } + break; + } + case NPVariantType_Object: { + uint32_t i, identifierCount = 0; + NPIdentifier* identifiers; + NPObject* result = NPVARIANT_TO_OBJECT(*var1); + NPObject* expected = NPVARIANT_TO_OBJECT(*var2); + bool enumerate_result = + NPN_Enumerate(instance, expected, &identifiers, &identifierCount); + if (!enumerate_result) { + id->err << "NPN_Enumerate failed"; + success = false; + } + for (i = 0; i < identifierCount; i++) { + NPVariant resultVariant, expectedVariant; + if (!NPN_GetProperty(instance, expected, identifiers[i], + &expectedVariant)) { + id->err << "NPN_GetProperty returned false"; + success = false; + } else { + if (!NPN_HasProperty(instance, result, identifiers[i])) { + id->err << "NPN_HasProperty returned false"; + success = false; + } else { + if (!NPN_GetProperty(instance, result, identifiers[i], + &resultVariant)) { + id->err << "NPN_GetProperty 2 returned false"; + success = false; + } else { + success = + compareVariants(instance, &resultVariant, &expectedVariant); + NPN_ReleaseVariantValue(&expectedVariant); + } + } + NPN_ReleaseVariantValue(&resultVariant); + } + } + NPN_MemFree(identifiers); + break; + } + default: + id->err << "Unknown variant type"; + success = false; + MOZ_ASSERT_UNREACHABLE("Unknown variant type?!"); + } + + return success; +} + +static bool throwExceptionNextInvoke(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->throwOnNextInvoke = true; + BOOLEAN_TO_NPVARIANT(true, *result); + return true; +} + +static bool npnInvokeDefaultTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPIdentifier objectIdentifier = variantToIdentifier(args[0]); + if (!objectIdentifier) return false; + + NPVariant objectVariant; + if (NPN_GetProperty(npp, windowObject, objectIdentifier, &objectVariant)) { + if (NPVARIANT_IS_OBJECT(objectVariant)) { + NPObject* selfObject = NPVARIANT_TO_OBJECT(objectVariant); + if (selfObject != nullptr) { + NPVariant resultVariant; + if (NPN_InvokeDefault(npp, selfObject, + argCount > 1 ? &args[1] : nullptr, argCount - 1, + &resultVariant)) { + *result = resultVariant; + success = true; + } + } + } + NPN_ReleaseVariantValue(&objectVariant); + } + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool npnInvokeTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->err.str(""); + if (argCount < 2) return false; + + NPIdentifier function = variantToIdentifier(args[0]); + if (!function) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + NPVariant invokeResult; + bool invokeReturn = + NPN_Invoke(npp, windowObject, function, argCount > 2 ? &args[2] : nullptr, + argCount - 2, &invokeResult); + + bool compareResult = compareVariants(npp, &invokeResult, &args[1]); + + NPN_ReleaseObject(windowObject); + NPN_ReleaseVariantValue(&invokeResult); + BOOLEAN_TO_NPVARIANT(invokeReturn && compareResult, *result); + return true; +} + +static bool npnEvaluateTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + bool success = false; + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (argCount != 1) return false; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return false; + + success = NPN_Evaluate(npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(args[0]), result); + + NPN_ReleaseObject(windowObject); + return success; +} + +static bool setUndefinedValueTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + NPError err = NPN_SetValue(npp, (NPPVariable)0x0, 0x0); + BOOLEAN_TO_NPVARIANT((err == NPERR_NO_ERROR), *result); + return true; +} + +static bool identifierToStringTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + NPIdentifier identifier = variantToIdentifier(args[0]); + if (!identifier) return false; + + NPUTF8* utf8String = NPN_UTF8FromIdentifier(identifier); + if (!utf8String) return false; + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool queryPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPBool pms = false; + NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, NPNVprivateModeBool, + &pms); + BOOLEAN_TO_NPVARIANT(pms, *result); + return true; +} + +static bool lastReportedPrivateModeState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->lastReportedPrivateModeState, *result); + return true; +} + +static bool hasWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + BOOLEAN_TO_NPVARIANT(id->hasWidget, *result); + return true; +} + +static bool getEdge(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[0]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetEdge(id, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectCount(id); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool getClipRegionRectEdge(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 2) return false; + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t rectIndex = NPVARIANT_TO_INT32(args[0]); + if (rectIndex < 0) return false; + if (!NPVARIANT_IS_INT32(args[1])) return false; + int32_t edge = NPVARIANT_TO_INT32(args[1]); + if (edge < EDGE_LEFT || edge > EDGE_BOTTOM) return false; + + InstanceData* id = + static_cast<InstanceData*>(static_cast<TestNPObject*>(npobj)->npp->pdata); + int32_t r = pluginGetClipRegionRectEdge(id, rectIndex, RectEdge(edge)); + if (r == NPTEST_INT32_ERROR) return false; + INT32_TO_NPVARIANT(r, *result); + return true; +} + +static bool startWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (sWatchingInstanceCount) return false; + + sWatchingInstanceCount = true; + sInstanceCount = 0; + ++sCurrentInstanceCountWatchGeneration; + return true; +} + +static bool getInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + INT32_TO_NPVARIANT(sInstanceCount, *result); + return true; +} + +static bool stopWatchingInstanceCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + if (!sWatchingInstanceCount) return false; + + sWatchingInstanceCount = false; + return true; +} + +static bool getLastMouseX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseX, *result); + return true; +} + +static bool getLastMouseY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->lastMouseY, *result); + return true; +} + +static bool getPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->paintCount, *result); + return true; +} + +static bool resetPaintCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->paintCount = 0; + return true; +} + +static bool getWidthAtLastPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->widthAtLastPaint, *result); + return true; +} + +static bool setInvalidateDuringPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool doInvalidate = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->invalidateDuringPaint = doInvalidate; + return true; +} + +static bool setSlowPaint(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool slow = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->slowPaint = slow; + return true; +} + +static bool getError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (id->err.str().length() == 0) { + char* outval = NPN_StrDup(SUCCESS_STRING); + STRINGZ_TO_NPVARIANT(outval, *result); + } else { + char* outval = NPN_StrDup(id->err.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + } + return true; +} + +static bool doInternalConsistencyCheck(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string error; + pluginDoInternalConsistencyCheck(id, error); + NPUTF8* utf8String = (NPUTF8*)NPN_MemAlloc(error.length() + 1); + if (!utf8String) { + return false; + } + memcpy(utf8String, error.c_str(), error.length() + 1); + STRINGZ_TO_NPVARIANT(utf8String, *result); + return true; +} + +static bool convertPointX(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultX, *result); + return true; +} + +static bool convertPointY(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 4) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_INT32(args[0])) return false; + int32_t sourceSpace = NPVARIANT_TO_INT32(args[0]); + + if (!NPVARIANT_IS_INT32(args[1])) return false; + double sourceX = static_cast<double>(NPVARIANT_TO_INT32(args[1])); + + if (!NPVARIANT_IS_INT32(args[2])) return false; + double sourceY = static_cast<double>(NPVARIANT_TO_INT32(args[2])); + + if (!NPVARIANT_IS_INT32(args[3])) return false; + int32_t destSpace = NPVARIANT_TO_INT32(args[3]); + + double resultX, resultY; + NPN_ConvertPoint(npp, sourceX, sourceY, (NPCoordinateSpace)sourceSpace, + &resultX, &resultY, (NPCoordinateSpace)destSpace); + + DOUBLE_TO_NPVARIANT(resultY, *result); + return true; +} + +static bool streamTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // .streamTest(url, doPost, postData, writeCallback, notifyCallback, + // redirectCallback, allowRedirects, postFile = false) + if (!(7 <= argCount && argCount <= 8)) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString url = NPVARIANT_TO_STRING(args[0]); + + if (!NPVARIANT_IS_BOOLEAN(args[1])) return false; + bool doPost = NPVARIANT_TO_BOOLEAN(args[1]); + + NPString postData = {nullptr, 0}; + if (NPVARIANT_IS_STRING(args[2])) { + postData = NPVARIANT_TO_STRING(args[2]); + } else { + if (!NPVARIANT_IS_NULL(args[2])) { + return false; + } + } + + NPObject* writeCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[3])) { + writeCallback = NPVARIANT_TO_OBJECT(args[3]); + } else { + if (!NPVARIANT_IS_NULL(args[3])) { + return false; + } + } + + NPObject* notifyCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[4])) { + notifyCallback = NPVARIANT_TO_OBJECT(args[4]); + } else { + if (!NPVARIANT_IS_NULL(args[4])) { + return false; + } + } + + NPObject* redirectCallback = nullptr; + if (NPVARIANT_IS_OBJECT(args[5])) { + redirectCallback = NPVARIANT_TO_OBJECT(args[5]); + } else { + if (!NPVARIANT_IS_NULL(args[5])) { + return false; + } + } + + if (!NPVARIANT_IS_BOOLEAN(args[6])) return false; + bool allowRedirects = NPVARIANT_TO_BOOLEAN(args[6]); + + bool postFile = false; + if (argCount >= 8) { + if (!NPVARIANT_IS_BOOLEAN(args[7])) { + return false; + } + postFile = NPVARIANT_TO_BOOLEAN(args[7]); + } + + URLNotifyData* ndata = new URLNotifyData; + ndata->cookie = "dynamic-cookie"; + ndata->writeCallback = writeCallback; + ndata->notifyCallback = notifyCallback; + ndata->redirectCallback = redirectCallback; + ndata->size = 0; + ndata->data = nullptr; + ndata->allowRedirects = allowRedirects; + + /* null-terminate "url" */ + char* urlstr = (char*)malloc(url.UTF8Length + 1); + strncpy(urlstr, url.UTF8Characters, url.UTF8Length); + urlstr[url.UTF8Length] = '\0'; + + NPError err; + if (doPost) { + err = NPN_PostURLNotify(npp, urlstr, nullptr, postData.UTF8Length, + postData.UTF8Characters, postFile, ndata); + } else { + err = NPN_GetURLNotify(npp, urlstr, nullptr, ndata); + } + + free(urlstr); + + if (NPERR_NO_ERROR == err) { + if (ndata->writeCallback) { + NPN_RetainObject(ndata->writeCallback); + } + if (ndata->notifyCallback) { + NPN_RetainObject(ndata->notifyCallback); + } + if (ndata->redirectCallback) { + NPN_RetainObject(ndata->redirectCallback); + } + BOOLEAN_TO_NPVARIANT(true, *result); + } else { + delete ndata; + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + string url; + { + if (!NPVARIANT_IS_STRING(args[0])) return false; + NPString npurl = NPVARIANT_TO_STRING(args[0]); + // make a copy to ensure that the url string is null-terminated + url = string(npurl.UTF8Characters, npurl.UTF8Length); + } + + NPError err; + { + string buf("/path/to/file"); + err = NPN_PostURL(npp, url.c_str(), nullptr /* target */, buf.length(), + buf.c_str(), true /* file */); + } + + BOOLEAN_TO_NPVARIANT(NPERR_NO_ERROR == err, *result); + return true; +} + +static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (1 != argCount) return false; + + if (!NPVARIANT_IS_BOOLEAN(args[0])) return false; + bool wantsAllStreams = NPVARIANT_TO_BOOLEAN(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->wantsAllStreams = wantsAllStreams; + + return true; +} + +static bool crashPlugin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + IntentionalCrash(); + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->crashOnDestroy = true; + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool setColor(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) return false; + if (!NPVARIANT_IS_STRING(args[0])) return false; + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + id->scriptableObject->drawColor = + parseHexColor(str->UTF8Characters, str->UTF8Length); + + NPRect r; + r.left = 0; + r.top = 0; + r.right = id->window.width; + r.bottom = id->window.height; + if (id->asyncDrawing == AD_NONE) { + NPN_InvalidateRect(npp, &r); + } else if (id->asyncDrawing == AD_BITMAP) { + drawAsyncBitmapColor(id); + } + + VOID_TO_NPVARIANT(*result); + return true; +} + +void notifyDidPaint(InstanceData* instanceData) { + ++instanceData->paintCount; + instanceData->widthAtLastPaint = instanceData->window.width; + + if (instanceData->invalidateDuringPaint) { + NPRect r; + r.left = 0; + r.top = 0; + r.right = instanceData->window.width; + r.bottom = instanceData->window.height; + NPN_InvalidateRect(instanceData->npp, &r); + } + + if (instanceData->slowPaint) { + XPSleep(1); + } + + if (instanceData->runScriptOnPaint) { + NPObject* o = nullptr; + NPN_GetValue(instanceData->npp, NPNVPluginElementNPObject, &o); + if (o) { + NPVariant param; + STRINGZ_TO_NPVARIANT("paintscript", param); + NPVariant result; + NPN_Invoke(instanceData->npp, o, NPN_GetStringIdentifier("getAttribute"), + ¶m, 1, &result); + + if (NPVARIANT_IS_STRING(result)) { + NPObject* windowObject; + NPN_GetValue(instanceData->npp, NPNVWindowNPObject, &windowObject); + if (windowObject) { + NPVariant evalResult; + NPN_Evaluate(instanceData->npp, windowObject, + (NPString*)&NPVARIANT_TO_STRING(result), &evalResult); + NPN_ReleaseVariantValue(&evalResult); + NPN_ReleaseObject(windowObject); + } + } + + NPN_ReleaseVariantValue(&result); + NPN_ReleaseObject(o); + } + } +} + +static const NPClass kTestSharedNPClass = { + NP_CLASS_STRUCT_VERSION, + // Everything else is nullptr +}; + +static bool getObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* o = + NPN_CreateObject(npp, const_cast<NPClass*>(&kTestSharedNPClass)); + if (!o) return false; + + OBJECT_TO_NPVARIANT(o, *result); + return true; +} + +static bool checkObjectValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + + if (1 != argCount) return false; + + if (!NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* o = NPVARIANT_TO_OBJECT(args[0]); + + BOOLEAN_TO_NPVARIANT(o->_class == &kTestSharedNPClass, *result); + return true; +} + +static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + VOID_TO_NPVARIANT(*result); + +#if defined(XP_WIN) && defined(_M_IX86) + _control87(0, _MCW_EM); + return true; +#else + return false; +#endif +} + +static void timerCallback(NPP npp, uint32_t timerID) { + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount++; + timerEvent event = timerEvents[currentTimerEventCount]; + + NPObject* windowObject; + NPN_GetValue(npp, NPNVWindowNPObject, &windowObject); + if (!windowObject) return; + + NPVariant rval; + if (timerID != id->timerID[event.timerIdReceive]) { + id->timerTestResult = false; + } + + if (currentTimerEventCount == totalTimerEvents - 1) { + NPVariant arg; + BOOLEAN_TO_NPVARIANT(id->timerTestResult, arg); + NPN_Invoke(npp, windowObject, + NPN_GetStringIdentifier(id->timerTestScriptCallback.c_str()), + &arg, 1, &rval); + NPN_ReleaseVariantValue(&arg); + } + + NPN_ReleaseObject(windowObject); + + if (event.timerIdSchedule > -1) { + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + } + if (event.timerIdUnschedule > -1) { + NPN_UnscheduleTimer(npp, id->timerID[event.timerIdUnschedule]); + } +} + +static bool timerTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + currentTimerEventCount = 0; + + if (argCount < 1 || !NPVARIANT_IS_STRING(args[0])) return false; + const NPString* argstr = &NPVARIANT_TO_STRING(args[0]); + id->timerTestScriptCallback = argstr->UTF8Characters; + + id->timerTestResult = true; + timerEvent event = timerEvents[currentTimerEventCount]; + + id->timerID[event.timerIdSchedule] = NPN_ScheduleTimer( + npp, event.timerInterval, event.timerRepeat, timerCallback); + + return id->timerID[event.timerIdSchedule] != 0; +} + +bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + mozilla::NoteIntentionalCrash("plugin"); + + bool busyHang = false; + if ((argCount == 1) && NPVARIANT_IS_BOOLEAN(args[0])) { + busyHang = NPVARIANT_TO_BOOLEAN(args[0]); + } + + if (busyHang) { + const time_t start = std::time(nullptr); + while ((std::time(nullptr) - start) < 100000) { + volatile int dummy = 0; + for (int i = 0; i < 1000; ++i) { + dummy++; + } + } + } else { +#ifdef XP_WIN + Sleep(100000000); + Sleep(100000000); +#else + pause(); + pause(); +#endif + } + + // NB: returning true here means that we weren't terminated, and + // thus the hang detection/handling didn't work correctly. The + // test harness will succeed in calling this function, and the + // test will fail. + return true; +} + +bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + uint32_t stallTimeSeconds = 0; + if ((argCount == 1) && NPVARIANT_IS_INT32(args[0])) { + stallTimeSeconds = (uint32_t)NPVARIANT_TO_INT32(args[0]); + } + +#ifdef XP_WIN + Sleep(stallTimeSeconds * 1000U); +#else + sleep(stallTimeSeconds); +#endif + + return true; +} + +#if defined(MOZ_WIDGET_GTK) +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + string sel = pluginGetClipboardText(id); + + uint32_t len = sel.size(); + char* selCopy = static_cast<char*>(NPN_MemAlloc(1 + len)); + if (!selCopy) return false; + + memcpy(selCopy, sel.c_str(), len); + selCopy[len] = '\0'; + + STRINGN_TO_NPVARIANT(selCopy, len, *result); + // *result owns str now + + return true; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginCrashInNestedLoop(id); +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginTriggerXError(id); +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + return pluginDestroySharedGfxStuff(id); +} + +#else +bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool triggerXError(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + // XXX Not implemented! + return false; +} + +bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +#if defined(XP_WIN) +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + bool visible = pluginNativeWidgetIsVisible(id); + BOOLEAN_TO_NPVARIANT(visible, *result); + return true; +} +#else +bool nativeWidgetIsVisible(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + // XXX Not implemented! + return false; +} +#endif + +bool getLastCompositionText(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { +#ifdef XP_WIN + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + char* outval = NPN_StrDup(id->lastComposition.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +#else + // XXX not implemented + return false; +#endif +} + +bool scriptableInvokeDefault(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + ostringstream value; + value << sPluginName; + for (uint32_t i = 0; i < argCount; i++) { + switch (args[i].type) { + case NPVariantType_Int32: + value << ";" << NPVARIANT_TO_INT32(args[i]); + break; + case NPVariantType_String: { + const NPString* argstr = &NPVARIANT_TO_STRING(args[i]); + value << ";" << argstr->UTF8Characters; + break; + } + case NPVariantType_Void: + value << ";undefined"; + break; + case NPVariantType_Null: + value << ";null"; + break; + default: + value << ";other"; + } + } + + char* outval = NPN_StrDup(value.str().c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +static const NPClass kInvokeDefaultClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + scriptableInvokeDefault, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getInvokeDefaultObject(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (0 != argCount) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + NPObject* testObject = + NPN_CreateObject(npp, const_cast<NPClass*>(&kInvokeDefaultClass)); + OBJECT_TO_NPVARIANT(testObject, *result); + return true; +} + +bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (id->callOnDestroy) return false; + + if (1 != argCount || !NPVARIANT_IS_OBJECT(args[0])) return false; + + id->callOnDestroy = NPVARIANT_TO_OBJECT(args[0]); + NPN_RetainObject(id->callOnDestroy); + + return true; +} + +// On Linux at least, a windowed plugin resize causes Flash Player to +// reconnect to the browser window. This method simulates that. +bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + if (!id->hasWidget) return false; + + pluginWidgetInit(id, id->window.window); + return true; +} + +bool propertyAndMethod(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + INT32_TO_NPVARIANT(5, *result); + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventWindowFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->topLevelWindowActivationState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->topLevelWindowActivationState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->topLevelWindowActivationState == + ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getTopLevelWindowActivationEventCount(NPObject* npobj, + const NPVariant* args, + uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->topLevelWindowActivationEventCount, *result); + + return true; +} + +// Returns top-level window activation state as indicated by Cocoa NPAPI's +// NPCocoaEventFocusChanged events - 'true' if active, 'false' if not. +// Throws an exception if no events have been received and thus this state +// is unknown. +bool getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + // Throw an exception for unknown state. + if (id->focusState == ACTIVATION_STATE_UNKNOWN) { + return false; + } + + if (id->focusState == ACTIVATION_STATE_ACTIVATED) { + BOOLEAN_TO_NPVARIANT(true, *result); + } else if (id->focusState == ACTIVATION_STATE_DEACTIVATED) { + BOOLEAN_TO_NPVARIANT(false, *result); + } + + return true; +} + +bool getFocusEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->focusEventCount, *result); + + return true; +} + +bool getEventModel(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + INT32_TO_NPVARIANT(id->eventModel, *result); + + return true; +} + +static bool ReflectorHasMethod(NPObject* npobj, NPIdentifier name) { + return false; +} + +static bool ReflectorHasProperty(NPObject* npobj, NPIdentifier name) { + return true; +} + +static bool ReflectorGetProperty(NPObject* npobj, NPIdentifier name, + NPVariant* result) { + if (NPN_IdentifierIsString(name)) { + char* s = NPN_UTF8FromIdentifier(name); + STRINGZ_TO_NPVARIANT(s, *result); + return true; + } + + INT32_TO_NPVARIANT(NPN_IntFromIdentifier(name), *result); + return true; +} + +static const NPClass kReflectorNPClass = {NP_CLASS_STRUCT_VERSION, + nullptr, + nullptr, + nullptr, + ReflectorHasMethod, + nullptr, + nullptr, + ReflectorHasProperty, + ReflectorGetProperty, + nullptr, + nullptr, + nullptr, + nullptr}; + +bool getReflector(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (0 != argCount) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + NPObject* reflector = + NPN_CreateObject(npp, + const_cast<NPClass*>(&kReflectorNPClass)); // retains + OBJECT_TO_NPVARIANT(reflector, *result); + return true; +} + +bool isVisible(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + BOOLEAN_TO_NPVARIANT( + id->window.clipRect.top != 0 || id->window.clipRect.left != 0 || + id->window.clipRect.bottom != 0 || id->window.clipRect.right != 0, + *result); + return true; +} + +bool getWindowPosition(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + NPObject* window = nullptr; + NPError err = NPN_GetValue(npp, NPNVWindowNPObject, &window); + if (NPERR_NO_ERROR != err || !window) return false; + + NPIdentifier arrayID = NPN_GetStringIdentifier("Array"); + NPVariant arrayFunctionV; + bool ok = NPN_GetProperty(npp, window, arrayID, &arrayFunctionV); + + NPN_ReleaseObject(window); + + if (!ok) return false; + + if (!NPVARIANT_IS_OBJECT(arrayFunctionV)) { + NPN_ReleaseVariantValue(&arrayFunctionV); + return false; + } + NPObject* arrayFunction = NPVARIANT_TO_OBJECT(arrayFunctionV); + + NPVariant elements[4]; + INT32_TO_NPVARIANT(id->window.x, elements[0]); + INT32_TO_NPVARIANT(id->window.y, elements[1]); + INT32_TO_NPVARIANT(id->window.width, elements[2]); + INT32_TO_NPVARIANT(id->window.height, elements[3]); + + ok = NPN_InvokeDefault(npp, arrayFunction, elements, 4, result); + + NPN_ReleaseObject(arrayFunction); + + return ok; +} + +bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount == 0 || !NPVARIANT_IS_OBJECT(args[0])) return false; + + NPObject* ctor = NPVARIANT_TO_OBJECT(args[0]); + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + return NPN_Construct(npp, ctor, args + 1, argCount - 1, result); +} + +bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_STRING(args[0])) return false; + + // Clear existing data. + delete sSitesWithData; + + const NPString* str = &NPVARIANT_TO_STRING(args[0]); + if (str->UTF8Length == 0) return true; + + // Parse the comma-delimited string into a vector. + sSitesWithData = new list<siteData>; + const char* iterator = str->UTF8Characters; + const char* end = iterator + str->UTF8Length; + while (1) { + const char* next = strchr(iterator, ','); + if (!next) next = end; + + // Parse out the three tokens into a siteData struct. + const char* siteEnd = strchr(iterator, ':'); + *((char*)siteEnd) = '\0'; + const char* flagsEnd = strchr(siteEnd + 1, ':'); + *((char*)flagsEnd) = '\0'; + *((char*)next) = '\0'; + + siteData data; + data.site = string(iterator); + data.flags = atoi(siteEnd + 1); + data.age = atoi(flagsEnd + 1); + + sSitesWithData->push_back(data); + + if (next == end) break; + + iterator = next + 1; + } + + return true; +} + +bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 1 || !NPVARIANT_IS_BOOLEAN(args[0])) return false; + + sClearByAgeSupported = NPVARIANT_TO_BOOLEAN(args[0]); + return true; +} + +bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + + char* outval = NPN_StrDup(id->lastKeyText.c_str()); + STRINGZ_TO_NPVARIANT(outval, *result); + return true; +} + +bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + + char* origin = nullptr; + NPError err = NPN_GetValue(npp, NPNVdocumentOrigin, &origin); + if (err != NPERR_NO_ERROR) { + return false; + } + + STRINGZ_TO_NPVARIANT(origin, *result); + return true; +} + +bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + INT32_TO_NPVARIANT(id->mouseUpEventCount, *result); + return true; +} + +bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double scaleFactor = 1.0; +#if defined(XP_MACOSX) || defined(XP_WIN) + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVcontentsScaleFactor, &scaleFactor); + if (err != NPERR_NO_ERROR) { + return false; + } +#endif + DOUBLE_TO_NPVARIANT(scaleFactor, *result); + return true; +} + +bool queryCSSZoomFactorSetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + if (!npp) { + return false; + } + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + if (!id) { + return false; + } + DOUBLE_TO_NPVARIANT(id->cssZoomFactor, *result); + return true; +} + +bool queryCSSZoomFactorGetValue(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) return false; + + double zoomFactor = 1.0; + NPError err = NPN_GetValue(static_cast<TestNPObject*>(npobj)->npp, + NPNVCSSZoomFactor, &zoomFactor); + if (err != NPERR_NO_ERROR) { + return false; + } + DOUBLE_TO_NPVARIANT(zoomFactor, *result); + return true; +} + +bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + if (argCount != 1) { + return false; + } + + if (!NPVARIANT_IS_STRING(args[0])) { + return false; + } + + const NPString& arg = NPVARIANT_TO_STRING(args[0]); + NPUTF8* buffer = + static_cast<NPUTF8*>(NPN_MemAlloc(sizeof(NPUTF8) * arg.UTF8Length)); + if (!buffer) { + return false; + } + + std::copy(arg.UTF8Characters, arg.UTF8Characters + arg.UTF8Length, buffer); + STRINGN_TO_NPVARIANT(buffer, arg.UTF8Length, *result); + + return true; +} + +static bool toggleAudioPlayback(NPObject* npobj, uint32_t argCount, + bool playingAudio, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + id->playingAudio = playingAudio; + + NPN_SetValue(npp, NPPVpluginIsPlayingAudio, (void*)playingAudio); + + VOID_TO_NPVARIANT(*result); + return true; +} + +static bool startAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, true, result); +} + +static bool stopAudioPlayback(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + return toggleAudioPlayback(npobj, argCount, false, result); +} + +static bool getAudioMuted(NPObject* npobj, const NPVariant* args, + uint32_t argCount, NPVariant* result) { + if (argCount != 0) { + return false; + } + + NPP npp = static_cast<TestNPObject*>(npobj)->npp; + InstanceData* id = static_cast<InstanceData*>(npp->pdata); + BOOLEAN_TO_NPVARIANT(id->audioMuted, *result); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest.def b/dom/plugins/test/testplugin/nptest.def new file mode 100644 index 0000000000..4c543d5b9f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/nptest.h b/dom/plugins/test/testplugin/nptest.h new file mode 100644 index 0000000000..3bac9cb65b --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.h @@ -0,0 +1,150 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_h_ +#define nptest_h_ + +#include "mozilla-config.h" + +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include <stdint.h> +#include <string> +#include <sstream> + +typedef enum { DM_DEFAULT, DM_SOLID_COLOR } DrawMode; + +typedef enum { + FUNCTION_NONE, + FUNCTION_NPP_GETURL, + FUNCTION_NPP_GETURLNOTIFY, + FUNCTION_NPP_POSTURL, + FUNCTION_NPP_POSTURLNOTIFY, + FUNCTION_NPP_NEWSTREAM, + FUNCTION_NPP_WRITEREADY, + FUNCTION_NPP_WRITE, + FUNCTION_NPP_DESTROYSTREAM, + FUNCTION_NPP_WRITE_RPC +} TestFunction; + +typedef enum { AD_NONE, AD_BITMAP, AD_DXGI } AsyncDrawing; + +typedef enum { + ACTIVATION_STATE_UNKNOWN, + ACTIVATION_STATE_ACTIVATED, + ACTIVATION_STATE_DEACTIVATED +} ActivationState; + +typedef struct FunctionTable { + TestFunction funcId; + const char* funcName; +} FunctionTable; + +typedef enum { POSTMODE_FRAME, POSTMODE_STREAM } PostMode; + +typedef struct TestNPObject : NPObject { + NPP npp; + DrawMode drawMode; + uint32_t drawColor; // 0xAARRGGBB +} TestNPObject; + +typedef struct _PlatformData PlatformData; + +typedef struct InstanceData { + NPP npp; + NPWindow window; + TestNPObject* scriptableObject; + PlatformData* platformData; + int32_t instanceCountWatchGeneration; + bool lastReportedPrivateModeState; + bool hasWidget; + bool npnNewStream; + bool throwOnNextInvoke; + bool runScriptOnPaint; + bool dontTouchElement; + uint32_t timerID[2]; + bool timerTestResult; + bool invalidateDuringPaint; + bool slowPaint; + bool playingAudio; + bool audioMuted; + int32_t winX; + int32_t winY; + int32_t lastMouseX; + int32_t lastMouseY; + int32_t widthAtLastPaint; + int32_t paintCount; + int32_t writeCount; + int32_t writeReadyCount; + TestFunction testFunction; + TestFunction functionToFail; + NPError failureCode; + NPObject* callOnDestroy; + PostMode postMode; + std::string testUrl; + std::string frame; + std::string timerTestScriptCallback; + std::ostringstream err; + int32_t streamChunkSize; + int32_t streamBufSize; + void* streamBuf; + bool crashOnDestroy; + bool cleanupWidget; + ActivationState topLevelWindowActivationState; + int32_t topLevelWindowActivationEventCount; + ActivationState focusState; + int32_t focusEventCount; + int32_t eventModel; + bool closeStream; + std::string lastKeyText; + bool wantsAllStreams; + int32_t mouseUpEventCount; + int32_t bugMode; + AsyncDrawing asyncDrawing; + NPAsyncSurface* frontBuffer; + NPAsyncSurface* backBuffer; + std::string lastComposition; + void* placeholderWnd; + double cssZoomFactor; +} InstanceData; + +void notifyDidPaint(InstanceData* instanceData); + +#if defined(XP_WIN) +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData); +void drawDxgiBitmapColor(InstanceData* instanceData); +#endif + +#endif // nptest_h_ diff --git a/dom/plugins/test/testplugin/nptest.rc b/dom/plugins/test/testplugin/nptest.rc new file mode 100644 index 0000000000..948fb846ef --- /dev/null +++ b/dom/plugins/test/testplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Plug-in for testing purposes.\x2122 (\x0939\x093f\x0928\x094d\x0926\x0940 \x4e2d\x6587 \x0627\x0644\x0639\x0631\x0628\x064a\x0629)" + VALUE "FileExtents", "tst" + VALUE "FileOpenName", L"Test \x2122 mimetype" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "nptest" + VALUE "MIMEType", "application/x-test" + VALUE "OriginalFilename", "nptest.dll" + VALUE "ProductName", "Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/nptest_droid.cpp b/dom/plugins/test/testplugin/nptest_droid.cpp new file mode 100644 index 0000000000..733f18befe --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_droid.cpp @@ -0,0 +1,80 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2010, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Brad Lassey <blassey@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ +#include "nptest_platform.h" +#include "npapi.h" + +struct _PlatformData {}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + printf("NPERR_INCOMPATIBLE_VERSION_ERROR\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // XXX nothing here yet since we don't support windowed plugins +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { return 0; } + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + // XXX nothing here yet since we don't support windowed plugins + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_gtk2.cpp b/dom/plugins/test/testplugin/nptest_gtk2.cpp new file mode 100644 index 0000000000..c7779b1d29 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_gtk2.cpp @@ -0,0 +1,707 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Michael Ventnor <mventnor@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "npapi.h" +#include <pthread.h> +#include <gdk/gdk.h> +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include <X11/extensions/shape.h> +#endif +#include <glib.h> +#include <gtk/gtk.h> +#include <unistd.h> + +#include "mozilla/Assertions.h" +#include "mozilla/IntentionalCrash.h" + +struct _PlatformData { +#ifdef MOZ_X11 + Display* display; + Visual* visual; + Colormap colormap; +#endif + GtkWidget* plug; +}; + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { +#ifdef MOZ_X11 + instanceData->platformData = + static_cast<PlatformData*>(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->display = nullptr; + instanceData->platformData->visual = nullptr; + instanceData->platformData->colormap = X11None; + instanceData->platformData->plug = nullptr; + + return NPERR_NO_ERROR; +#else + // we only support X11 here, since thats what the plugin system uses + return NPERR_INCOMPATIBLE_VERSION_ERROR; +#endif +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + if (instanceData->hasWidget) { + Window window = reinterpret_cast<XID>(instanceData->window.window); + + if (window != X11None) { + // This window XID should still be valid. + // See bug 429604 and bug 454756. + XWindowAttributes attributes; + if (!XGetWindowAttributes(instanceData->platformData->display, window, + &attributes)) + g_error("XGetWindowAttributes failed at plugin instance shutdown"); + } + } + + GtkWidget* plug = instanceData->platformData->plug; + if (plug) { + instanceData->platformData->plug = 0; + if (instanceData->cleanupWidget) { + // Default/tidy behavior + gtk_widget_destroy(plug); + } else { + // Flash Player style: let the GtkPlug destroy itself on disconnect. + g_signal_handlers_disconnect_matched(plug, G_SIGNAL_MATCH_DATA, 0, 0, + nullptr, nullptr, instanceData); + } + } + + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; +} + +static void SetCairoRGBA(cairo_t* cairoWindow, uint32_t rgba) { + float b = (rgba & 0xFF) / 255.0; + float g = ((rgba & 0xFF00) >> 8) / 255.0; + float r = ((rgba & 0xFF0000) >> 16) / 255.0; + float a = ((rgba & 0xFF000000) >> 24) / 255.0; + + cairo_set_source_rgba(cairoWindow, r, g, b, a); +} + +static void pluginDrawSolid(InstanceData* instanceData, GdkDrawable* gdkWindow, + int x, int y, int width, int height) { + cairo_t* cairoWindow = gdk_cairo_create(gdkWindow); + + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + NPRect* clip = &instanceData->window.clipRect; + cairo_rectangle(cairoWindow, clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top); + cairo_clip(cairoWindow); + + GdkRectangle windowRect = {x, y, width, height}; + gdk_cairo_rectangle(cairoWindow, &windowRect); + SetCairoRGBA(cairoWindow, instanceData->scriptableObject->drawColor); + + cairo_fill(cairoWindow); + cairo_destroy(cairoWindow); +} + +static void pluginDrawWindow(InstanceData* instanceData, GdkDrawable* gdkWindow, + const GdkRectangle& invalidRect) { + NPWindow& window = instanceData->window; + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + + int x = window.x; + int y = window.y; + int width = window.width; + int height = window.height; + + notifyDidPaint(instanceData); + + if (instanceData->scriptableObject->drawMode == DM_SOLID_COLOR) { + // drawing a solid color for reftests + pluginDrawSolid(instanceData, gdkWindow, invalidRect.x, invalidRect.y, + invalidRect.width, invalidRect.height); + return; + } + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + GdkGC* gdkContext = gdk_gc_new(gdkWindow); + if (!gdkContext) return; + + NPRect* clip = &window.clipRect; + GdkRectangle gdkClip = {clip->left, clip->top, clip->right - clip->left, + clip->bottom - clip->top}; + gdk_gc_set_clip_rectangle(gdkContext, &gdkClip); + + // draw a grey background for the plugin frame + GdkColor grey; + grey.red = grey.blue = grey.green = 32767; + gdk_gc_set_rgb_fg_color(gdkContext, &grey); + gdk_draw_rectangle(gdkWindow, gdkContext, TRUE, x, y, width, height); + + // draw a 3-pixel-thick black frame around the plugin + GdkColor black; + black.red = black.green = black.blue = 0; + gdk_gc_set_rgb_fg_color(gdkContext, &black); + gdk_gc_set_line_attributes(gdkContext, 3, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, + GDK_JOIN_MITER); + gdk_draw_rectangle(gdkWindow, gdkContext, FALSE, x + 1, y + 1, width - 3, + height - 3); + + // paint the UA string + PangoContext* pangoContext = gdk_pango_context_get(); + PangoLayout* pangoTextLayout = pango_layout_new(pangoContext); + pango_layout_set_width(pangoTextLayout, (width - 10) * PANGO_SCALE); + pango_layout_set_text(pangoTextLayout, uaString, -1); + gdk_draw_layout(gdkWindow, gdkContext, x + 5, y + 5, pangoTextLayout); + g_object_unref(pangoTextLayout); + + g_object_unref(gdkContext); +} + +static gboolean ExposeWidget(GtkWidget* widget, GdkEventExpose* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + pluginDrawWindow(instanceData, event->window, event->area); + return TRUE; +} + +static gboolean MotionEvent(GtkWidget* widget, GdkEventMotion* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + return TRUE; +} + +static gboolean ButtonEvent(GtkWidget* widget, GdkEventButton* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + instanceData->lastMouseX = event->x; + instanceData->lastMouseY = event->y; + if (event->type == GDK_BUTTON_RELEASE) { + instanceData->mouseUpEventCount++; + } + return TRUE; +} + +static gboolean DeleteWidget(GtkWidget* widget, GdkEvent* event, + gpointer user_data) { + InstanceData* instanceData = static_cast<InstanceData*>(user_data); + // Some plugins do not expect the plug to be removed from the socket before + // the plugin instance is destroyed. e.g. bug 485125 + if (instanceData->platformData->plug) g_error("plug removed"); // this aborts + + return FALSE; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; + +#ifdef MOZ_X11 + NPSetWindowCallbackStruct* ws_info = + static_cast<NPSetWindowCallbackStruct*>(newWindow->ws_info); + instanceData->platformData->display = ws_info->display; + instanceData->platformData->visual = ws_info->visual; + instanceData->platformData->colormap = ws_info->colormap; +#endif +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { +#ifdef MOZ_X11 + GtkWidget* oldPlug = instanceData->platformData->plug; + if (oldPlug) { + instanceData->platformData->plug = 0; + gtk_widget_destroy(oldPlug); + } + + GdkNativeWindow nativeWinId = + reinterpret_cast<XID>(instanceData->window.window); + + /* create a GtkPlug container */ + GtkWidget* plug = gtk_plug_new(nativeWinId); + + // Test for bugs 539138 and 561308 + if (!plug->window) g_error("Plug has no window"); // aborts + + /* make sure the widget is capable of receiving focus */ + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(plug), GTK_CAN_FOCUS); + + /* all the events that our widget wants to receive */ + gtk_widget_add_events(plug, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK); + g_signal_connect(plug, "expose-event", G_CALLBACK(ExposeWidget), + instanceData); + g_signal_connect(plug, "motion_notify_event", G_CALLBACK(MotionEvent), + instanceData); + g_signal_connect(plug, "button_press_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "button_release_event", G_CALLBACK(ButtonEvent), + instanceData); + g_signal_connect(plug, "delete-event", G_CALLBACK(DeleteWidget), + instanceData); + gtk_widget_show(plug); + + instanceData->platformData->plug = plug; +#endif +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { +#ifdef MOZ_X11 + XEvent* nsEvent = (XEvent*)event; + + switch (nsEvent->type) { + case GraphicsExpose: { + const XGraphicsExposeEvent& expose = nsEvent->xgraphicsexpose; + NPWindow& window = instanceData->window; + window.window = (void*)(expose.drawable); + + GdkNativeWindow nativeWinId = reinterpret_cast<XID>(window.window); + + GdkDisplay* gdkDisplay = gdk_x11_lookup_xdisplay(expose.display); + if (!gdkDisplay) { + g_warning("Display not opened by GDK"); + return 0; + } + // gdk_pixmap_foreign_new() doesn't check whether a GdkPixmap already + // exists, so check first. + // https://bugzilla.gnome.org/show_bug.cgi?id=590690 + GdkPixmap* gdkDrawable = + GDK_DRAWABLE(gdk_pixmap_lookup_for_display(gdkDisplay, nativeWinId)); + // If there is no existing GdkPixmap or it doesn't have a colormap then + // create our own. + if (gdkDrawable) { + GdkColormap* gdkColormap = gdk_drawable_get_colormap(gdkDrawable); + if (!gdkColormap) { + g_warning("No GdkColormap on GdkPixmap"); + return 0; + } + if (gdk_x11_colormap_get_xcolormap(gdkColormap) != + instanceData->platformData->colormap) { + g_warning("wrong Colormap"); + return 0; + } + if (gdk_x11_visual_get_xvisual(gdk_colormap_get_visual(gdkColormap)) != + instanceData->platformData->visual) { + g_warning("wrong Visual"); + return 0; + } + g_object_ref(gdkDrawable); + } else { + gdkDrawable = GDK_DRAWABLE( + gdk_pixmap_foreign_new_for_display(gdkDisplay, nativeWinId)); + VisualID visualID = instanceData->platformData->visual->visualid; + GdkVisual* gdkVisual = gdk_x11_screen_lookup_visual( + gdk_drawable_get_screen(gdkDrawable), visualID); + GdkColormap* gdkColormap = gdk_x11_colormap_foreign_new( + gdkVisual, instanceData->platformData->colormap); + gdk_drawable_set_colormap(gdkDrawable, gdkColormap); + g_object_unref(gdkColormap); + } + + const NPRect& clip = window.clipRect; + if (expose.x < clip.left || expose.y < clip.top || + expose.x + expose.width > clip.right || + expose.y + expose.height > clip.bottom) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in clip rectangle " + "(l=%d,t=%d,r=%d,b=%d)", + expose.x, expose.y, expose.width, expose.height, clip.left, + clip.top, clip.right, clip.bottom); + return 0; + } + if (expose.x < window.x || expose.y < window.y || + expose.x + expose.width > window.x + int32_t(window.width) || + expose.y + expose.height > window.y + int32_t(window.height)) { + g_warning( + "expose rectangle (x=%d,y=%d,w=%d,h=%d) not in plugin rectangle " + "(x=%d,y=%d,w=%d,h=%d)", + expose.x, expose.y, expose.width, expose.height, window.x, window.y, + window.width, window.height); + return 0; + } + + GdkRectangle invalidRect = {expose.x, expose.y, expose.width, + expose.height}; + pluginDrawWindow(instanceData, gdkDrawable, invalidRect); + g_object_unref(gdkDrawable); + break; + } + case MotionNotify: { + XMotionEvent* motion = &nsEvent->xmotion; + instanceData->lastMouseX = motion->x; + instanceData->lastMouseY = motion->y; + break; + } + case ButtonPress: + case ButtonRelease: { + XButtonEvent* button = &nsEvent->xbutton; + instanceData->lastMouseX = button->x; + instanceData->lastMouseY = button->y; + if (nsEvent->type == ButtonRelease) { + instanceData->mouseUpEventCount++; + } + break; + } + default: + break; + } +#endif + + return 0; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return NPTEST_INT32_ERROR; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return NPTEST_INT32_ERROR; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return NPTEST_INT32_ERROR; + + GdkWindow* toplevelGdk = 0; +#ifdef MOZ_X11 + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return NPTEST_INT32_ERROR; + toplevelGdk = gdk_window_foreign_new(toplevel); +#endif + if (!toplevelGdk) return NPTEST_INT32_ERROR; + + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + g_object_unref(toplevelGdk); + + gint pluginWidth, pluginHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &pluginWidth, &pluginHeight); + gint pluginOriginX, pluginOriginY; + gdk_window_get_origin(plugWnd, &pluginOriginX, &pluginOriginY); + gint pluginX = pluginOriginX - toplevelFrameExtents.x; + gint pluginY = pluginOriginY - toplevelFrameExtents.y; + + switch (edge) { + case EDGE_LEFT: + return pluginX; + case EDGE_TOP: + return pluginY; + case EDGE_RIGHT: + return pluginX + pluginWidth; + case EDGE_BOTTOM: + return pluginY + pluginHeight; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +#ifdef MOZ_X11 +static void intersectWithShapeRects(Display* display, Window window, int kind, + GdkRegion* region) { + int count = -1, order; + XRectangle* shapeRects = + XShapeGetRectangles(display, window, kind, &count, &order); + // The documentation says that shapeRects will be nullptr when the + // extension is not supported. Unfortunately XShapeGetRectangles + // also returns nullptr when the region is empty, so we can't treat + // nullptr as failure. I hope this way is OK. + if (count < 0) return; + + GdkRegion* shapeRegion = gdk_region_new(); + if (!shapeRegion) { + XFree(shapeRects); + return; + } + + for (int i = 0; i < count; ++i) { + XRectangle* r = &shapeRects[i]; + GdkRectangle rect = {r->x, r->y, r->width, r->height}; + gdk_region_union_with_rect(shapeRegion, &rect); + } + XFree(shapeRects); + + gdk_region_intersect(region, shapeRegion); + gdk_region_destroy(shapeRegion); +} +#endif + +static GdkRegion* computeClipRegion(InstanceData* instanceData) { + MOZ_RELEASE_ASSERT(!instanceData->hasWidget); + if (!instanceData->hasWidget) return 0; + + GtkWidget* plug = instanceData->platformData->plug; + if (!plug) return 0; + GdkWindow* plugWnd = plug->window; + if (!plugWnd) return 0; + + gint plugWidth, plugHeight; + gdk_drawable_get_size(GDK_DRAWABLE(plugWnd), &plugWidth, &plugHeight); + GdkRectangle pluginRect = {0, 0, plugWidth, plugHeight}; + GdkRegion* region = gdk_region_rectangle(&pluginRect); + if (!region) return 0; + + int pluginX = 0, pluginY = 0; + +#ifdef MOZ_X11 + Display* display = GDK_WINDOW_XDISPLAY(plugWnd); + Window window = GDK_WINDOW_XWINDOW(plugWnd); + + Window toplevel = 0; + NPN_GetValue(instanceData->npp, NPNVnetscapeWindow, &toplevel); + if (!toplevel) return 0; + + for (;;) { + Window root; + int x, y; + unsigned int width, height, border_width, depth; + if (!XGetGeometry(display, window, &root, &x, &y, &width, &height, + &border_width, &depth)) { + gdk_region_destroy(region); + return 0; + } + + GdkRectangle windowRect = {0, 0, static_cast<gint>(width), + static_cast<gint>(height)}; + GdkRegion* windowRgn = gdk_region_rectangle(&windowRect); + if (!windowRgn) { + gdk_region_destroy(region); + return 0; + } + intersectWithShapeRects(display, window, ShapeBounding, windowRgn); + intersectWithShapeRects(display, window, ShapeClip, windowRgn); + gdk_region_offset(windowRgn, -pluginX, -pluginY); + gdk_region_intersect(region, windowRgn); + gdk_region_destroy(windowRgn); + + // Stop now if we've reached the toplevel. Stopping here means + // clipping performed by the toplevel window is taken into account. + if (window == toplevel) break; + + Window parent; + Window* children; + unsigned int nchildren; + if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) { + gdk_region_destroy(region); + return 0; + } + XFree(children); + + pluginX += x; + pluginY += y; + + window = parent; + } +#endif + // pluginX and pluginY are now relative to the toplevel. Make them + // relative to the window frame top-left. + GdkWindow* toplevelGdk = gdk_window_foreign_new(window); + if (!toplevelGdk) return 0; + GdkRectangle toplevelFrameExtents; + gdk_window_get_frame_extents(toplevelGdk, &toplevelFrameExtents); + gint toplevelOriginX, toplevelOriginY; + gdk_window_get_origin(toplevelGdk, &toplevelOriginX, &toplevelOriginY); + g_object_unref(toplevelGdk); + + pluginX += toplevelOriginX - toplevelFrameExtents.x; + pluginY += toplevelOriginY - toplevelFrameExtents.y; + + gdk_region_offset(region, pluginX, pluginY); + return region; +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + g_free(rects); + return nrects; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + GdkRegion* region = computeClipRegion(instanceData); + if (!region) return NPTEST_INT32_ERROR; + + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(region, &rects, &nrects); + gdk_region_destroy(region); + if (rectIndex >= nrects) { + g_free(rects); + return NPTEST_INT32_ERROR; + } + + GdkRectangle rect = rects[rectIndex]; + g_free(rects); + + switch (edge) { + case EDGE_LEFT: + return rect.x; + case EDGE_TOP: + return rect.y; + case EDGE_RIGHT: + return rect.x + rect.width; + case EDGE_BOTTOM: + return rect.y + rect.height; + } + return NPTEST_INT32_ERROR; +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) {} + +std::string pluginGetClipboardText(InstanceData* instanceData) { + GtkClipboard* cb = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + // XXX this is a BAD WAY to interact with GtkClipboard. We use this + // deprecated interface only to test nested event loop handling. + gchar* text = gtk_clipboard_wait_for_text(cb); + std::string retText = text ? text : ""; + + g_free(text); + + return retText; +} + +//----------------------------------------------------------------------------- +// NB: this test is quite gross in that it's not only +// nondeterministic, but dependent on the guts of the nested glib +// event loop handling code in PluginModule. We first sleep long +// enough to make sure that the "detection timer" will be pending when +// we enter the nested glib loop, then similarly for the "process browser +// events" timer. Then we "schedule" the crasher thread to run at about the +// same time we expect that the PluginModule "process browser events" task +// will run. If all goes well, the plugin process will crash and generate the +// XPCOM "plugin crashed" task, and the browser will run that task while still +// in the "process some events" loop. + +static void* CrasherThread(void* data) { + // Give the parent thread a chance to send the message. + usleep(200); + + // Exit (without running atexit hooks) rather than crashing with a signal + // so as to make timing more reliable. The process terminates immediately + // rather than waiting for a thread in the parent process to attach and + // generate a minidump. + _exit(1); + + // not reached + return (nullptr); +} + +bool pluginCrashInNestedLoop(InstanceData* instanceData) { + // wait at least long enough for nested loop detector task to be pending ... + sleep(1); + + // Run the nested loop detector by processing all events that are waiting. + bool found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (!found_event) { + g_warning("DetectNestedEventLoop did not fire"); + return true; // trigger a test failure + } + + // wait at least long enough for the "process browser events" task to be + // pending ... + sleep(1); + + // we'll be crashing soon, note that fact now to avoid messing with + // timing too much + mozilla::NoteIntentionalCrash("plugin"); + + // schedule the crasher thread ... + pthread_t crasherThread; + if (0 != pthread_create(&crasherThread, nullptr, CrasherThread, nullptr)) { + g_warning("Failed to create thread"); + return true; // trigger a test failure + } + + // .. and hope it crashes at about the same time as the "process browser + // events" task (that should run in this loop) is being processed in the + // parent. + found_event = false; + while (g_main_context_iteration(nullptr, FALSE)) { + found_event = true; + } + if (found_event) { + g_warning("Should have crashed in ProcessBrowserEvents"); + } else { + g_warning("ProcessBrowserEvents did not fire"); + } + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +bool pluginTriggerXError(InstanceData* instanceData) { + mozilla::NoteIntentionalCrash("plugin"); + int num_prop_return; + // Window parameter is None to generate a fatal error, and this function + // should not return. + XListProperties(GDK_DISPLAY(), X11None, &num_prop_return); + + // if we get here without crashing, then we'll trigger a test failure + return true; +} + +static int SleepThenDie(Display* display) { + mozilla::NoteIntentionalCrash("plugin"); + fprintf(stderr, "[testplugin:%d] SleepThenDie: sleeping\n", getpid()); + sleep(1); + + fprintf(stderr, "[testplugin:%d] SleepThenDie: dying\n", getpid()); + _exit(1); +} + +bool pluginDestroySharedGfxStuff(InstanceData* instanceData) { + // Closing the X socket results in the gdk error handler being + // invoked, which exit()s us. We want to give the parent process a + // little while to do whatever it wanted to do, so steal the IO + // handler from gdk and set up our own that delays seppuku. + XSetIOErrorHandler(SleepThenDie); + close(ConnectionNumber(GDK_DISPLAY())); + return true; +} diff --git a/dom/plugins/test/testplugin/nptest_macosx.mm b/dom/plugins/test/testplugin/nptest_macosx.mm new file mode 100644 index 0000000000..191b8f5c6f --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_macosx.mm @@ -0,0 +1,275 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" +#include "nsAlgorithm.h" +#include <CoreServices/CoreServices.h> +#include <algorithm> + +bool pluginSupportsWindowMode() { return false; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + NPBool supportsCoreGraphics = false; + if ((NPN_GetValue(npp, NPNVsupportsCoreGraphicsBool, &supportsCoreGraphics) == NPERR_NO_ERROR) && + supportsCoreGraphics) { + NPN_SetValue(npp, NPPVpluginDrawingModel, (void*)NPDrawingModelCoreGraphics); + } else { + printf("CoreGraphics drawing model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + NPBool supportsCocoaEvents = false; + if ((NPN_GetValue(npp, NPNVsupportsCocoaBool, &supportsCocoaEvents) == NPERR_NO_ERROR) && + supportsCocoaEvents) { + NPN_SetValue(npp, NPPVpluginEventModel, (void*)NPEventModelCocoa); + instanceData->eventModel = NPEventModelCocoa; + } else { + printf("Cocoa event model not supported, can't create a plugin instance.\n"); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return NPERR_NO_ERROR; +} + +void pluginInstanceShutdown(InstanceData* instanceData) {} + +static bool RectEquals(const NPRect& r1, const NPRect& r2) { + return r1.left == r2.left && r1.top == r2.top && r1.right == r2.right && r1.bottom == r2.bottom; +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + // Ugh. Due to a terrible Gecko bug, we have to ignore position changes + // when the clip rect doesn't change; the position can be wrong + // when set by a path other than nsPluginFrame::FixUpPluginWindow. + int32_t oldX = instanceData->window.x; + int32_t oldY = instanceData->window.y; + bool clipChanged = !RectEquals(instanceData->window.clipRect, newWindow->clipRect); + instanceData->window = *newWindow; + if (!clipChanged) { + instanceData->window.x = oldX; + instanceData->window.y = oldY; + } +} + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + // Should never be called since we don't support window mode +} + +static void GetColorsFromRGBA(uint32_t rgba, float* r, float* g, float* b, float* a) { + *b = (rgba & 0xFF) / 255.0; + *g = ((rgba & 0xFF00) >> 8) / 255.0; + *r = ((rgba & 0xFF0000) >> 16) / 255.0; + *a = ((rgba & 0xFF000000) >> 24) / 255.0; +} + +static void pluginDraw(InstanceData* instanceData, NPCocoaEvent* event) { + if (!instanceData) return; + + notifyDidPaint(instanceData); + + NPP npp = instanceData->npp; + if (!npp) return; + + const char* uaString = NPN_UserAgent(npp); + if (!uaString) return; + + NPWindow window = instanceData->window; + + CGContextRef cgContext = event->data.draw.context; + + float windowWidth = window.width; + float windowHeight = window.height; + + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + CFStringRef uaCFString = + CFStringCreateWithCString(kCFAllocatorDefault, uaString, kCFStringEncodingASCII); + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a gray background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayFillColor(cgContext, 0.5, 1.0); + CGContextDrawPath(cgContext, kCGPathFill); + + // draw a black frame around the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + CGContextSetGrayStrokeColor(cgContext, 0.0, 1.0); + CGContextSetLineWidth(cgContext, 6.0); + CGContextStrokePath(cgContext); + + // draw the UA string using Core Text + CGContextSetTextMatrix(cgContext, CGAffineTransformIdentity); + + // Initialize a rectangular path. + CGMutablePathRef path = CGPathCreateMutable(); + CGRect bounds = CGRectMake(10.0, 10.0, std::max(0.0, windowWidth - 20.0), + std::max(0.0, windowHeight - 20.0)); + CGPathAddRect(path, NULL, bounds); + + // Initialize an attributed string. + CFMutableAttributedStringRef attrString = + CFAttributedStringCreateMutable(kCFAllocatorDefault, 0); + CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), uaCFString); + + // Create a color and add it as an attribute to the string. + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGFloat components[] = {0.0, 0.0, 0.0, 1.0}; + CGColorRef red = CGColorCreate(rgbColorSpace, components); + CGColorSpaceRelease(rgbColorSpace); + CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50), + kCTForegroundColorAttributeName, red); + + // Create the framesetter with the attributed string. + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attrString); + CFRelease(attrString); + + // Create the frame and draw it into the graphics context + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CFRelease(framesetter); + if (frame) { + CTFrameDraw(frame, cgContext); + CFRelease(frame); + } + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + case DM_SOLID_COLOR: { + // save the cgcontext gstate + CGContextSaveGState(cgContext); + + // we get a flipped context + CGContextTranslateCTM(cgContext, 0.0, windowHeight); + CGContextScaleCTM(cgContext, 1.0, -1.0); + + // draw a solid background for the plugin + CGContextAddRect(cgContext, CGRectMake(0, 0, windowWidth, windowHeight)); + + float r, g, b, a; + GetColorsFromRGBA(instanceData->scriptableObject->drawColor, &r, &g, &b, &a); + CGContextSetRGBFillColor(cgContext, r, g, b, a); + CGContextDrawPath(cgContext, kCGPathFill); + + // restore the cgcontext gstate + CGContextRestoreGState(cgContext); + break; + } + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPCocoaEvent* cocoaEvent = (NPCocoaEvent*)event; + if (!cocoaEvent) return kNPEventNotHandled; + + switch (cocoaEvent->type) { + case NPCocoaEventDrawRect: + pluginDraw(instanceData, cocoaEvent); + break; + case NPCocoaEventMouseDown: + case NPCocoaEventMouseUp: + case NPCocoaEventMouseMoved: + case NPCocoaEventMouseDragged: + instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX; + instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY; + if (cocoaEvent->type == NPCocoaEventMouseUp) { + instanceData->mouseUpEventCount++; + } + break; + case NPCocoaEventWindowFocusChanged: + instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus + ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->topLevelWindowActivationEventCount = + instanceData->topLevelWindowActivationEventCount + 1; + break; + case NPCocoaEventFocusChanged: + instanceData->focusState = cocoaEvent->data.focus.hasFocus ? ACTIVATION_STATE_ACTIVATED + : ACTIVATION_STATE_DEACTIVATED; + instanceData->focusEventCount = instanceData->focusEventCount + 1; + break; + default: + return kNPEventNotHandled; + } + + return kNPEventHandled; +} + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->x; + case EDGE_TOP: + return w->y; + case EDGE_RIGHT: + return w->x + w->width; + case EDGE_BOTTOM: + return w->y + w->height; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { return 1; } + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, int32_t rectIndex, RectEdge edge) { + if (rectIndex != 0) return NPTEST_INT32_ERROR; + + // We have to add the Cocoa titlebar height here since the clip rect + // is being returned relative to that + static const int COCOA_TITLEBAR_HEIGHT = 22; + + NPWindow* w = &instanceData->window; + switch (edge) { + case EDGE_LEFT: + return w->clipRect.left; + case EDGE_TOP: + return w->clipRect.top + COCOA_TITLEBAR_HEIGHT; + case EDGE_RIGHT: + return w->clipRect.right; + case EDGE_BOTTOM: + return w->clipRect.bottom + COCOA_TITLEBAR_HEIGHT; + } + MOZ_CRASH("Unexpected RectEdge?!"); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, std::string& error) {} diff --git a/dom/plugins/test/testplugin/nptest_name.cpp b/dom/plugins/test/testplugin/nptest_name.cpp new file mode 100644 index 0000000000..3fc6f1fb3c --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_name.cpp @@ -0,0 +1,8 @@ +const char* sPluginName = "Test Plug-in"; +const char* sPluginDescription = + "Plug-in for testing purposes.\xE2\x84\xA2 " + "(\xe0\xa4\xb9\xe0\xa4\xbf\xe0\xa4\xa8\xe0\xa5\x8d\xe0\xa4\xa6\xe0\xa5\x80 " + "\xe4\xb8\xad\xe6\x96\x87 " + "\xd8\xa7\xd9\x84\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a\xd8\xa9)"; +const char* sMimeDescription = + "application/x-test:tst:Test \xE2\x84\xA2 mimetype"; diff --git a/dom/plugins/test/testplugin/nptest_platform.h b/dom/plugins/test/testplugin/nptest_platform.h new file mode 100644 index 0000000000..4b9584932d --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_platform.h @@ -0,0 +1,155 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_platform_h_ +#define nptest_platform_h_ + +#include "nptest.h" + +/** + * Returns true if the plugin supports windowed mode + */ +bool pluginSupportsWindowMode(); + +/** + * Returns true if the plugin supports windowless mode. At least one of + * "pluginSupportsWindowMode" and "pluginSupportsWindowlessMode" must + * return true. + */ +bool pluginSupportsWindowlessMode(); + +/** + * Initialize the plugin instance. Returning an error here will cause the + * plugin instantiation to fail. + */ +NPError pluginInstanceInit(InstanceData* instanceData); + +/** + * Shutdown the plugin instance. + */ +void pluginInstanceShutdown(InstanceData* instanceData); + +/** + * Set the instanceData's window to newWindow. + */ +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow); + +/** + * Initialize the window for a windowed plugin. oldWindow is the old + * native window value. This will never be called for windowless plugins. + */ +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow); + +/** + * Handle an event for a windowless plugin. (Windowed plugins are + * responsible for listening for their own events.) + */ +int16_t pluginHandleEvent(InstanceData* instanceData, void* event); + +enum RectEdge { EDGE_LEFT = 0, EDGE_TOP = 1, EDGE_RIGHT = 2, EDGE_BOTTOM = 3 }; + +enum { NPTEST_INT32_ERROR = 0x7FFFFFFF }; + +/** + * Return the coordinate of the given edge of the plugin's area, relative + * to the top-left corner of the toplevel window containing the plugin, + * including window decorations. Only works for window-mode plugins + * and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge); + +/** + * Return the number of rectangles in the plugin's clip region. Only + * works for window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData); + +/** + * Return the coordinate of the given edge of a rectangle in the plugin's + * clip region, relative to the top-left corner of the toplevel window + * containing the plugin, including window decorations. Only works for + * window-mode plugins and Mac plugins. + * Returns NPTEST_ERROR on error. + */ +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge); + +/** + * Check that the platform-specific plugin state is internally consistent. + * Just return if everything is OK, otherwise append error messages + * to 'error' separated by \n. + */ +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error); + +/** + * Get the current clipboard item as text. If the clipboard item + * isn't text, the returned value is undefined. + */ +std::string pluginGetClipboardText(InstanceData* instanceData); + +/** + * Crash while in a nested event loop. The goal is to catch the + * browser processing the XPCOM event generated from the plugin's + * crash while other plugin code is still on the stack. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=550026. + */ +bool pluginCrashInNestedLoop(InstanceData* instanceData); + +/** + * Generate an X11 protocol error to terminate the plugin process. + */ +bool pluginTriggerXError(InstanceData* instanceData); + +/** + * Destroy gfx things that might be shared with the parent process + * when we're run out-of-process. It's not expected that this + * function will be called when the test plugin is loaded in-process, + * and bad things will happen if it is called. + * + * This call leaves the plugin subprocess in an undefined state. It + * must not be used after this call or weird things will happen. + */ +bool pluginDestroySharedGfxStuff(InstanceData* instanceData); + +/** + * Checks to see if the native widget is marked as visible. Works + * in e10s and non-e10s. Useful in testing e10s related compositor + * plugin window functionality. Supported on Windows. + */ +bool pluginNativeWidgetIsVisible(InstanceData* instanceData); + +#endif // nptest_platform_h_ diff --git a/dom/plugins/test/testplugin/nptest_utils.cpp b/dom/plugins/test/testplugin/nptest_utils.cpp new file mode 100644 index 0000000000..39a3f4b7b7 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.cpp @@ -0,0 +1,100 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant) { + size_t length = NPVARIANT_TO_STRING(*variant).UTF8Length; + NPUTF8* result = (NPUTF8*)malloc(length + 1); + memcpy(result, NPVARIANT_TO_STRING(*variant).UTF8Characters, length); + result[length] = '\0'; + return result; +} + +NPIdentifier variantToIdentifier(NPVariant variant) { + if (NPVARIANT_IS_STRING(variant)) + return stringVariantToIdentifier(variant); + else if (NPVARIANT_IS_INT32(variant)) + return int32VariantToIdentifier(variant); + else if (NPVARIANT_IS_DOUBLE(variant)) + return doubleVariantToIdentifier(variant); + return 0; +} + +NPIdentifier stringVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_STRING(variant)); + NPUTF8* utf8String = createCStringFromNPVariant(&variant); + NPIdentifier identifier = NPN_GetStringIdentifier(utf8String); + free(utf8String); + return identifier; +} + +NPIdentifier int32VariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_INT32(variant)); + int32_t integer = NPVARIANT_TO_INT32(variant); + return NPN_GetIntIdentifier(integer); +} + +NPIdentifier doubleVariantToIdentifier(NPVariant variant) { + assert(NPVARIANT_IS_DOUBLE(variant)); + double value = NPVARIANT_TO_DOUBLE(variant); + // sadly there is no "getdoubleidentifier" + int32_t integer = static_cast<int32_t>(value); + return NPN_GetIntIdentifier(integer); +} + +/* + * Parse a color in hex format, #AARRGGBB or AARRGGBB. + */ +uint32_t parseHexColor(const char* color, int len) { + uint8_t bgra[4] = {0, 0, 0, 0xFF}; + int i = 0; + + // Ignore unsupported formats. + if (len != 9 && len != 8) return 0; + + // start from the right and work to the left + while (len >= 2) { // we have at least #AA or AA left. + char byte[3]; + // parse two hex digits + byte[0] = color[len - 2]; + byte[1] = color[len - 1]; + byte[2] = '\0'; + + bgra[i] = (uint8_t)(strtoul(byte, nullptr, 16) & 0xFF); + i++; + len -= 2; + } + return (bgra[3] << 24) | (bgra[2] << 16) | (bgra[1] << 8) | bgra[0]; +} diff --git a/dom/plugins/test/testplugin/nptest_utils.h b/dom/plugins/test/testplugin/nptest_utils.h new file mode 100644 index 0000000000..cb2ca5a803 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_utils.h @@ -0,0 +1,45 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nptest_utils_h_ +#define nptest_utils_h_ + +#include "nptest.h" + +NPUTF8* createCStringFromNPVariant(const NPVariant* variant); + +NPIdentifier variantToIdentifier(NPVariant variant); +NPIdentifier stringVariantToIdentifier(NPVariant variant); +NPIdentifier int32VariantToIdentifier(NPVariant variant); +NPIdentifier doubleVariantToIdentifier(NPVariant variant); + +uint32_t parseHexColor(const char* color, int len); + +#endif // nptest_utils_h_ diff --git a/dom/plugins/test/testplugin/nptest_windows.cpp b/dom/plugins/test/testplugin/nptest_windows.cpp new file mode 100644 index 0000000000..0490d23367 --- /dev/null +++ b/dom/plugins/test/testplugin/nptest_windows.cpp @@ -0,0 +1,797 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * + * Copyright (c) 2008, Mozilla Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the Mozilla Corporation nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * Contributor(s): + * Josh Aas <josh@mozilla.com> + * Jim Mathies <jmathies@mozilla.com> + * + * ***** END LICENSE BLOCK ***** */ + +#include "nptest_platform.h" + +#include <windows.h> +#include <windowsx.h> +#include <stdio.h> + +#include <d3d10_1.h> +#include <d2d1.h> + +void SetSubclass(HWND hWnd, InstanceData* instanceData); +void ClearSubclass(HWND hWnd); +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam); + +struct _PlatformData { + HWND childWindow; + IDXGIAdapter1* adapter; + ID3D10Device1* device; + ID3D10Texture2D* frontBuffer; + ID3D10Texture2D* backBuffer; + ID2D1Factory* d2d1Factory; +}; + +bool pluginSupportsWindowMode() { return true; } + +bool pluginSupportsWindowlessMode() { return true; } + +NPError pluginInstanceInit(InstanceData* instanceData) { + instanceData->platformData = + static_cast<PlatformData*>(NPN_MemAlloc(sizeof(PlatformData))); + if (!instanceData->platformData) return NPERR_OUT_OF_MEMORY_ERROR; + + instanceData->platformData->childWindow = nullptr; + instanceData->platformData->device = nullptr; + instanceData->platformData->frontBuffer = nullptr; + instanceData->platformData->backBuffer = nullptr; + instanceData->platformData->adapter = nullptr; + instanceData->platformData->d2d1Factory = nullptr; + return NPERR_NO_ERROR; +} + +static inline bool openSharedTex2D(ID3D10Device* device, HANDLE handle, + ID3D10Texture2D** out) { + HRESULT hr = device->OpenSharedResource(handle, __uuidof(ID3D10Texture2D), + (void**)out); + if (FAILED(hr) || !*out) { + return false; + } + return true; +} + +// This is overloaded in d2d1.h so we can't use decltype(). +typedef HRESULT(WINAPI* D2D1CreateFactoryFunc)( + D2D1_FACTORY_TYPE factoryType, REFIID iid, + CONST D2D1_FACTORY_OPTIONS* pFactoryOptions, void** factory); + +static IDXGIAdapter1* FindDXGIAdapter(NPP npp, IDXGIFactory1* factory) { + DXGI_ADAPTER_DESC preferred; + if (NPN_GetValue(npp, NPNVpreferredDXGIAdapter, &preferred) != + NPERR_NO_ERROR) { + return nullptr; + } + + UINT index = 0; + for (;;) { + IDXGIAdapter1* adapter = nullptr; + if (FAILED(factory->EnumAdapters1(index, &adapter)) || !adapter) { + return nullptr; + } + + DXGI_ADAPTER_DESC desc; + if (SUCCEEDED(adapter->GetDesc(&desc)) && + desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart && + desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart && + desc.VendorId == preferred.VendorId && + desc.DeviceId == preferred.DeviceId) { + return adapter; + } + + adapter->Release(); + index++; + } +} + +// Note: we leak modules since we need them anyway. +bool setupDxgiSurfaces(NPP npp, InstanceData* instanceData) { + HMODULE dxgi = LoadLibraryA("dxgi.dll"); + if (!dxgi) { + return false; + } + decltype(CreateDXGIFactory1)* createDXGIFactory1 = + (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgi, "CreateDXGIFactory1"); + if (!createDXGIFactory1) { + return false; + } + + IDXGIFactory1* factory1 = nullptr; + HRESULT hr = createDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory1); + if (FAILED(hr) || !factory1) { + return false; + } + + instanceData->platformData->adapter = FindDXGIAdapter(npp, factory1); + if (!instanceData->platformData->adapter) { + return false; + } + + HMODULE d3d10 = LoadLibraryA("d3d10_1.dll"); + if (!d3d10) { + return false; + } + + decltype(D3D10CreateDevice1)* createDevice = + (decltype(D3D10CreateDevice1)*)GetProcAddress(d3d10, + "D3D10CreateDevice1"); + if (!createDevice) { + return false; + } + + hr = createDevice( + instanceData->platformData->adapter, D3D10_DRIVER_TYPE_HARDWARE, nullptr, + D3D10_CREATE_DEVICE_BGRA_SUPPORT | + D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS, + D3D10_FEATURE_LEVEL_10_1, D3D10_1_SDK_VERSION, + &instanceData->platformData->device); + if (FAILED(hr) || !instanceData->platformData->device) { + return false; + } + + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->frontBuffer->sharedHandle, + &instanceData->platformData->frontBuffer)) { + return false; + } + if (!openSharedTex2D(instanceData->platformData->device, + instanceData->backBuffer->sharedHandle, + &instanceData->platformData->backBuffer)) { + return false; + } + + HMODULE d2d1 = LoadLibraryA("D2d1.dll"); + if (!d2d1) { + return false; + } + auto d2d1CreateFactory = + (D2D1CreateFactoryFunc)GetProcAddress(d2d1, "D2D1CreateFactory"); + if (!d2d1CreateFactory) { + return false; + } + + D2D1_FACTORY_OPTIONS options; + options.debugLevel = D2D1_DEBUG_LEVEL_NONE; + + hr = d2d1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, + __uuidof(ID2D1Factory), &options, + (void**)&instanceData->platformData->d2d1Factory); + if (FAILED(hr) || !instanceData->platformData->d2d1Factory) { + return false; + } + + return true; +} + +void drawDxgiBitmapColor(InstanceData* instanceData) { + NPP npp = instanceData->npp; + + HRESULT hr; + + IDXGISurface* surface = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGISurface), (void**)&surface); + if (FAILED(hr) || !surface) { + return; + } + + D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + ID2D1RenderTarget* target = nullptr; + hr = instanceData->platformData->d2d1Factory->CreateDxgiSurfaceRenderTarget( + surface, &props, &target); + if (FAILED(hr) || !target) { + surface->Release(); + return; + } + + IDXGIKeyedMutex* mutex = nullptr; + hr = instanceData->platformData->backBuffer->QueryInterface( + __uuidof(IDXGIKeyedMutex), (void**)&mutex); + if (mutex) { + mutex->AcquireSync(0, 0); + } + + target->BeginDraw(); + + unsigned char subpixels[4]; + memcpy(subpixels, &instanceData->scriptableObject->drawColor, + sizeof(subpixels)); + + auto rect = D2D1::RectF(0, 0, instanceData->backBuffer->size.width, + instanceData->backBuffer->size.height); + auto color = D2D1::ColorF(float(subpixels[3] * subpixels[2]) / 0xFF, + float(subpixels[3] * subpixels[1]) / 0xFF, + float(subpixels[3] * subpixels[0]) / 0xFF, + float(subpixels[3]) / 0xff); + + ID2D1SolidColorBrush* brush = nullptr; + hr = target->CreateSolidColorBrush(color, &brush); + if (SUCCEEDED(hr) && brush) { + target->FillRectangle(rect, brush); + brush->Release(); + brush = nullptr; + } + hr = target->EndDraw(); + + if (mutex) { + mutex->ReleaseSync(0); + mutex->Release(); + mutex = nullptr; + } + + target->Release(); + surface->Release(); + target = nullptr; + surface = nullptr; + + NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL); + std::swap(instanceData->backBuffer, instanceData->frontBuffer); + std::swap(instanceData->platformData->backBuffer, + instanceData->platformData->frontBuffer); +} + +void pluginInstanceShutdown(InstanceData* instanceData) { + PlatformData* pd = instanceData->platformData; + if (pd->frontBuffer) { + pd->frontBuffer->Release(); + } + if (pd->backBuffer) { + pd->backBuffer->Release(); + } + if (pd->d2d1Factory) { + pd->d2d1Factory->Release(); + } + if (pd->device) { + pd->device->Release(); + } + if (pd->adapter) { + pd->adapter->Release(); + } + NPN_MemFree(instanceData->platformData); + instanceData->platformData = 0; + ClearSubclass((HWND)instanceData->window.window); +} + +void pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow) { + instanceData->window = *newWindow; +} + +#define CHILD_WIDGET_SIZE 10 + +void pluginWidgetInit(InstanceData* instanceData, void* oldWindow) { + HWND hWnd = (HWND)instanceData->window.window; + if (oldWindow) { + // chrashtests/539897-1.html excercises this code + HWND hWndOld = (HWND)oldWindow; + ClearSubclass(hWndOld); + if (instanceData->platformData->childWindow) { + ::DestroyWindow(instanceData->platformData->childWindow); + } + } + + SetSubclass(hWnd, instanceData); + + instanceData->platformData->childWindow = ::CreateWindowW( + L"SCROLLBAR", L"Dummy child window", WS_CHILD, 0, 0, CHILD_WIDGET_SIZE, + CHILD_WIDGET_SIZE, hWnd, nullptr, nullptr, nullptr); +} + +static void drawToDC(InstanceData* instanceData, HDC dc, int x, int y, + int width, int height) { + switch (instanceData->scriptableObject->drawMode) { + case DM_DEFAULT: { + const RECT fill = {x, y, x + width, y + height}; + + int oldBkMode = ::SetBkMode(dc, TRANSPARENT); + HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0)); + if (brush) { + ::FillRect(dc, &fill, brush); + ::DeleteObject(brush); + } + if (width > 6 && height > 6) { + brush = ::CreateSolidBrush(RGB(192, 192, 192)); + if (brush) { + RECT inset = {x + 3, y + 3, x + width - 3, y + height - 3}; + ::FillRect(dc, &inset, brush); + ::DeleteObject(brush); + } + } + + const char* uaString = NPN_UserAgent(instanceData->npp); + if (uaString && width > 10 && height > 10) { + HFONT font = ::CreateFontA(20, 0, 0, 0, 400, FALSE, FALSE, FALSE, + DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, 5, // CLEARTYPE_QUALITY + DEFAULT_PITCH, "Arial"); + if (font) { + HFONT oldFont = (HFONT)::SelectObject(dc, font); + RECT inset = {x + 5, y + 5, x + width - 5, y + height - 5}; + ::DrawTextA(dc, uaString, -1, &inset, + DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK); + ::SelectObject(dc, oldFont); + ::DeleteObject(font); + } + } + ::SetBkMode(dc, oldBkMode); + } break; + + case DM_SOLID_COLOR: { + HDC offscreenDC = ::CreateCompatibleDC(dc); + if (!offscreenDC) return; + + const BITMAPV4HEADER bitmapheader = { + sizeof(BITMAPV4HEADER), + width, + height, + 1, // planes + 32, // bits + BI_BITFIELDS, + 0, // unused size + 0, + 0, // unused metrics + 0, + 0, // unused colors used/important + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000, // ARGB masks + }; + uint32_t* pixelData; + HBITMAP offscreenBitmap = ::CreateDIBSection( + dc, reinterpret_cast<const BITMAPINFO*>(&bitmapheader), 0, + reinterpret_cast<void**>(&pixelData), 0, 0); + if (!offscreenBitmap) return; + + uint32_t rgba = instanceData->scriptableObject->drawColor; + unsigned int alpha = ((rgba & 0xFF000000) >> 24); + BYTE r = ((rgba & 0xFF0000) >> 16); + BYTE g = ((rgba & 0xFF00) >> 8); + BYTE b = (rgba & 0xFF); + + // Windows expects premultiplied + r = BYTE(float(alpha * r) / 0xFF); + g = BYTE(float(alpha * g) / 0xFF); + b = BYTE(float(alpha * b) / 0xFF); + uint32_t premultiplied = (alpha << 24) + (r << 16) + (g << 8) + b; + + for (uint32_t* lastPixel = pixelData + width * height; + pixelData < lastPixel; ++pixelData) + *pixelData = premultiplied; + + ::SelectObject(offscreenDC, offscreenBitmap); + BLENDFUNCTION blendFunc; + blendFunc.BlendOp = AC_SRC_OVER; + blendFunc.BlendFlags = 0; + blendFunc.SourceConstantAlpha = 255; + blendFunc.AlphaFormat = AC_SRC_ALPHA; + ::AlphaBlend(dc, x, y, width, height, offscreenDC, 0, 0, width, height, + blendFunc); + + ::DeleteObject(offscreenDC); + ::DeleteObject(offscreenBitmap); + } break; + } +} + +void pluginDraw(InstanceData* instanceData) { + NPP npp = instanceData->npp; + if (!npp) return; + + HDC hdc = nullptr; + PAINTSTRUCT ps; + + notifyDidPaint(instanceData); + + if (instanceData->hasWidget) + hdc = ::BeginPaint((HWND)instanceData->window.window, &ps); + else + hdc = (HDC)instanceData->window.window; + + if (hdc == nullptr) return; + + // Push the browser's hdc on the resource stack. If this test plugin is + // windowless, we share the drawing surface with the rest of the browser. + int savedDCID = SaveDC(hdc); + + // When we have a widget, window.x/y are meaningless since our widget + // is always positioned correctly and we just draw into it at 0,0. + int x = instanceData->hasWidget ? 0 : instanceData->window.x; + int y = instanceData->hasWidget ? 0 : instanceData->window.y; + int width = instanceData->window.width; + int height = instanceData->window.height; + drawToDC(instanceData, hdc, x, y, width, height); + + // Pop our hdc changes off the resource stack + RestoreDC(hdc, savedDCID); + + if (instanceData->hasWidget) + ::EndPaint((HWND)instanceData->window.window, &ps); +} + +/* script interface */ + +int32_t pluginGetEdge(InstanceData* instanceData, RectEdge edge) { + if (!instanceData || !instanceData->hasWidget) return NPTEST_INT32_ERROR; + + // Get the plugin client rect in screen coordinates + RECT rect = {0}; + if (!::GetClientRect((HWND)instanceData->window.window, &rect)) + return NPTEST_INT32_ERROR; + ::MapWindowPoints((HWND)instanceData->window.window, nullptr, (LPPOINT)&rect, + 2); + + // Get the toplevel window frame rect in screen coordinates + HWND rootWnd = ::GetAncestor((HWND)instanceData->window.window, GA_ROOT); + if (!rootWnd) return NPTEST_INT32_ERROR; + RECT rootRect; + if (!::GetWindowRect(rootWnd, &rootRect)) return NPTEST_INT32_ERROR; + + switch (edge) { + case EDGE_LEFT: + return rect.left - rootRect.left; + case EDGE_TOP: + return rect.top - rootRect.top; + case EDGE_RIGHT: + return rect.right - rootRect.left; + case EDGE_BOTTOM: + return rect.bottom - rootRect.top; + } + + return NPTEST_INT32_ERROR; +} + +static BOOL getWindowRegion(HWND wnd, HRGN rgn) { + if (::GetWindowRgn(wnd, rgn) != ERROR) return TRUE; + + RECT clientRect; + if (!::GetClientRect(wnd, &clientRect)) return FALSE; + return ::SetRectRgn(rgn, 0, 0, clientRect.right, clientRect.bottom); +} + +static RGNDATA* computeClipRegion(InstanceData* instanceData) { + HWND wnd = (HWND)instanceData->window.window; + HRGN rgn = ::CreateRectRgn(0, 0, 0, 0); + if (!rgn) return nullptr; + HRGN ancestorRgn = ::CreateRectRgn(0, 0, 0, 0); + if (!ancestorRgn) { + ::DeleteObject(rgn); + return nullptr; + } + if (!getWindowRegion(wnd, rgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return nullptr; + } + + HWND ancestor = wnd; + for (;;) { + ancestor = ::GetAncestor(ancestor, GA_PARENT); + if (!ancestor || ancestor == ::GetDesktopWindow()) { + ::DeleteObject(ancestorRgn); + + DWORD size = ::GetRegionData(rgn, 0, nullptr); + if (!size) { + ::DeleteObject(rgn); + return nullptr; + } + + HANDLE heap = ::GetProcessHeap(); + RGNDATA* data = static_cast<RGNDATA*>(::HeapAlloc(heap, 0, size)); + if (!data) { + ::DeleteObject(rgn); + return nullptr; + } + DWORD result = ::GetRegionData(rgn, size, data); + ::DeleteObject(rgn); + if (!result) { + ::HeapFree(heap, 0, data); + return nullptr; + } + + return data; + } + + if (!getWindowRegion(ancestor, ancestorRgn)) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + + POINT pt = {0, 0}; + ::MapWindowPoints(ancestor, wnd, &pt, 1); + if (::OffsetRgn(ancestorRgn, pt.x, pt.y) == ERROR || + ::CombineRgn(rgn, rgn, ancestorRgn, RGN_AND) == ERROR) { + ::DeleteObject(ancestorRgn); + ::DeleteObject(rgn); + return 0; + } + } +} + +int32_t pluginGetClipRegionRectCount(InstanceData* instanceData) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + int32_t result = data->rdh.nCount; + ::HeapFree(::GetProcessHeap(), 0, data); + return result; +} + +static int32_t addOffset(LONG coord, int32_t offset) { + if (offset == NPTEST_INT32_ERROR) return NPTEST_INT32_ERROR; + return coord + offset; +} + +int32_t pluginGetClipRegionRectEdge(InstanceData* instanceData, + int32_t rectIndex, RectEdge edge) { + RGNDATA* data = computeClipRegion(instanceData); + if (!data) return NPTEST_INT32_ERROR; + + HANDLE heap = ::GetProcessHeap(); + if (rectIndex >= int32_t(data->rdh.nCount)) { + ::HeapFree(heap, 0, data); + return NPTEST_INT32_ERROR; + } + + RECT rect = reinterpret_cast<RECT*>(data->Buffer)[rectIndex]; + ::HeapFree(heap, 0, data); + + switch (edge) { + case EDGE_LEFT: + return addOffset(rect.left, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_TOP: + return addOffset(rect.top, pluginGetEdge(instanceData, EDGE_TOP)); + case EDGE_RIGHT: + return addOffset(rect.right, pluginGetEdge(instanceData, EDGE_LEFT)); + case EDGE_BOTTOM: + return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP)); + } + + return NPTEST_INT32_ERROR; +} + +static void createDummyWindowForIME(InstanceData* instanceData) { + WNDCLASSW wndClass; + wndClass.style = 0; + wndClass.lpfnWndProc = DefWindowProcW; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = GetModuleHandleW(NULL); + wndClass.hIcon = nullptr; + wndClass.hCursor = nullptr; + wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = L"SWFlash_PlaceholderX"; + RegisterClassW(&wndClass); + + instanceData->placeholderWnd = static_cast<void*>( + CreateWindowW(L"SWFlash_PlaceholderX", L"", WS_CHILD, 0, 0, 0, 0, + HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL)); +} + +/* windowless plugin events */ + +static bool handleEventInternal(InstanceData* instanceData, NPEvent* pe, + LRESULT* result) { + switch ((UINT)pe->event) { + case WM_PAINT: + pluginDraw(instanceData); + return true; + + case WM_MOUSEACTIVATE: + if (instanceData->hasWidget) { + ::SetFocus((HWND)instanceData->window.window); + *result = MA_ACTIVATEANDEAT; + return true; + } + return false; + + case WM_MOUSEWHEEL: + return true; + + case WM_WINDOWPOSCHANGED: { + WINDOWPOS* pPos = (WINDOWPOS*)pe->lParam; + instanceData->winX = instanceData->winY = 0; + if (pPos) { + instanceData->winX = pPos->x; + instanceData->winY = pPos->y; + return true; + } + return false; + } + + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: { + int x = instanceData->hasWidget ? 0 : instanceData->winX; + int y = instanceData->hasWidget ? 0 : instanceData->winY; + instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x; + instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y; + if ((UINT)pe->event == WM_LBUTTONUP) { + instanceData->mouseUpEventCount++; + } + return true; + } + + case WM_KEYDOWN: + instanceData->lastKeyText.erase(); + *result = 0; + return true; + + case WM_CHAR: { + *result = 0; + wchar_t uniChar = static_cast<wchar_t>(pe->wParam); + if (!uniChar) { + return true; + } + char utf8Char[6]; + int len = ::WideCharToMultiByte(CP_UTF8, 0, &uniChar, 1, utf8Char, 6, + nullptr, nullptr); + if (len == 0 || len > 6) { + return true; + } + instanceData->lastKeyText.append(utf8Char, len); + return true; + } + + case WM_IME_STARTCOMPOSITION: + instanceData->lastComposition.erase(); + if (!instanceData->placeholderWnd) { + createDummyWindowForIME(instanceData); + } + return true; + + case WM_IME_ENDCOMPOSITION: + instanceData->lastComposition.erase(); + return true; + + case WM_IME_COMPOSITION: { + if (pe->lParam & GCS_COMPSTR) { + HIMC hIMC = ImmGetContext((HWND)instanceData->placeholderWnd); + if (!hIMC) { + return false; + } + WCHAR compStr[256]; + LONG len = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, compStr, + 256 * sizeof(WCHAR)); + CHAR buffer[256]; + len = ::WideCharToMultiByte(CP_UTF8, 0, compStr, len / sizeof(WCHAR), + buffer, 256, nullptr, nullptr); + instanceData->lastComposition.append(buffer, len); + ::ImmReleaseContext((HWND)instanceData->placeholderWnd, hIMC); + } + return true; + } + + default: + return false; + } +} + +int16_t pluginHandleEvent(InstanceData* instanceData, void* event) { + NPEvent* pe = (NPEvent*)event; + + if (pe == nullptr || instanceData == nullptr || + instanceData->window.type != NPWindowTypeDrawable) + return 0; + + LRESULT result = 0; + return handleEventInternal(instanceData, pe, &result); +} + +/* windowed plugin events */ + +LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc"); + if (!wndProc) return 0; + InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData"); + if (!pInstance) return 0; + + NPEvent event = {static_cast<uint16_t>(uMsg), wParam, lParam}; + + LRESULT result = 0; + if (handleEventInternal(pInstance, &event, &result)) return result; + + if (uMsg == WM_CLOSE) { + ClearSubclass((HWND)pInstance->window.window); + } + + return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam); +} + +void ClearSubclass(HWND hWnd) { + if (GetProp(hWnd, "MozillaWndProc")) { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, + (LONG_PTR)GetProp(hWnd, "MozillaWndProc")); + RemoveProp(hWnd, "MozillaWndProc"); + RemoveProp(hWnd, "InstanceData"); + } +} + +void SetSubclass(HWND hWnd, InstanceData* instanceData) { + // Subclass the plugin window so we can handle our own windows events. + SetProp(hWnd, "InstanceData", (HANDLE)instanceData); + WNDPROC origProc = + (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc); + SetProp(hWnd, "MozillaWndProc", (HANDLE)origProc); +} + +static void checkEquals(int a, int b, const char* msg, std::string& error) { + if (a == b) { + return; + } + + error.append(msg); + char buf[100]; + sprintf(buf, " (got %d, expected %d)\n", a, b); + error.append(buf); +} + +void pluginDoInternalConsistencyCheck(InstanceData* instanceData, + std::string& error) { + if (instanceData->platformData->childWindow) { + RECT childRect; + ::GetWindowRect(instanceData->platformData->childWindow, &childRect); + RECT ourRect; + HWND hWnd = (HWND)instanceData->window.window; + ::GetWindowRect(hWnd, &ourRect); + checkEquals(childRect.left, ourRect.left, "Child widget left", error); + checkEquals(childRect.top, ourRect.top, "Child widget top", error); + checkEquals(childRect.right, childRect.left + CHILD_WIDGET_SIZE, + "Child widget width", error); + checkEquals(childRect.bottom, childRect.top + CHILD_WIDGET_SIZE, + "Child widget height", error); + } +} + +bool pluginNativeWidgetIsVisible(InstanceData* instanceData) { + HWND hWnd = (HWND)instanceData->window.window; + wchar_t className[60]; + if (::GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t)) && + !wcsicmp(className, L"GeckoPluginWindow")) { + return ::IsWindowVisible(hWnd); + } + // something isn't right, fail the check + return false; +} diff --git a/dom/plugins/test/testplugin/secondplugin/Info.plist b/dom/plugins/test/testplugin/secondplugin/Info.plist new file mode 100644 index 0000000000..afa83a63ce --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/Info.plist @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>libnpsecondtest.dylib</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.SecondTestPlugin</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BRPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0.0.0</string> + <key>CFBundleSignature</key> + <string>SECONDTEST</string> + <key>CFBundleVersion</key> + <string>1.0.0.0</string> + <key>WebPluginName</key> + <string>Second Test Plug-in</string> + <key>WebPluginDescription</key> + <string>Second plug-in for testing purposes.</string> + <key>WebPluginMIMETypes</key> + <dict> + <key>application/x-Second-Test</key> + <dict> + <key>WebPluginExtensions</key> + <array> + <string>ts2</string> + </array> + <key>WebPluginTypeDescription</key> + <string>Second test type</string> + </dict> + </dict> +</dict> +</plist> diff --git a/dom/plugins/test/testplugin/secondplugin/moz.build b/dom/plugins/test/testplugin/secondplugin/moz.build new file mode 100644 index 0000000000..29c12260b4 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SharedLibrary("npsecondtest") + +relative_path = "secondplugin" +cocoa_name = "SecondTest" +include("../testplugin.mozbuild") diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.def b/dom/plugins/test/testplugin/secondplugin/nptest.def new file mode 100644 index 0000000000..c6584387d2 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.def @@ -0,0 +1,7 @@ +LIBRARY NPSECONDTEST + +EXPORTS + NP_GetEntryPoints @1 + NP_Initialize @2 + NP_Shutdown @3 + NP_GetMIMEDescription @4 diff --git a/dom/plugins/test/testplugin/secondplugin/nptest.rc b/dom/plugins/test/testplugin/secondplugin/nptest.rc new file mode 100644 index 0000000000..835906d0cb --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest.rc @@ -0,0 +1,42 @@ +#include<winver.h> + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "mozilla.org" + VALUE "FileDescription", L"Second plug-in for testing purposes." + VALUE "FileExtents", "ts2" + VALUE "FileOpenName", "Second test type" + VALUE "FileVersion", "1.0" + VALUE "InternalName", "npsecondtest" + VALUE "MIMEType", "application/x-Second-Test" + VALUE "OriginalFilename", "npsecondtest.dll" + VALUE "ProductName", "Second Test Plug-in" + VALUE "ProductVersion", "1.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp new file mode 100644 index 0000000000..23b821ae61 --- /dev/null +++ b/dom/plugins/test/testplugin/secondplugin/nptest_name.cpp @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const char* sPluginName = "Second Test Plug-in"; +const char* sPluginDescription = "Second plug-in for testing purposes."; +const char* sMimeDescription = "application/x-Second-Test:ts2:Second test type"; diff --git a/dom/plugins/test/testplugin/testplugin.mozbuild b/dom/plugins/test/testplugin/testplugin.mozbuild new file mode 100644 index 0000000000..2c466409ea --- /dev/null +++ b/dom/plugins/test/testplugin/testplugin.mozbuild @@ -0,0 +1,64 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nptest.cpp', + 'nptest_utils.cpp', +] + +UNIFIED_SOURCES += [ + '%s/nptest_name.cpp' % relative_path, +] + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] +if toolkit == 'cocoa': + UNIFIED_SOURCES += [ + 'nptest_macosx.mm' + ] +elif toolkit == 'gtk': + UNIFIED_SOURCES += [ + 'nptest_gtk2.cpp', + ] +elif toolkit == 'windows': + UNIFIED_SOURCES += [ + 'nptest_windows.cpp', + ] + OS_LIBS += [ + 'msimg32', + 'imm32' + ] + +# must link statically with the CRT; nptest isn't Gecko code +USE_STATIC_LIBS = True + +# Don't use STL wrappers; nptest isn't Gecko code +DisableStlWrapping() + +NO_PGO = True + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + RCFILE = 'nptest.rc' + DEFFILE = 'nptest.def' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa' and CONFIG['TARGET_CPU'] == 'x86_64': + OS_LIBS += ['-framework Carbon'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk': + CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + CFLAGS += CONFIG['MOZ_GTK2_CFLAGS'] + OS_LIBS += CONFIG['MOZ_GTK2_LIBS'] + OS_LIBS += CONFIG['XLDFLAGS'] + OS_LIBS += CONFIG['XLIBS'] + OS_LIBS += CONFIG['XEXT_LIBS'] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + FINAL_TARGET = 'dist/plugins/%s.plugin/Contents/MacOS' % cocoa_name + OBJDIR_FILES.dist.plugins['%s.plugin' % cocoa_name].Contents += ['%s/Info.plist' % relative_path] +else: + FINAL_TARGET = 'dist/plugins' + +if CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += ['-Wno-error=shadow'] |