--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -65,16 +65,17 @@
#if defined(MOZ_X11) && defined(MOZ_WIDGET_GTK)
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#endif
#include "Layers.h"
#include "gfxPrefs.h"
+#include "gfxUtils.h"
#include "mozilla/dom/AudioDeviceInfo.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/IDBFactoryBinding.h"
#include "mozilla/dom/IDBMutableFileBinding.h"
#include "mozilla/dom/IDBMutableFile.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
@@ -105,16 +106,17 @@
#include "nsNetUtil.h"
#include "nsDocument.h"
#include "HTMLImageElement.h"
#include "HTMLCanvasElement.h"
#include "mozilla/css/ImageLoader.h"
#include "mozilla/layers/APZCTreeManager.h" // for layers::ZoomToRectBehavior
#include "mozilla/dom/Promise.h"
#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/dom/TimeoutManager.h"
#include "mozilla/PreloadedStyleSheet.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#ifdef XP_WIN
#undef GetClassName
@@ -683,16 +685,56 @@ nsDOMWindowUtils::GetPresShellId(uint32_
if (presShell) {
*aPresShellId = presShell->GetPresShellId();
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
+nsDOMWindowUtils::GetCursorForTests(JSContext* aCx, JS::MutableHandleValue aResult)
+{
+ nsCOMPtr<nsIWidget> widget = GetWidget();
+ NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
+
+ RefPtr<mozilla::gfx::SourceSurface> cursorSurface;
+ uint32_t hotspotX = 0, hotspotY = 0;
+ nsresult rv = widget->GetCursorForTests(cursorSurface, hotspotX, hotspotY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(cursorSurface, NS_ERROR_FAILURE);
+
+ RefPtr<DataSourceSurface> dataSurface = cursorSurface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ UniquePtr<uint8_t[]> pixels = SurfaceToPackedBGRA(dataSurface);
+ NS_ENSURE_TRUE(pixels, NS_ERROR_FAILURE);
+
+ IntSize size = cursorSurface->GetSize();
+ int length = size.width * size.height * 4;
+ gfxUtils::ConvertBGRAtoRGBA(pixels.get(), length);
+
+ JS::RootedObject obj(aCx, JS_NewObject(aCx, nullptr));
+ JS::RootedValue hotspotValueX(aCx, JS::Int32Value(hotspotX));
+ JS::RootedValue hotspotValueY(aCx, JS::Int32Value(hotspotY));
+ JS::RootedValue WidthValue(aCx, JS::Int32Value(size.width));
+ JS::RootedValue heightValue(aCx, JS::Int32Value(size.height));
+ JS::RootedString rgbaString(aCx, JS_NewStringCopyN(aCx,
+ reinterpret_cast<const char*>(pixels.get()), length));
+ JS::RootedValue rgbaValue(aCx, JS::StringValue(rgbaString));
+
+ JS_SetProperty(aCx, obj, "hotspotX", hotspotValueX);
+ JS_SetProperty(aCx, obj, "hotspotY", hotspotValueY);
+ JS_SetProperty(aCx, obj, "width", WidthValue);
+ JS_SetProperty(aCx, obj, "height", heightValue);
+ JS_SetProperty(aCx, obj, "rgba", rgbaValue);
+ aResult.setObject(*obj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
float aX,
float aY,
int32_t aButton,
int32_t aClickCount,
int32_t aModifiers,
bool aIgnoreRootScrollFrame,
float aPressure,
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -255,16 +255,32 @@ interface nsIDOMWindowUtils : nsISupport
*
* Can only be accessed with chrome privileges.
*/
attribute boolean isFirstPaint;
uint32_t getPresShellId();
/**
+ * This is used for testing custom CSS cursor images. The returned value is a
+ * JavaScript object that looks like this:
+ *
+ * {
+ * width: the pixel width of the image,
+ * height: the pixel height of the image,
+ * hotspotX: the x coordinate of the hotspot,
+ * hotspotY: the x coordinate of the hotspot,
+ * rgba: a string of length "width * height * 4" containing RGBA data
+ * }
+ *
+ * Can only be accessed with chrome privileges.
+ */
+ [implicit_jscontext] jsval getCursorForTests();
+
+ /**
* Following modifiers are for sent*Event() except sendNative*Event().
* NOTE: MODIFIER_ALT, MODIFIER_CONTROL, MODIFIER_SHIFT and MODIFIER_META
* are must be same values as nsIDOMNSEvent::*_MASK for backward
* compatibility.
*/
const long MODIFIER_ALT = 0x0001;
const long MODIFIER_CONTROL = 0x0002;
const long MODIFIER_SHIFT = 0x0004;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -370,34 +370,45 @@ parent:
*/
async SetCursor(uint32_t value, bool force);
/**
* Set the native cursor using a custom image.
* @param cursorData
* Serialized image data.
* @param width
- * Width of the image.
+ * Width of the image. This may be scaled by the display scale factor.
+ * Vector images need to be pre-scaled before they pass across the IPC
+ * boundary.
* @param height
- * Height of the image.
+ * Height of the image. This may be scaled by the display scale factor
+ * for the same reason as the width parameter.
* @param stride
* Stride used in the image data.
* @param format
* Image format, see gfx::SurfaceFormat for possible values.
* @param hotspotX
* Horizontal hotspot of the image, as specified by the css cursor property.
* @param hotspotY
* Vertical hotspot of the image, as specified by the css cursor property.
* @param force
* Invalidate any locally cached cursor settings and force an
* update.
+ * @param unscaledWidth
+ * The original width of the image (i.e. not scaled by the display scale
+ * factor). This may be different than the width parameter for vector
+ * images.
+ * @param unscaledHeight
+ * The original height of the image. This may be different than the height
+ * parameter just like the unscaledWidth parameter.
*/
async SetCustomCursor(nsCString cursorData, uint32_t width, uint32_t height,
- uint32_t stride, uint8_t format,
- uint32_t hotspotX, uint32_t hotspotY, bool force);
+ uint32_t stride, uint8_t format, uint32_t hotspotX,
+ uint32_t hotspotY, bool force, uint32_t unscaledWidth,
+ uint32_t unscaledHeight);
/**
* Used to set the current text of the status tooltip.
* Nowadays this is mainly used for link locations on hover.
*/
async SetStatus(uint32_t type, nsString status);
/**
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1780,17 +1780,19 @@ TabParent::RecvSetCursor(const uint32_t&
mozilla::ipc::IPCResult
TabParent::RecvSetCustomCursor(const nsCString& aCursorData,
const uint32_t& aWidth,
const uint32_t& aHeight,
const uint32_t& aStride,
const uint8_t& aFormat,
const uint32_t& aHotspotX,
const uint32_t& aHotspotY,
- const bool& aForce)
+ const bool& aForce,
+ const uint32_t &aUnscaledWidth,
+ const uint32_t &aUnscaledHeight)
{
mCursor = eCursorInvalid;
nsCOMPtr<nsIWidget> widget = GetWidget();
if (widget) {
if (aForce) {
widget->ClearCachedCursor();
}
@@ -1799,17 +1801,23 @@ TabParent::RecvSetCustomCursor(const nsC
const gfx::IntSize size(aWidth, aHeight);
RefPtr<gfx::DataSourceSurface> customCursor =
gfx::CreateDataSourceSurfaceFromData(size,
static_cast<gfx::SurfaceFormat>(aFormat),
reinterpret_cast<const uint8_t*>(aCursorData.BeginReading()),
aStride);
- RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(customCursor, size);
+ Matrix matrix = Matrix::Scaling(
+ (float)aUnscaledWidth / (float)aWidth,
+ (float)aUnscaledHeight / (float)aHeight);
+
+ RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(
+ new gfxPattern(customCursor, matrix), IntSize(aUnscaledWidth, aUnscaledHeight));
+
nsCOMPtr<imgIContainer> cursorImage(image::ImageOps::CreateFromDrawable(drawable));
widget->SetCursor(cursorImage, aHotspotX, aHotspotY);
mCustomCursor = cursorImage;
mCustomCursorHotspotX = aHotspotX;
mCustomCursorHotspotY = aHotspotY;
}
}
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -280,17 +280,19 @@ public:
virtual mozilla::ipc::IPCResult RecvSetCustomCursor(const nsCString& aUri,
const uint32_t& aWidth,
const uint32_t& aHeight,
const uint32_t& aStride,
const uint8_t& aFormat,
const uint32_t& aHotspotX,
const uint32_t& aHotspotY,
- const bool& aForce) override;
+ const bool& aForce,
+ const uint32_t &aUnscaledWidth,
+ const uint32_t &aUnscaledHeight) override;
virtual mozilla::ipc::IPCResult RecvSetStatus(const uint32_t& aType,
const nsString& aStatus) override;
virtual mozilla::ipc::IPCResult RecvIsParentWindowMainWidgetVisible(bool* aIsVisible) override;
virtual mozilla::ipc::IPCResult RecvShowTooltip(const uint32_t& aX,
const uint32_t& aY,
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -1008,18 +1008,30 @@ PuppetWidget::SetCursor(imgIContainer* a
if (mCustomCursor == aCursor &&
mCursorHotspotX == aHotspotX &&
mCursorHotspotY == aHotspotY &&
!mUpdateCursor) {
return NS_OK;
}
#endif
+ int32_t width = 0;
+ int32_t height = 0;
+ nsresult rv;
+ rv = aCursor->GetWidth(&width);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aCursor->GetHeight(&height);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ IntSize scaledSize(
+ NSToIntRound(width * mDefaultScale),
+ NSToIntRound(height * mDefaultScale));
+
RefPtr<mozilla::gfx::SourceSurface> surface =
- aCursor->GetFrame(imgIContainer::FRAME_CURRENT,
+ aCursor->GetFrameAtSize(scaledSize, imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
if (!surface) {
return NS_ERROR_FAILURE;
}
RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
surface->GetDataSurface();
if (!dataSurface) {
@@ -1030,17 +1042,17 @@ PuppetWidget::SetCursor(imgIContainer* a
int32_t stride;
mozilla::UniquePtr<char[]> surfaceData =
nsContentUtils::GetSurfaceData(WrapNotNull(dataSurface), &length, &stride);
nsDependentCString cursorData(surfaceData.get(), length);
mozilla::gfx::IntSize size = dataSurface->GetSize();
if (!mTabChild->SendSetCustomCursor(cursorData, size.width, size.height, stride,
static_cast<uint8_t>(dataSurface->GetFormat()),
- aHotspotX, aHotspotY, mUpdateCursor)) {
+ aHotspotX, aHotspotY, mUpdateCursor, width, height)) {
return NS_ERROR_FAILURE;
}
mCursor = eCursorInvalid;
mCustomCursor = aCursor;
mCursorHotspotX = aHotspotX;
mCursorHotspotY = aHotspotY;
mUpdateCursor = false;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -706,16 +706,22 @@ nsBaseWidget::SetCursor(nsCursor aCursor
nsresult
nsBaseWidget::SetCursor(imgIContainer* aCursor,
uint32_t aHotspotX, uint32_t aHotspotY)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
+nsresult
+nsBaseWidget::GetCursorForTests(RefPtr<mozilla::gfx::SourceSurface> &aCursor,
+ uint32_t &aHotspotX, uint32_t &aHotspotY) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
//-------------------------------------------------------------------------
//
// Window transparency methods
//
//-------------------------------------------------------------------------
void nsBaseWidget::SetTransparencyMode(nsTransparencyMode aMode) {
}
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -172,16 +172,18 @@ public:
virtual bool IsFullyOccluded() const override
{
return mIsFullyOccluded;
}
virtual void SetCursor(nsCursor aCursor) override;
virtual nsresult SetCursor(imgIContainer* aCursor,
uint32_t aHotspotX, uint32_t aHotspotY) override;
+ virtual nsresult GetCursorForTests(RefPtr<mozilla::gfx::SourceSurface> &aCursor,
+ uint32_t &aHotspotX, uint32_t &aHotspotY) override;
virtual void ClearCachedCursor() override { mUpdateCursor = true; }
virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
virtual nsTransparencyMode GetTransparencyMode() override;
virtual void GetWindowClipRegion(nsTArray<LayoutDeviceIntRect>* aRects) override;
virtual void SetWindowShadowStyle(int32_t aStyle) override {}
virtual void SetShowsToolbarButton(bool aShow) override {}
virtual void SetShowsFullScreenButton(bool aShow) override {}
virtual void SetWindowAnimationType(WindowAnimationType aType) override {}
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -982,16 +982,29 @@ class nsIWidget : public nsISupports
* @param aY the Y coordinate of the hotspot (from top).
* @retval NS_ERROR_NOT_IMPLEMENTED if setting images as cursors is not
* supported
*/
virtual nsresult SetCursor(imgIContainer* aCursor,
uint32_t aHotspotX, uint32_t aHotspotY) = 0;
/**
+ * Returns the current custom cursor image and hotspot. This is only useful
+ * for tests.
+ *
+ * @param aCursor the cursor to get
+ * @param aX the X coordinate of the hotspot (from left).
+ * @param aY the Y coordinate of the hotspot (from top).
+ * @retval NS_ERROR_NOT_IMPLEMENTED if retrieving the cursor is not
+ * supported
+ */
+ virtual nsresult GetCursorForTests(RefPtr<mozilla::gfx::SourceSurface> &aCursor,
+ uint32_t &aHotspotX, uint32_t &aHotspotY) = 0;
+
+ /**
* Get the window type of this widget.
*/
nsWindowType WindowType() { return mWindowType; }
/**
* Determines if this widget is one of the three types of plugin widgets.
*/
bool IsPlugin() {
--- a/widget/tests/chrome.ini
+++ b/widget/tests/chrome.ini
@@ -94,13 +94,16 @@ skip-if = toolkit != "cocoa"
[test_chrome_context_menus_win.xul]
skip-if = toolkit != "windows"
support-files = chrome_context_menus_win.xul
[test_plugin_input_event.html]
skip-if = toolkit != "windows"
[test_mouse_scroll.xul]
skip-if = toolkit != "windows"
support-files = window_mouse_scroll_win.html
+[test_custom_cursor_win.xul]
+skip-if = toolkit != "windows"
+support-files = window_custom_cursor_win.html
# Privacy relevant
[test_bug1123480.xul]
subsuite = clipboard
new file mode 100644
--- /dev/null
+++ b/widget/tests/test_custom_cursor_win.xul
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Testing composition, text and query content events"
+ onload="setTimeout(onLoad, 0);"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+function onLoad()
+{
+ SpecialPowers.pushPrefEnv({"set": [
+ ["security.data_uri.unique_opaque_origin", false]]}, runTest);
+}
+
+function runTest()
+{
+ window.open("window_custom_cursor_win.html", "_blank",
+ "chrome,width=600,height=600");
+}
+]]>
+</script>
+</window>
new file mode 100644
--- /dev/null
+++ b/widget/tests/window_custom_cursor_win.html
@@ -0,0 +1,221 @@
+<html lang="en-US">
+<head>
+ <title>Test for custom CSS cursors on Windows</title>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+ <style>
+
+body {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ margin: 0;
+}
+
+ </style>
+</head>
+<body onunload="onUnload();">
+<script class="testbody" type="application/javascript">
+
+window.parent.wrappedJSObject.SimpleTest.waitForFocus(prepareTests, window);
+
+const nsIDOMWindowUtils = Components.interfaces.nsIDOMWindowUtils;
+const kLayoutCssDevPixelsPerPxPref = 'layout.css.devPixelsPerPx';
+
+function ok(aCondition, aMessage)
+{
+ window.parent.wrappedJSObject.SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+ window.parent.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function onUnload()
+{
+ SpecialPowers.setCharPref(kLayoutCssDevPixelsPerPxPref, '-1.0');
+ window.parent.wrappedJSObject.SimpleTest.finish();
+}
+
+function createDemoImage()
+{
+ const width = 27;
+ const height = 29;
+ const bytes = new Uint8Array(width * height * 4);
+
+ for (let y = 0, i = 0; y < height; y++) {
+ for (let x = 0; x < width; x++, i += 4) {
+ if ((x ^ y) & 1) continue;
+ if ((x ^ y) & 2) bytes[i] = 0xFF;
+ if ((x ^ y) & 4) bytes[i + 1] = 0xFF;
+ if ((x ^ y) & 8) bytes[i + 2] = 0xFF;
+ bytes[i + 3] = 0xFF;
+ }
+ }
+
+ return {width, height, bytes};
+}
+
+function convertImageToSVG({width, height, bytes})
+{
+ let svg = `<svg xmlns="http://www.w3.org/2000/svg"
+ width="${width}px" height="${height}px">`;
+ for (let y = 0, i = 0; y < height; y++) {
+ for (let x = 0; x < width; x++, i += 4) {
+ const r = bytes[i], g = bytes[i + 1], b = bytes[i + 2], a = bytes[i + 3];
+ const fill = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
+ svg += `<rect x="${x}" y="${y}" width="1" height="1" fill="${fill}" />`;
+ }
+ }
+ svg += '</svg>';
+ return `data:image/svg+xml;base64,${btoa(svg)}`;
+}
+
+function convertImageToPNG({width, height, bytes})
+{
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ const imageData = context.createImageData(width, height);
+ imageData.data.set(bytes);
+ canvas.width = width;
+ canvas.height = height;
+ context.putImageData(imageData, 0, 0);
+ return canvas.toDataURL();
+}
+
+async function bug1304098()
+{
+ const sleep = time => new Promise(resolve => setTimeout(resolve, time));
+ const utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(nsIDOMWindowUtils);
+
+ // Generate a custom cursor image
+ const {width, height, bytes} = createDemoImage();
+ const hotspotX = 26;
+ const hotspotY = 13;
+
+ // Check PNG cursors
+ const pngURL = convertImageToPNG({width, height, bytes});
+ await checkCursor('PNG cursor @1x', pngURL, '1', checkCursor1x);
+ await checkCursor('PNG cursor @2x', pngURL, '2', checkCursor2xScaled);
+
+ // Check SVG cursors
+ const svgURL = convertImageToSVG({width, height, bytes});
+ await checkCursor('SVG cursor @1x', svgURL, '1', checkCursor1x);
+ await checkCursor('SVG cursor @2x', svgURL, '2', checkCursor2xSharp);
+
+ async function jiggleMouse()
+ {
+ for (let i = 0; i < 3; i++) {
+ const scale = utils.screenPixelsPerCSSPixel;
+ const x = (window.mozInnerScreenX + 50 + i) * scale;
+ const y = (window.mozInnerScreenY + 50 + i) * scale;
+ await utils.sendNativeMouseMove(x, y, null);
+ await sleep(100);
+ }
+ }
+
+ async function checkCursor(name, cursorURL, displayScale, checkCallback)
+ {
+ // Pretend we're on a screen with a specific display scale
+ SpecialPowers.setCharPref(kLayoutCssDevPixelsPerPxPref, displayScale);
+
+ // Change to the default cursor to clear the cached cursor
+ document.body.style.cursor = 'default';
+ await jiggleMouse();
+
+ // Change to our custom cursor
+ document.body.style.cursor = `url(${cursorURL}) ${hotspotX} ${hotspotY}, auto`;
+ await jiggleMouse();
+
+ // Check that the cursor is correct
+ const cursor = await utils.getCursorForTests();
+ checkCallback(name, cursor);
+ }
+
+ function checkCursor1x(name, cursor)
+ {
+ is(cursor.width, width, `Checking width for ${name}`);
+ is(cursor.height, height, `Checking height for ${name}`);
+ is(cursor.hotspotX, hotspotX, `Checking hotspotX for ${name}`);
+ is(cursor.hotspotY, hotspotY, `Checking hotspotY for ${name}`);
+
+ // Check that the cursor has the correct contents
+ const get = i => cursor.rgba.charCodeAt(i);
+ for (let y = 0, i = 0; y < height; y++) {
+ for (let x = 0; x < width; x++, i += 4) {
+ const expected = `${bytes[i]}, ${bytes[i + 1]}, ${bytes[i + 2]}, ${bytes[i + 3]}`;
+ const observed = `${get(i)}, ${get(i + 1)}, ${get(i + 2)}, ${get(i + 3)}`;
+
+ // Don't pollute the log with lots of checks
+ if (expected !== observed) {
+ is(observed, expected, `Checking pixel at (${x}, ${y}) for ${name}`);
+ return;
+ }
+ }
+ }
+ }
+
+ function checkCursor2xSharp(name, cursor)
+ {
+ is(cursor.width, width * 2, `Checking width for ${name}`);
+ is(cursor.height, height * 2, `Checking height for ${name}`);
+ is(cursor.hotspotX, hotspotX, `Checking hotspotX for ${name}`);
+ is(cursor.hotspotY, hotspotY, `Checking hotspotY for ${name}`);
+
+ // Check that the cursor has the correct contents
+ const get = i => cursor.rgba.charCodeAt(i);
+ for (let y = 0, i = 0; y < 2 * height; y++) {
+ for (let x = 0; x < 2 * width; x++, i += 4) {
+ const j = ((x >> 1) + (y >> 1) * width) * 4;
+ const expected = `${bytes[j]}, ${bytes[j + 1]}, ${bytes[j + 2]}, ${bytes[j + 3]}`;
+ const observed = `${get(i)}, ${get(i + 1)}, ${get(i + 2)}, ${get(i + 3)}`;
+
+ // Don't pollute the log with lots of checks
+ if (expected !== observed) {
+ is(observed, expected, `Checking pixel at (${x}, ${y}) for ${name}`);
+ return;
+ }
+ }
+ }
+ }
+
+ function checkCursor2xScaled(name, cursor)
+ {
+ is(cursor.width, width * 2, `Checking width for ${name}`);
+ is(cursor.height, height * 2, `Checking height for ${name}`);
+ is(cursor.hotspotX, hotspotX, `Checking hotspotX for ${name}`);
+ is(cursor.hotspotY, hotspotY, `Checking hotspotY for ${name}`);
+
+ // Raster cursors will be scaled up. Don't check the pixel values here
+ // because the details of the scaling algorithm shouldn't be baked into
+ // this test. Just check the scale to make sure it's 2x bigger.
+ }
+}
+
+async function prepareTests()
+{
+ try {
+ await bug1304098();
+
+ ok(true, 'Success');
+ window.close();
+ }
+
+ catch (e) {
+ ok(false, `Uncaught exception: ${e}`);
+ window.close();
+ }
+}
+
+</script>
+</body>
+
+</html>
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -3029,16 +3029,85 @@ nsWindow::SetCursor(imgIContainer* aCurs
if (sHCursor != nullptr)
::DestroyIcon(sHCursor);
sHCursor = cursor;
return NS_OK;
}
+nsresult
+nsWindow::GetCursorForTests(RefPtr<mozilla::gfx::SourceSurface> &aCursor,
+ uint32_t &aHotspotX, uint32_t &aHotspotY) {
+ ICONINFO iconInfo = {};
+
+ aCursor = nullptr;
+ aHotspotX = 0;
+ aHotspotY = 0;
+
+ if (!sHCursor || !GetIconInfo(sHCursor, &iconInfo)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ HDC dc = ::GetDC(nullptr);
+ auto releaseDC = MakeScopeExit([&] {
+ ::ReleaseDC(nullptr, dc);
+ });
+
+ BITMAPINFO bmInfo = {};
+ bmInfo.bmiHeader.biSize = sizeof(bmInfo.bmiHeader);
+
+ if (!GetDIBits(dc, iconInfo.hbmColor, 0, 0, nullptr, &bmInfo, DIB_RGB_COLORS)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int width = bmInfo.bmiHeader.biWidth;
+ int height = abs(bmInfo.bmiHeader.biHeight);
+ int stride = width * 4;
+
+ RefPtr<DataSourceSurface> surface = Factory::CreateDataSourceSurface(
+ IntSize(width, height), SurfaceFormat::B8G8R8A8);
+
+ if (!surface || BytesPerPixel(surface->GetFormat()) != 4) {
+ return NS_ERROR_FAILURE;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ bool mappedOK = surface->Map(DataSourceSurface::MapType::READ_WRITE, &map);
+
+ if (!mappedOK || map.mStride < stride) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bmInfo.bmiHeader.biHeight = -height;
+ bmInfo.bmiHeader.biSizeImage = width * height * 4;
+ bmInfo.bmiHeader.biPlanes = 1;
+ bmInfo.bmiHeader.biBitCount = 32;
+ bmInfo.bmiHeader.biCompression = BI_RGB;
+
+ if (!GetDIBits(dc, iconInfo.hbmColor, 0, height, map.mData, &bmInfo, DIB_RGB_COLORS)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // GetDIBits returns data where all rows have been tightly packed together,
+ // but the mapped surface may have a larger stride. Expand the rows out by
+ // the stride of the surface so they're in the right place. This can be done
+ // in place if it's done bottom to top.
+ for (int y = height - 1; y > 0; y--) {
+ memmove(map.mData + y * map.mStride, map.mData + y * stride, stride);
+ }
+
+ surface->Unmap();
+
+ aCursor = surface;
+ aHotspotX = iconInfo.xHotspot;
+ aHotspotY = iconInfo.yHotspot;
+ return NS_OK;
+}
+
/**************************************************************
*
* SECTION: nsIWidget::Get/SetTransparencyMode
*
* Manage the transparency mode of the window containing this
* widget. Only works for popup and dialog windows when the
* Desktop Window Manager compositor is not enabled.
*
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -145,16 +145,18 @@ public:
virtual LayoutDeviceIntRect GetScreenBounds() override;
virtual MOZ_MUST_USE nsresult GetRestoredBounds(LayoutDeviceIntRect& aRect) override;
virtual LayoutDeviceIntRect GetClientBounds() override;
virtual LayoutDeviceIntPoint GetClientOffset() override;
void SetBackgroundColor(const nscolor& aColor) override;
virtual nsresult SetCursor(imgIContainer* aCursor,
uint32_t aHotspotX, uint32_t aHotspotY) override;
virtual void SetCursor(nsCursor aCursor) override;
+ virtual nsresult GetCursorForTests(RefPtr<mozilla::gfx::SourceSurface> &aCursor,
+ uint32_t &aHotspotX, uint32_t &aHotspotY) override;
virtual nsresult ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
uint16_t aDuration,
nsISupports* aData,
nsIRunnable* aCallback) override;
virtual nsresult MakeFullScreen(bool aFullScreen,
nsIScreen* aScreen = nullptr) override;
--- a/widget/windows/nsWindowGfx.cpp
+++ b/widget/windows/nsWindowGfx.cpp
@@ -465,17 +465,17 @@ nsresult nsWindowGfx::CreateIcon(imgICon
IntSize aScaledSize,
HICON *aIcon) {
MOZ_ASSERT((aScaledSize.width > 0 && aScaledSize.height > 0) ||
(aScaledSize.width == 0 && aScaledSize.height == 0));
// Get the image data
RefPtr<SourceSurface> surface =
- aContainer->GetFrame(imgIContainer::FRAME_CURRENT,
+ aContainer->GetFrameAtSize(aScaledSize, imgIContainer::FRAME_CURRENT,
imgIContainer::FLAG_SYNC_DECODE);
NS_ENSURE_TRUE(surface, NS_ERROR_NOT_AVAILABLE);
IntSize frameSize = surface->GetSize();
if (frameSize.IsEmpty()) {
return NS_ERROR_FAILURE;
}