michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* code for HTML client-side image maps */ michael@0: michael@0: #include "nsImageMap.h" michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() michael@0: #include "nsString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsImageFrame.h" michael@0: #include "nsCoord.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class Area { michael@0: public: michael@0: Area(nsIContent* aArea); michael@0: virtual ~Area(); michael@0: michael@0: virtual void ParseCoords(const nsAString& aSpec); michael@0: michael@0: virtual bool IsInside(nscoord x, nscoord y) const = 0; michael@0: virtual void Draw(nsIFrame* aFrame, nsRenderingContext& aRC) = 0; michael@0: virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) = 0; michael@0: michael@0: void HasFocus(bool aHasFocus); michael@0: michael@0: nsCOMPtr mArea; michael@0: nscoord* mCoords; michael@0: int32_t mNumCoords; michael@0: bool mHasFocus; michael@0: }; michael@0: michael@0: Area::Area(nsIContent* aArea) michael@0: : mArea(aArea) michael@0: { michael@0: MOZ_COUNT_CTOR(Area); michael@0: NS_PRECONDITION(mArea, "How did that happen?"); michael@0: mCoords = nullptr; michael@0: mNumCoords = 0; michael@0: mHasFocus = false; michael@0: } michael@0: michael@0: Area::~Area() michael@0: { michael@0: MOZ_COUNT_DTOR(Area); michael@0: delete [] mCoords; michael@0: } michael@0: michael@0: #include michael@0: michael@0: inline bool michael@0: is_space(char c) michael@0: { michael@0: return (c == ' ' || michael@0: c == '\f' || michael@0: c == '\n' || michael@0: c == '\r' || michael@0: c == '\t' || michael@0: c == '\v'); michael@0: } michael@0: michael@0: static void logMessage(nsIContent* aContent, michael@0: const nsAString& aCoordsSpec, michael@0: int32_t aFlags, michael@0: const char* aMessageName) { michael@0: nsIDocument* doc = aContent->OwnerDoc(); michael@0: michael@0: nsContentUtils::ReportToConsole( michael@0: aFlags, NS_LITERAL_CSTRING("ImageMap"), doc, michael@0: nsContentUtils::eLAYOUT_PROPERTIES, michael@0: aMessageName, michael@0: nullptr, /* params */ michael@0: 0, /* params length */ michael@0: nullptr, michael@0: PromiseFlatString(NS_LITERAL_STRING("coords=\"") + michael@0: aCoordsSpec + michael@0: NS_LITERAL_STRING("\""))); /* source line */ michael@0: } michael@0: michael@0: void Area::ParseCoords(const nsAString& aSpec) michael@0: { michael@0: char* cp = ToNewCString(aSpec); michael@0: if (cp) { michael@0: char *tptr; michael@0: char *n_str; michael@0: int32_t i, cnt; michael@0: int32_t *value_list; michael@0: michael@0: /* michael@0: * Nothing in an empty list michael@0: */ michael@0: mNumCoords = 0; michael@0: mCoords = nullptr; michael@0: if (*cp == '\0') michael@0: { michael@0: nsMemory::Free(cp); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * Skip beginning whitespace, all whitespace is empty list. michael@0: */ michael@0: n_str = cp; michael@0: while (is_space(*n_str)) michael@0: { michael@0: n_str++; michael@0: } michael@0: if (*n_str == '\0') michael@0: { michael@0: nsMemory::Free(cp); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * Make a pass where any two numbers separated by just whitespace michael@0: * are given a comma separator. Count entries while passing. michael@0: */ michael@0: cnt = 0; michael@0: while (*n_str != '\0') michael@0: { michael@0: bool has_comma; michael@0: michael@0: /* michael@0: * Skip to a separator michael@0: */ michael@0: tptr = n_str; michael@0: while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') michael@0: { michael@0: tptr++; michael@0: } michael@0: n_str = tptr; michael@0: michael@0: /* michael@0: * If no more entries, break out here michael@0: */ michael@0: if (*n_str == '\0') michael@0: { michael@0: break; michael@0: } michael@0: michael@0: /* michael@0: * Skip to the end of the separator, noting if we have a michael@0: * comma. michael@0: */ michael@0: has_comma = false; michael@0: while (is_space(*tptr) || *tptr == ',') michael@0: { michael@0: if (*tptr == ',') michael@0: { michael@0: if (!has_comma) michael@0: { michael@0: has_comma = true; michael@0: } michael@0: else michael@0: { michael@0: break; michael@0: } michael@0: } michael@0: tptr++; michael@0: } michael@0: /* michael@0: * If this was trailing whitespace we skipped, we are done. michael@0: */ michael@0: if ((*tptr == '\0') && !has_comma) michael@0: { michael@0: break; michael@0: } michael@0: /* michael@0: * Else if the separator is all whitespace, and this is not the michael@0: * end of the string, add a comma to the separator. michael@0: */ michael@0: else if (!has_comma) michael@0: { michael@0: *n_str = ','; michael@0: } michael@0: michael@0: /* michael@0: * count the entry skipped. michael@0: */ michael@0: cnt++; michael@0: michael@0: n_str = tptr; michael@0: } michael@0: /* michael@0: * count the last entry in the list. michael@0: */ michael@0: cnt++; michael@0: michael@0: /* michael@0: * Allocate space for the coordinate array. michael@0: */ michael@0: value_list = new nscoord[cnt]; michael@0: if (!value_list) michael@0: { michael@0: nsMemory::Free(cp); michael@0: return; michael@0: } michael@0: michael@0: /* michael@0: * Second pass to copy integer values into list. michael@0: */ michael@0: tptr = cp; michael@0: for (i=0; iGetRect(); michael@0: r.MoveTo(0, 0); michael@0: nscoord x1 = r.x; michael@0: nscoord y1 = r.y; michael@0: const nscoord kOnePixel = nsPresContext::CSSPixelsToAppUnits(1); michael@0: nscoord x2 = r.XMost() - kOnePixel; michael@0: nscoord y2 = r.YMost() - kOnePixel; michael@0: // XXX aRC.DrawRect(r) result is ugly, that's why we use DrawLine. michael@0: aRC.DrawLine(x1, y1, x1, y2); michael@0: aRC.DrawLine(x1, y2, x2, y2); michael@0: aRC.DrawLine(x1, y1, x2, y1); michael@0: aRC.DrawLine(x2, y1, x2, y2); michael@0: } michael@0: } michael@0: michael@0: void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect) michael@0: { michael@0: aRect = aFrame->GetRect(); michael@0: aRect.MoveTo(0, 0); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: class RectArea : public Area { michael@0: public: michael@0: RectArea(nsIContent* aArea); michael@0: michael@0: virtual void ParseCoords(const nsAString& aSpec) MOZ_OVERRIDE; michael@0: virtual bool IsInside(nscoord x, nscoord y) const MOZ_OVERRIDE; michael@0: virtual void Draw(nsIFrame* aFrame, nsRenderingContext& aRC) MOZ_OVERRIDE; michael@0: virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: RectArea::RectArea(nsIContent* aArea) michael@0: : Area(aArea) michael@0: { michael@0: } michael@0: michael@0: void RectArea::ParseCoords(const nsAString& aSpec) michael@0: { michael@0: Area::ParseCoords(aSpec); michael@0: michael@0: bool saneRect = true; michael@0: int32_t flag = nsIScriptError::warningFlag; michael@0: if (mNumCoords >= 4) { michael@0: if (mCoords[0] > mCoords[2]) { michael@0: // x-coords in reversed order michael@0: nscoord x = mCoords[2]; michael@0: mCoords[2] = mCoords[0]; michael@0: mCoords[0] = x; michael@0: saneRect = false; michael@0: } michael@0: michael@0: if (mCoords[1] > mCoords[3]) { michael@0: // y-coords in reversed order michael@0: nscoord y = mCoords[3]; michael@0: mCoords[3] = mCoords[1]; michael@0: mCoords[1] = y; michael@0: saneRect = false; michael@0: } michael@0: michael@0: if (mNumCoords > 4) { michael@0: // Someone missed the concept of a rect here michael@0: saneRect = false; michael@0: } michael@0: } else { michael@0: saneRect = false; michael@0: flag = nsIScriptError::errorFlag; michael@0: } michael@0: michael@0: if (!saneRect) { michael@0: logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError"); michael@0: } michael@0: } michael@0: michael@0: bool RectArea::IsInside(nscoord x, nscoord y) const michael@0: { michael@0: if (mNumCoords >= 4) { // Note: > is for nav compatibility michael@0: nscoord x1 = mCoords[0]; michael@0: nscoord y1 = mCoords[1]; michael@0: nscoord x2 = mCoords[2]; michael@0: nscoord y2 = mCoords[3]; michael@0: NS_ASSERTION(x1 <= x2 && y1 <= y2, michael@0: "Someone screwed up RectArea::ParseCoords"); michael@0: if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void RectArea::Draw(nsIFrame* aFrame, nsRenderingContext& aRC) michael@0: { michael@0: if (mHasFocus) { michael@0: if (mNumCoords >= 4) { michael@0: nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]); michael@0: nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]); michael@0: NS_ASSERTION(x1 <= x2 && y1 <= y2, michael@0: "Someone screwed up RectArea::ParseCoords"); michael@0: aRC.DrawLine(x1, y1, x1, y2); michael@0: aRC.DrawLine(x1, y2, x2, y2); michael@0: aRC.DrawLine(x1, y1, x2, y1); michael@0: aRC.DrawLine(x2, y1, x2, y2); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void RectArea::GetRect(nsIFrame* aFrame, nsRect& aRect) michael@0: { michael@0: if (mNumCoords >= 4) { michael@0: nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]); michael@0: nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]); michael@0: NS_ASSERTION(x1 <= x2 && y1 <= y2, michael@0: "Someone screwed up RectArea::ParseCoords"); michael@0: michael@0: aRect.SetRect(x1, y1, x2, y2); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: class PolyArea : public Area { michael@0: public: michael@0: PolyArea(nsIContent* aArea); michael@0: michael@0: virtual void ParseCoords(const nsAString& aSpec) MOZ_OVERRIDE; michael@0: virtual bool IsInside(nscoord x, nscoord y) const MOZ_OVERRIDE; michael@0: virtual void Draw(nsIFrame* aFrame, nsRenderingContext& aRC) MOZ_OVERRIDE; michael@0: virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: PolyArea::PolyArea(nsIContent* aArea) michael@0: : Area(aArea) michael@0: { michael@0: } michael@0: michael@0: void PolyArea::ParseCoords(const nsAString& aSpec) michael@0: { michael@0: Area::ParseCoords(aSpec); michael@0: michael@0: if (mNumCoords >= 2) { michael@0: if (mNumCoords & 1U) { michael@0: logMessage(mArea, michael@0: aSpec, michael@0: nsIScriptError::warningFlag, michael@0: "ImageMapPolyOddNumberOfCoords"); michael@0: } michael@0: } else { michael@0: logMessage(mArea, michael@0: aSpec, michael@0: nsIScriptError::errorFlag, michael@0: "ImageMapPolyWrongNumberOfCoords"); michael@0: } michael@0: } michael@0: michael@0: bool PolyArea::IsInside(nscoord x, nscoord y) const michael@0: { michael@0: if (mNumCoords >= 6) { michael@0: int32_t intersects = 0; michael@0: nscoord wherex = x; michael@0: nscoord wherey = y; michael@0: int32_t totalv = mNumCoords / 2; michael@0: int32_t totalc = totalv * 2; michael@0: nscoord xval = mCoords[totalc - 2]; michael@0: nscoord yval = mCoords[totalc - 1]; michael@0: int32_t end = totalc; michael@0: int32_t pointer = 1; michael@0: michael@0: if ((yval >= wherey) != (mCoords[pointer] >= wherey)) { michael@0: if ((xval >= wherex) == (mCoords[0] >= wherex)) { michael@0: intersects += (xval >= wherex) ? 1 : 0; michael@0: } else { michael@0: intersects += ((xval - (yval - wherey) * michael@0: (mCoords[0] - xval) / michael@0: (mCoords[pointer] - yval)) >= wherex) ? 1 : 0; michael@0: } michael@0: } michael@0: michael@0: // XXX I wonder what this is doing; this is a translation of ptinpoly.c michael@0: while (pointer < end) { michael@0: yval = mCoords[pointer]; michael@0: pointer += 2; michael@0: if (yval >= wherey) { michael@0: while((pointer < end) && (mCoords[pointer] >= wherey)) michael@0: pointer+=2; michael@0: if (pointer >= end) michael@0: break; michael@0: if ((mCoords[pointer-3] >= wherex) == michael@0: (mCoords[pointer-1] >= wherex)) { michael@0: intersects += (mCoords[pointer-3] >= wherex) ? 1 : 0; michael@0: } else { michael@0: intersects += michael@0: ((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) * michael@0: (mCoords[pointer-1] - mCoords[pointer-3]) / michael@0: (mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0; michael@0: } michael@0: } else { michael@0: while((pointer < end) && (mCoords[pointer] < wherey)) michael@0: pointer+=2; michael@0: if (pointer >= end) michael@0: break; michael@0: if ((mCoords[pointer-3] >= wherex) == michael@0: (mCoords[pointer-1] >= wherex)) { michael@0: intersects += (mCoords[pointer-3] >= wherex) ? 1:0; michael@0: } else { michael@0: intersects += michael@0: ((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) * michael@0: (mCoords[pointer-1] - mCoords[pointer-3]) / michael@0: (mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0; michael@0: } michael@0: } michael@0: } michael@0: if ((intersects & 1) != 0) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void PolyArea::Draw(nsIFrame* aFrame, nsRenderingContext& aRC) michael@0: { michael@0: if (mHasFocus) { michael@0: if (mNumCoords >= 6) { michael@0: nscoord x0 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: nscoord y0 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: nscoord x1, y1; michael@0: for (int32_t i = 2; i < mNumCoords; i += 2) { michael@0: x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[i]); michael@0: y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[i+1]); michael@0: aRC.DrawLine(x0, y0, x1, y1); michael@0: x0 = x1; michael@0: y0 = y1; michael@0: } michael@0: x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: aRC.DrawLine(x0, y0, x1, y1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void PolyArea::GetRect(nsIFrame* aFrame, nsRect& aRect) michael@0: { michael@0: if (mNumCoords >= 6) { michael@0: nscoord x1, x2, y1, y2, xtmp, ytmp; michael@0: x1 = x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: y1 = y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: for (int32_t i = 2; i < mNumCoords; i += 2) { michael@0: xtmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i]); michael@0: ytmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i+1]); michael@0: x1 = x1 < xtmp ? x1 : xtmp; michael@0: y1 = y1 < ytmp ? y1 : ytmp; michael@0: x2 = x2 > xtmp ? x2 : xtmp; michael@0: y2 = y2 > ytmp ? y2 : ytmp; michael@0: } michael@0: michael@0: aRect.SetRect(x1, y1, x2, y2); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: class CircleArea : public Area { michael@0: public: michael@0: CircleArea(nsIContent* aArea); michael@0: michael@0: virtual void ParseCoords(const nsAString& aSpec) MOZ_OVERRIDE; michael@0: virtual bool IsInside(nscoord x, nscoord y) const MOZ_OVERRIDE; michael@0: virtual void Draw(nsIFrame* aFrame, nsRenderingContext& aRC) MOZ_OVERRIDE; michael@0: virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) MOZ_OVERRIDE; michael@0: }; michael@0: michael@0: CircleArea::CircleArea(nsIContent* aArea) michael@0: : Area(aArea) michael@0: { michael@0: } michael@0: michael@0: void CircleArea::ParseCoords(const nsAString& aSpec) michael@0: { michael@0: Area::ParseCoords(aSpec); michael@0: michael@0: bool wrongNumberOfCoords = false; michael@0: int32_t flag = nsIScriptError::warningFlag; michael@0: if (mNumCoords >= 3) { michael@0: if (mCoords[2] < 0) { michael@0: logMessage(mArea, michael@0: aSpec, michael@0: nsIScriptError::errorFlag, michael@0: "ImageMapCircleNegativeRadius"); michael@0: } michael@0: michael@0: if (mNumCoords > 3) { michael@0: wrongNumberOfCoords = true; michael@0: } michael@0: } else { michael@0: wrongNumberOfCoords = true; michael@0: flag = nsIScriptError::errorFlag; michael@0: } michael@0: michael@0: if (wrongNumberOfCoords) { michael@0: logMessage(mArea, michael@0: aSpec, michael@0: flag, michael@0: "ImageMapCircleWrongNumberOfCoords"); michael@0: } michael@0: } michael@0: michael@0: bool CircleArea::IsInside(nscoord x, nscoord y) const michael@0: { michael@0: // Note: > is for nav compatibility michael@0: if (mNumCoords >= 3) { michael@0: nscoord x1 = mCoords[0]; michael@0: nscoord y1 = mCoords[1]; michael@0: nscoord radius = mCoords[2]; michael@0: if (radius < 0) { michael@0: return false; michael@0: } michael@0: nscoord dx = x1 - x; michael@0: nscoord dy = y1 - y; michael@0: nscoord dist = (dx * dx) + (dy * dy); michael@0: if (dist <= (radius * radius)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void CircleArea::Draw(nsIFrame* aFrame, nsRenderingContext& aRC) michael@0: { michael@0: if (mHasFocus) { michael@0: if (mNumCoords >= 3) { michael@0: nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]); michael@0: if (radius < 0) { michael@0: return; michael@0: } michael@0: nscoord x = x1 - radius; michael@0: nscoord y = y1 - radius; michael@0: nscoord w = 2 * radius; michael@0: aRC.DrawEllipse(x, y, w, w); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void CircleArea::GetRect(nsIFrame* aFrame, nsRect& aRect) michael@0: { michael@0: if (mNumCoords >= 3) { michael@0: nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]); michael@0: nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]); michael@0: nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]); michael@0: if (radius < 0) { michael@0: return; michael@0: } michael@0: michael@0: aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: nsImageMap::nsImageMap() : michael@0: mImageFrame(nullptr), michael@0: mContainsBlockContents(false) michael@0: { michael@0: } michael@0: michael@0: nsImageMap::~nsImageMap() michael@0: { michael@0: NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called"); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsImageMap, michael@0: nsIMutationObserver, michael@0: nsIDOMEventListener) michael@0: michael@0: nsresult michael@0: nsImageMap::GetBoundsForAreaContent(nsIContent *aContent, michael@0: nsRect& aBounds) michael@0: { michael@0: NS_ENSURE_TRUE(aContent && mImageFrame, NS_ERROR_INVALID_ARG); michael@0: michael@0: // Find the Area struct associated with this content node, and return bounds michael@0: uint32_t i, n = mAreas.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: Area* area = mAreas.ElementAt(i); michael@0: if (area->mArea == aContent) { michael@0: aBounds = nsRect(); michael@0: area->GetRect(mImageFrame, aBounds); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: void michael@0: nsImageMap::FreeAreas() michael@0: { michael@0: uint32_t i, n = mAreas.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: Area* area = mAreas.ElementAt(i); michael@0: if (area->mArea->IsInDoc()) { michael@0: NS_ASSERTION(area->mArea->GetPrimaryFrame() == mImageFrame, michael@0: "Unexpected primary frame"); michael@0: michael@0: area->mArea->SetPrimaryFrame(nullptr); michael@0: } michael@0: michael@0: area->mArea->RemoveSystemEventListener(NS_LITERAL_STRING("focus"), this, michael@0: false); michael@0: area->mArea->RemoveSystemEventListener(NS_LITERAL_STRING("blur"), this, michael@0: false); michael@0: delete area; michael@0: } michael@0: mAreas.Clear(); michael@0: } michael@0: michael@0: nsresult michael@0: nsImageMap::Init(nsImageFrame* aImageFrame, nsIContent* aMap) michael@0: { michael@0: NS_PRECONDITION(aMap, "null ptr"); michael@0: if (!aMap) { michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: mImageFrame = aImageFrame; michael@0: michael@0: mMap = aMap; michael@0: mMap->AddMutationObserver(this); michael@0: michael@0: // "Compile" the areas in the map into faster access versions michael@0: return UpdateAreas(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsImageMap::SearchForAreas(nsIContent* aParent, bool& aFoundArea, michael@0: bool& aFoundAnchor) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: uint32_t i, n = aParent->GetChildCount(); michael@0: michael@0: // Look for or elements. We'll use whichever type we find first. michael@0: for (i = 0; i < n; i++) { michael@0: nsIContent *child = aParent->GetChildAt(i); michael@0: michael@0: if (child->IsHTML()) { michael@0: // If we haven't determined that the map element contains an michael@0: // element yet, then look for . michael@0: if (!aFoundAnchor && child->Tag() == nsGkAtoms::area) { michael@0: aFoundArea = true; michael@0: rv = AddArea(child); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Continue to next child. This stops mContainsBlockContents from michael@0: // getting set. It also makes us ignore children of s which michael@0: // is consistent with how we react to dynamic insertion of such michael@0: // children. michael@0: continue; michael@0: } michael@0: // If we haven't determined that the map element contains an michael@0: // element yet, then look for . michael@0: if (!aFoundArea && child->Tag() == nsGkAtoms::a) { michael@0: aFoundAnchor = true; michael@0: rv = AddArea(child); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: if (child->IsElement()) { michael@0: mContainsBlockContents = true; michael@0: rv = SearchForAreas(child, aFoundArea, aFoundAnchor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageMap::UpdateAreas() michael@0: { michael@0: // Get rid of old area data michael@0: FreeAreas(); michael@0: michael@0: bool foundArea = false; michael@0: bool foundAnchor = false; michael@0: mContainsBlockContents = false; michael@0: michael@0: nsresult rv = SearchForAreas(mMap, foundArea, foundAnchor); michael@0: #ifdef ACCESSIBILITY michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAccessibilityService* accService = GetAccService(); michael@0: if (accService) { michael@0: accService->UpdateImageMap(mImageFrame); michael@0: } michael@0: } michael@0: #endif michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsImageMap::AddArea(nsIContent* aArea) michael@0: { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::rect, &nsGkAtoms::rectangle, michael@0: &nsGkAtoms::circle, &nsGkAtoms::circ, michael@0: &nsGkAtoms::_default, michael@0: &nsGkAtoms::poly, &nsGkAtoms::polygon, michael@0: nullptr}; michael@0: michael@0: Area* area; michael@0: switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape, michael@0: strings, eIgnoreCase)) { michael@0: case nsIContent::ATTR_VALUE_NO_MATCH: michael@0: case nsIContent::ATTR_MISSING: michael@0: case 0: michael@0: case 1: michael@0: area = new RectArea(aArea); michael@0: break; michael@0: case 2: michael@0: case 3: michael@0: area = new CircleArea(aArea); michael@0: break; michael@0: case 4: michael@0: area = new DefaultArea(aArea); michael@0: break; michael@0: case 5: michael@0: case 6: michael@0: area = new PolyArea(aArea); michael@0: break; michael@0: default: michael@0: area = nullptr; michael@0: NS_NOTREACHED("FindAttrValueIn returned an unexpected value."); michael@0: break; michael@0: } michael@0: if (!area) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: //Add focus listener to track area focus changes michael@0: aArea->AddSystemEventListener(NS_LITERAL_STRING("focus"), this, false, michael@0: false); michael@0: aArea->AddSystemEventListener(NS_LITERAL_STRING("blur"), this, false, michael@0: false); michael@0: michael@0: // This is a nasty hack. It needs to go away: see bug 135040. Once this is michael@0: // removed, the code added to RestyleManager::RestyleElement, michael@0: // nsCSSFrameConstructor::ContentRemoved (both hacks there), and michael@0: // RestyleManager::ProcessRestyledFrames to work around this issue can michael@0: // be removed. michael@0: aArea->SetPrimaryFrame(mImageFrame); michael@0: michael@0: nsAutoString coords; michael@0: aArea->GetAttr(kNameSpaceID_None, nsGkAtoms::coords, coords); michael@0: area->ParseCoords(coords); michael@0: mAreas.AppendElement(area); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsImageMap::GetArea(nscoord aX, nscoord aY) const michael@0: { michael@0: NS_ASSERTION(mMap, "Not initialized"); michael@0: uint32_t i, n = mAreas.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: Area* area = mAreas.ElementAt(i); michael@0: if (area->IsInside(aX, aY)) { michael@0: return area->mArea; michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsImageMap::GetAreaAt(uint32_t aIndex) const michael@0: { michael@0: return mAreas.ElementAt(aIndex)->mArea; michael@0: } michael@0: michael@0: void michael@0: nsImageMap::Draw(nsIFrame* aFrame, nsRenderingContext& aRC) michael@0: { michael@0: uint32_t i, n = mAreas.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: Area* area = mAreas.ElementAt(i); michael@0: area->Draw(aFrame, aRC); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageMap::MaybeUpdateAreas(nsIContent *aContent) michael@0: { michael@0: if (aContent == mMap || mContainsBlockContents) { michael@0: UpdateAreas(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageMap::AttributeChanged(nsIDocument* aDocument, michael@0: dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: // If the parent of the changing content node is our map then update michael@0: // the map. But only do this if the node is an HTML or michael@0: // and the attribute that's changing is "shape" or "coords" -- those michael@0: // are the only cases we care about. michael@0: if ((aElement->NodeInfo()->Equals(nsGkAtoms::area) || michael@0: aElement->NodeInfo()->Equals(nsGkAtoms::a)) && michael@0: aElement->IsHTML() && michael@0: aNameSpaceID == kNameSpaceID_None && michael@0: (aAttribute == nsGkAtoms::shape || michael@0: aAttribute == nsGkAtoms::coords)) { michael@0: MaybeUpdateAreas(aElement->GetParent()); michael@0: } else if (aElement == mMap && michael@0: aNameSpaceID == kNameSpaceID_None && michael@0: (aAttribute == nsGkAtoms::name || michael@0: aAttribute == nsGkAtoms::id) && michael@0: mImageFrame) { michael@0: // ID or name has changed. Let ImageFrame recreate ImageMap. michael@0: mImageFrame->DisconnectMap(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsImageMap::ContentAppended(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t /* unused */) michael@0: { michael@0: MaybeUpdateAreas(aContainer); michael@0: } michael@0: michael@0: void michael@0: nsImageMap::ContentInserted(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t /* unused */) michael@0: { michael@0: MaybeUpdateAreas(aContainer); michael@0: } michael@0: michael@0: void michael@0: nsImageMap::ContentRemoved(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: MaybeUpdateAreas(aContainer); michael@0: } michael@0: michael@0: void michael@0: nsImageMap::ParentChainChanged(nsIContent* aContent) michael@0: { michael@0: NS_ASSERTION(aContent == mMap, michael@0: "Unexpected ParentChainChanged notification!"); michael@0: if (mImageFrame) { michael@0: mImageFrame->DisconnectMap(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsImageMap::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: bool focus = eventType.EqualsLiteral("focus"); michael@0: NS_ABORT_IF_FALSE(focus == !eventType.EqualsLiteral("blur"), michael@0: "Unexpected event type"); michael@0: michael@0: //Set which one of our areas changed focus michael@0: nsCOMPtr targetContent = do_QueryInterface( michael@0: aEvent->InternalDOMEvent()->GetTarget()); michael@0: if (!targetContent) { michael@0: return NS_OK; michael@0: } michael@0: uint32_t i, n = mAreas.Length(); michael@0: for (i = 0; i < n; i++) { michael@0: Area* area = mAreas.ElementAt(i); michael@0: if (area->mArea == targetContent) { michael@0: //Set or Remove internal focus michael@0: area->HasFocus(focus); michael@0: //Now invalidate the rect michael@0: if (mImageFrame) { michael@0: mImageFrame->InvalidateFrame(); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsImageMap::Destroy(void) michael@0: { michael@0: FreeAreas(); michael@0: mImageFrame = nullptr; michael@0: mMap->RemoveMutationObserver(this); michael@0: }