Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / layout / generic / nsImageMap.cpp
blob9e9ae96f26d4cffd0b44379270e7b54ff6e0a4b5
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* code for HTML client-side image maps */
9 #include "nsImageMap.h"
11 #include "mozilla/dom/Element.h"
12 #include "mozilla/dom/Event.h" // for Event
13 #include "mozilla/dom/HTMLAreaElement.h"
14 #include "mozilla/gfx/PathHelpers.h"
15 #include "mozilla/UniquePtr.h"
16 #include "nsString.h"
17 #include "nsReadableUtils.h"
18 #include "nsPresContext.h"
19 #include "nsNameSpaceManager.h"
20 #include "nsGkAtoms.h"
21 #include "nsImageFrame.h"
22 #include "nsCoord.h"
23 #include "nsIContentInlines.h"
24 #include "nsIScriptError.h"
25 #include "nsContentUtils.h"
26 #include "nsLayoutUtils.h"
28 #ifdef ACCESSIBILITY
29 # include "nsAccessibilityService.h"
30 #endif
32 using namespace mozilla;
33 using namespace mozilla::gfx;
34 using namespace mozilla::dom;
36 class Area {
37 public:
38 explicit Area(HTMLAreaElement* aArea);
39 virtual ~Area();
41 virtual void ParseCoords(const nsAString& aSpec);
43 virtual bool IsInside(nscoord x, nscoord y) const = 0;
44 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
45 const ColorPattern& aColor,
46 const StrokeOptions& aStrokeOptions) = 0;
47 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) = 0;
49 void HasFocus(bool aHasFocus);
51 RefPtr<HTMLAreaElement> mArea;
52 UniquePtr<nscoord[]> mCoords;
53 int32_t mNumCoords;
54 bool mHasFocus;
57 Area::Area(HTMLAreaElement* aArea) : mArea(aArea) {
58 MOZ_COUNT_CTOR(Area);
59 MOZ_ASSERT(mArea, "How did that happen?");
60 mNumCoords = 0;
61 mHasFocus = false;
64 Area::~Area() { MOZ_COUNT_DTOR(Area); }
66 #include <stdlib.h>
68 inline bool is_space(char c) {
69 return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ||
70 c == '\v');
73 static void logMessage(nsIContent* aContent, const nsAString& aCoordsSpec,
74 int32_t aFlags, const char* aMessageName) {
75 nsContentUtils::ReportToConsole(
76 aFlags, "Layout: ImageMap"_ns, aContent->OwnerDoc(),
77 nsContentUtils::eLAYOUT_PROPERTIES, aMessageName,
78 nsTArray<nsString>(), /* params */
79 nullptr,
80 PromiseFlatString(u"coords=\""_ns + aCoordsSpec +
81 u"\""_ns)); /* source line */
84 void Area::ParseCoords(const nsAString& aSpec) {
85 char* cp = ToNewUTF8String(aSpec);
86 if (cp) {
87 char* tptr;
88 char* n_str;
89 int32_t i, cnt;
92 * Nothing in an empty list
94 mNumCoords = 0;
95 mCoords = nullptr;
96 if (*cp == '\0') {
97 free(cp);
98 return;
102 * Skip beginning whitespace, all whitespace is empty list.
104 n_str = cp;
105 while (is_space(*n_str)) {
106 n_str++;
108 if (*n_str == '\0') {
109 free(cp);
110 return;
114 * Make a pass where any two numbers separated by just whitespace
115 * are given a comma separator. Count entries while passing.
117 cnt = 0;
118 while (*n_str != '\0') {
119 bool has_comma;
122 * Skip to a separator
124 tptr = n_str;
125 while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') {
126 tptr++;
128 n_str = tptr;
131 * If no more entries, break out here
133 if (*n_str == '\0') {
134 break;
138 * Skip to the end of the separator, noting if we have a
139 * comma.
141 has_comma = false;
142 while (is_space(*tptr) || *tptr == ',') {
143 if (*tptr == ',') {
144 if (!has_comma) {
145 has_comma = true;
146 } else {
147 break;
150 tptr++;
153 * If this was trailing whitespace we skipped, we are done.
155 if ((*tptr == '\0') && !has_comma) {
156 break;
159 * Else if the separator is all whitespace, and this is not the
160 * end of the string, add a comma to the separator.
162 else if (!has_comma) {
163 *n_str = ',';
167 * count the entry skipped.
169 cnt++;
171 n_str = tptr;
174 * count the last entry in the list.
176 cnt++;
179 * Allocate space for the coordinate array.
181 UniquePtr<nscoord[]> value_list = MakeUnique<nscoord[]>(cnt);
182 if (!value_list) {
183 free(cp);
184 return;
188 * Second pass to copy integer values into list.
190 tptr = cp;
191 for (i = 0; i < cnt; i++) {
192 char* ptr;
194 ptr = strchr(tptr, ',');
195 if (ptr) {
196 *ptr = '\0';
199 * Strip whitespace in front of number because I don't
200 * trust atoi to do it on all platforms.
202 while (is_space(*tptr)) {
203 tptr++;
205 if (*tptr == '\0') {
206 value_list[i] = 0;
207 } else {
208 value_list[i] = (nscoord)::atoi(tptr);
210 if (ptr) {
211 *ptr = ',';
212 tptr = ptr + 1;
216 mNumCoords = cnt;
217 mCoords = std::move(value_list);
219 free(cp);
223 void Area::HasFocus(bool aHasFocus) { mHasFocus = aHasFocus; }
225 //----------------------------------------------------------------------
227 class DefaultArea final : public Area {
228 public:
229 explicit DefaultArea(HTMLAreaElement* aArea);
231 virtual bool IsInside(nscoord x, nscoord y) const override;
232 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
233 const ColorPattern& aColor,
234 const StrokeOptions& aStrokeOptions) override;
235 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
238 DefaultArea::DefaultArea(HTMLAreaElement* aArea) : Area(aArea) {}
240 bool DefaultArea::IsInside(nscoord x, nscoord y) const { return true; }
242 void DefaultArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
243 const ColorPattern& aColor,
244 const StrokeOptions& aStrokeOptions) {
245 if (mHasFocus) {
246 nsRect r(nsPoint(0, 0), aFrame->GetSize());
247 const nscoord kOnePixel = nsPresContext::CSSPixelsToAppUnits(1);
248 r.width -= kOnePixel;
249 r.height -= kOnePixel;
250 Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
251 r, aFrame->PresContext()->AppUnitsPerDevPixel()));
252 StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
256 void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
257 aRect = aFrame->GetRect();
258 aRect.MoveTo(0, 0);
261 //----------------------------------------------------------------------
263 class RectArea final : public Area {
264 public:
265 explicit RectArea(HTMLAreaElement* aArea);
267 virtual void ParseCoords(const nsAString& aSpec) override;
268 virtual bool IsInside(nscoord x, nscoord y) const override;
269 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
270 const ColorPattern& aColor,
271 const StrokeOptions& aStrokeOptions) override;
272 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
275 RectArea::RectArea(HTMLAreaElement* aArea) : Area(aArea) {}
277 void RectArea::ParseCoords(const nsAString& aSpec) {
278 Area::ParseCoords(aSpec);
280 bool saneRect = true;
281 int32_t flag = nsIScriptError::warningFlag;
282 if (mNumCoords >= 4) {
283 if (mCoords[0] > mCoords[2]) {
284 // x-coords in reversed order
285 nscoord x = mCoords[2];
286 mCoords[2] = mCoords[0];
287 mCoords[0] = x;
288 saneRect = false;
291 if (mCoords[1] > mCoords[3]) {
292 // y-coords in reversed order
293 nscoord y = mCoords[3];
294 mCoords[3] = mCoords[1];
295 mCoords[1] = y;
296 saneRect = false;
299 if (mNumCoords > 4) {
300 // Someone missed the concept of a rect here
301 saneRect = false;
303 } else {
304 saneRect = false;
305 flag = nsIScriptError::errorFlag;
308 if (!saneRect) {
309 logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError");
313 bool RectArea::IsInside(nscoord x, nscoord y) const {
314 if (mNumCoords >= 4) { // Note: > is for nav compatibility
315 nscoord x1 = mCoords[0];
316 nscoord y1 = mCoords[1];
317 nscoord x2 = mCoords[2];
318 nscoord y2 = mCoords[3];
319 NS_ASSERTION(x1 <= x2 && y1 <= y2,
320 "Someone screwed up RectArea::ParseCoords");
321 if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) {
322 return true;
325 return false;
328 void RectArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
329 const ColorPattern& aColor,
330 const StrokeOptions& aStrokeOptions) {
331 if (mHasFocus) {
332 if (mNumCoords >= 4) {
333 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
334 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
335 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
336 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
337 NS_ASSERTION(x1 <= x2 && y1 <= y2,
338 "Someone screwed up RectArea::ParseCoords");
339 nsRect r(x1, y1, x2 - x1, y2 - y1);
340 Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
341 r, aFrame->PresContext()->AppUnitsPerDevPixel()));
342 StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
347 void RectArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
348 if (mNumCoords >= 4) {
349 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
350 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
351 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
352 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
353 NS_ASSERTION(x1 <= x2 && y1 <= y2,
354 "Someone screwed up RectArea::ParseCoords");
356 aRect.SetRect(x1, y1, x2, y2);
360 //----------------------------------------------------------------------
362 class PolyArea final : public Area {
363 public:
364 explicit PolyArea(HTMLAreaElement* aArea);
366 virtual void ParseCoords(const nsAString& aSpec) override;
367 virtual bool IsInside(nscoord x, nscoord y) const override;
368 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
369 const ColorPattern& aColor,
370 const StrokeOptions& aStrokeOptions) override;
371 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
374 PolyArea::PolyArea(HTMLAreaElement* aArea) : Area(aArea) {}
376 void PolyArea::ParseCoords(const nsAString& aSpec) {
377 Area::ParseCoords(aSpec);
379 if (mNumCoords >= 2) {
380 if (mNumCoords & 1U) {
381 logMessage(mArea, aSpec, nsIScriptError::warningFlag,
382 "ImageMapPolyOddNumberOfCoords");
384 } else {
385 logMessage(mArea, aSpec, nsIScriptError::errorFlag,
386 "ImageMapPolyWrongNumberOfCoords");
390 bool PolyArea::IsInside(nscoord x, nscoord y) const {
391 if (mNumCoords >= 6) {
392 int32_t intersects = 0;
393 nscoord wherex = x;
394 nscoord wherey = y;
395 int32_t totalv = mNumCoords / 2;
396 int32_t totalc = totalv * 2;
397 nscoord xval = mCoords[totalc - 2];
398 nscoord yval = mCoords[totalc - 1];
399 int32_t end = totalc;
400 int32_t pointer = 1;
402 if ((yval >= wherey) != (mCoords[pointer] >= wherey)) {
403 if ((xval >= wherex) == (mCoords[0] >= wherex)) {
404 intersects += (xval >= wherex) ? 1 : 0;
405 } else {
406 intersects += ((xval - (yval - wherey) * (mCoords[0] - xval) /
407 (mCoords[pointer] - yval)) >= wherex)
409 : 0;
413 // XXX I wonder what this is doing; this is a translation of ptinpoly.c
414 while (pointer < end) {
415 yval = mCoords[pointer];
416 pointer += 2;
417 if (yval >= wherey) {
418 while ((pointer < end) && (mCoords[pointer] >= wherey)) pointer += 2;
419 if (pointer >= end) break;
420 if ((mCoords[pointer - 3] >= wherex) ==
421 (mCoords[pointer - 1] >= wherex)) {
422 intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
423 } else {
424 intersects +=
425 ((mCoords[pointer - 3] -
426 (mCoords[pointer - 2] - wherey) *
427 (mCoords[pointer - 1] - mCoords[pointer - 3]) /
428 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
430 : 0;
432 } else {
433 while ((pointer < end) && (mCoords[pointer] < wherey)) pointer += 2;
434 if (pointer >= end) break;
435 if ((mCoords[pointer - 3] >= wherex) ==
436 (mCoords[pointer - 1] >= wherex)) {
437 intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
438 } else {
439 intersects +=
440 ((mCoords[pointer - 3] -
441 (mCoords[pointer - 2] - wherey) *
442 (mCoords[pointer - 1] - mCoords[pointer - 3]) /
443 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
445 : 0;
449 if ((intersects & 1) != 0) {
450 return true;
453 return false;
456 void PolyArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
457 const ColorPattern& aColor,
458 const StrokeOptions& aStrokeOptions) {
459 if (mHasFocus) {
460 if (mNumCoords >= 6) {
461 // Where possible, we want all horizontal and vertical lines to align on
462 // pixel rows or columns, and to start at pixel boundaries so that one
463 // pixel dashing neatly sits on pixels to give us neat lines. To achieve
464 // that we draw each line segment as a separate path, snapping it to
465 // device pixels if applicable.
466 nsPresContext* pc = aFrame->PresContext();
467 Point p1(pc->CSSPixelsToDevPixels(mCoords[0]),
468 pc->CSSPixelsToDevPixels(mCoords[1]));
469 Point p2, p1snapped, p2snapped;
470 for (int32_t i = 2; i < mNumCoords; i += 2) {
471 p2.x = pc->CSSPixelsToDevPixels(mCoords[i]);
472 p2.y = pc->CSSPixelsToDevPixels(mCoords[i + 1]);
473 p1snapped = p1;
474 p2snapped = p2;
475 SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
476 aStrokeOptions.mLineWidth);
477 aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
478 p1 = p2;
480 p2.x = pc->CSSPixelsToDevPixels(mCoords[0]);
481 p2.y = pc->CSSPixelsToDevPixels(mCoords[1]);
482 p1snapped = p1;
483 p2snapped = p2;
484 SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
485 aStrokeOptions.mLineWidth);
486 aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
491 void PolyArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
492 if (mNumCoords >= 6) {
493 nscoord x1, x2, y1, y2, xtmp, ytmp;
494 x1 = x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
495 y1 = y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
496 for (int32_t i = 2; i < mNumCoords; i += 2) {
497 xtmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i]);
498 ytmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i + 1]);
499 x1 = x1 < xtmp ? x1 : xtmp;
500 y1 = y1 < ytmp ? y1 : ytmp;
501 x2 = x2 > xtmp ? x2 : xtmp;
502 y2 = y2 > ytmp ? y2 : ytmp;
505 aRect.SetRect(x1, y1, x2, y2);
509 //----------------------------------------------------------------------
511 class CircleArea final : public Area {
512 public:
513 explicit CircleArea(HTMLAreaElement* aArea);
515 virtual void ParseCoords(const nsAString& aSpec) override;
516 virtual bool IsInside(nscoord x, nscoord y) const override;
517 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
518 const ColorPattern& aColor,
519 const StrokeOptions& aStrokeOptions) override;
520 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
523 CircleArea::CircleArea(HTMLAreaElement* aArea) : Area(aArea) {}
525 void CircleArea::ParseCoords(const nsAString& aSpec) {
526 Area::ParseCoords(aSpec);
528 bool wrongNumberOfCoords = false;
529 int32_t flag = nsIScriptError::warningFlag;
530 if (mNumCoords >= 3) {
531 if (mCoords[2] < 0) {
532 logMessage(mArea, aSpec, nsIScriptError::errorFlag,
533 "ImageMapCircleNegativeRadius");
536 if (mNumCoords > 3) {
537 wrongNumberOfCoords = true;
539 } else {
540 wrongNumberOfCoords = true;
541 flag = nsIScriptError::errorFlag;
544 if (wrongNumberOfCoords) {
545 logMessage(mArea, aSpec, flag, "ImageMapCircleWrongNumberOfCoords");
549 bool CircleArea::IsInside(nscoord x, nscoord y) const {
550 // Note: > is for nav compatibility
551 if (mNumCoords >= 3) {
552 nscoord x1 = mCoords[0];
553 nscoord y1 = mCoords[1];
554 nscoord radius = mCoords[2];
555 if (radius < 0) {
556 return false;
558 nscoord dx = x1 - x;
559 nscoord dy = y1 - y;
560 nscoord dist = (dx * dx) + (dy * dy);
561 if (dist <= (radius * radius)) {
562 return true;
565 return false;
568 void CircleArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
569 const ColorPattern& aColor,
570 const StrokeOptions& aStrokeOptions) {
571 if (mHasFocus) {
572 if (mNumCoords >= 3) {
573 Point center(aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[0]),
574 aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[1]));
575 Float diameter =
576 2 * aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[2]);
577 if (diameter <= 0) {
578 return;
580 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
581 AppendEllipseToPath(builder, center, Size(diameter, diameter));
582 RefPtr<Path> circle = builder->Finish();
583 aDrawTarget.Stroke(circle, aColor, aStrokeOptions);
588 void CircleArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
589 if (mNumCoords >= 3) {
590 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
591 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
592 nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
593 if (radius < 0) {
594 return;
597 aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius);
601 //----------------------------------------------------------------------
603 nsImageMap::nsImageMap() : mImageFrame(nullptr), mConsiderWholeSubtree(false) {}
605 nsImageMap::~nsImageMap() {
606 NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called");
609 NS_IMPL_ISUPPORTS(nsImageMap, nsIMutationObserver, nsIDOMEventListener)
611 nsresult nsImageMap::GetBoundsForAreaContent(nsIContent* aContent,
612 nsRect& aBounds) {
613 NS_ENSURE_TRUE(aContent && mImageFrame, NS_ERROR_INVALID_ARG);
615 // Find the Area struct associated with this content node, and return bounds
616 for (auto& area : mAreas) {
617 if (area->mArea == aContent) {
618 aBounds = nsRect();
619 area->GetRect(mImageFrame, aBounds);
620 return NS_OK;
623 return NS_ERROR_FAILURE;
626 void nsImageMap::AreaRemoved(HTMLAreaElement* aArea) {
627 if (aArea->GetPrimaryFrame() == mImageFrame) {
628 aArea->SetPrimaryFrame(nullptr);
631 aArea->RemoveSystemEventListener(u"focus"_ns, this, false);
632 aArea->RemoveSystemEventListener(u"blur"_ns, this, false);
635 void nsImageMap::FreeAreas() {
636 for (UniquePtr<Area>& area : mAreas) {
637 AreaRemoved(area->mArea);
640 mAreas.Clear();
643 void nsImageMap::Init(nsImageFrame* aImageFrame, nsIContent* aMap) {
644 MOZ_ASSERT(aMap);
645 MOZ_ASSERT(aImageFrame);
647 mImageFrame = aImageFrame;
648 mMap = aMap;
649 mMap->AddMutationObserver(this);
651 // "Compile" the areas in the map into faster access versions
652 UpdateAreas();
655 void nsImageMap::SearchForAreas(nsIContent* aParent) {
656 // Look for <area> elements.
657 for (nsIContent* child = aParent->GetFirstChild(); child;
658 child = child->GetNextSibling()) {
659 if (auto* area = HTMLAreaElement::FromNode(child)) {
660 AddArea(area);
662 // Continue to next child. This stops mConsiderWholeSubtree from
663 // getting set. It also makes us ignore children of <area>s which
664 // is consistent with how we react to dynamic insertion of such
665 // children.
666 continue;
669 if (child->IsElement()) {
670 mConsiderWholeSubtree = true;
671 SearchForAreas(child);
676 void nsImageMap::UpdateAreas() {
677 // Get rid of old area data
678 FreeAreas();
680 mConsiderWholeSubtree = false;
681 SearchForAreas(mMap);
683 #ifdef ACCESSIBILITY
684 if (nsAccessibilityService* accService = GetAccService()) {
685 accService->UpdateImageMap(mImageFrame);
687 #endif
690 void nsImageMap::AddArea(HTMLAreaElement* aArea) {
691 static AttrArray::AttrValuesArray strings[] = {
692 nsGkAtoms::rect, nsGkAtoms::rectangle,
693 nsGkAtoms::circle, nsGkAtoms::circ,
694 nsGkAtoms::_default, nsGkAtoms::poly,
695 nsGkAtoms::polygon, nullptr};
697 UniquePtr<Area> area;
698 switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape, strings,
699 eIgnoreCase)) {
700 case AttrArray::ATTR_VALUE_NO_MATCH:
701 case AttrArray::ATTR_MISSING:
702 case 0:
703 case 1:
704 area = MakeUnique<RectArea>(aArea);
705 break;
706 case 2:
707 case 3:
708 area = MakeUnique<CircleArea>(aArea);
709 break;
710 case 4:
711 area = MakeUnique<DefaultArea>(aArea);
712 break;
713 case 5:
714 case 6:
715 area = MakeUnique<PolyArea>(aArea);
716 break;
717 default:
718 area = nullptr;
719 MOZ_ASSERT_UNREACHABLE("FindAttrValueIn returned an unexpected value.");
720 break;
723 // Add focus listener to track area focus changes
724 aArea->AddSystemEventListener(u"focus"_ns, this, false, false);
725 aArea->AddSystemEventListener(u"blur"_ns, this, false, false);
727 // This is a nasty hack. It needs to go away: see bug 135040. Once this is
728 // removed, the code added to RestyleManager::RestyleElement,
729 // nsCSSFrameConstructor::ContentRemoved (both hacks there), and
730 // RestyleManager::ProcessRestyledFrames to work around this issue can
731 // be removed.
732 aArea->SetPrimaryFrame(mImageFrame);
734 nsAutoString coords;
735 aArea->GetAttr(nsGkAtoms::coords, coords);
736 area->ParseCoords(coords);
737 mAreas.AppendElement(std::move(area));
740 HTMLAreaElement* nsImageMap::GetArea(const CSSIntPoint& aPt) const {
741 NS_ASSERTION(mMap, "Not initialized");
742 for (const auto& area : mAreas) {
743 if (area->IsInside(aPt.x, aPt.y)) {
744 return area->mArea;
748 return nullptr;
751 HTMLAreaElement* nsImageMap::GetAreaAt(uint32_t aIndex) const {
752 return mAreas.ElementAt(aIndex)->mArea;
755 void nsImageMap::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
756 const ColorPattern& aColor,
757 const StrokeOptions& aStrokeOptions) {
758 for (auto& area : mAreas) {
759 area->Draw(aFrame, aDrawTarget, aColor, aStrokeOptions);
763 void nsImageMap::MaybeUpdateAreas(nsIContent* aContent) {
764 if (aContent == mMap || mConsiderWholeSubtree) {
765 UpdateAreas();
769 void nsImageMap::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
770 nsAtom* aAttribute, int32_t aModType,
771 const nsAttrValue* aOldValue) {
772 // If the parent of the changing content node is our map then update
773 // the map. But only do this if the node is an HTML <area> or <a>
774 // and the attribute that's changing is "shape" or "coords" -- those
775 // are the only cases we care about.
776 if ((aElement->NodeInfo()->Equals(nsGkAtoms::area) ||
777 aElement->NodeInfo()->Equals(nsGkAtoms::a)) &&
778 aElement->IsHTMLElement() && aNameSpaceID == kNameSpaceID_None &&
779 (aAttribute == nsGkAtoms::shape || aAttribute == nsGkAtoms::coords)) {
780 MaybeUpdateAreas(aElement->GetParent());
781 } else if (aElement == mMap && aNameSpaceID == kNameSpaceID_None &&
782 (aAttribute == nsGkAtoms::name || aAttribute == nsGkAtoms::id) &&
783 mImageFrame) {
784 // ID or name has changed. Let ImageFrame recreate ImageMap.
785 mImageFrame->DisconnectMap();
789 void nsImageMap::ContentAppended(nsIContent* aFirstNewContent) {
790 MaybeUpdateAreas(aFirstNewContent->GetParent());
793 void nsImageMap::ContentInserted(nsIContent* aChild) {
794 MaybeUpdateAreas(aChild->GetParent());
797 static UniquePtr<Area> TakeArea(nsImageMap::AreaList& aAreas,
798 HTMLAreaElement* aArea) {
799 UniquePtr<Area> result;
800 size_t index = 0;
801 for (UniquePtr<Area>& area : aAreas) {
802 if (area->mArea == aArea) {
803 result = std::move(area);
804 break;
806 index++;
809 if (result) {
810 aAreas.RemoveElementAt(index);
813 return result;
816 void nsImageMap::ContentRemoved(nsIContent* aChild,
817 nsIContent* aPreviousSibling) {
818 if (aChild->GetParent() != mMap && !mConsiderWholeSubtree) {
819 return;
822 auto* areaElement = HTMLAreaElement::FromNode(aChild);
823 if (!areaElement) {
824 return;
827 UniquePtr<Area> area = TakeArea(mAreas, areaElement);
828 if (!area) {
829 return;
832 AreaRemoved(area->mArea);
834 #ifdef ACCESSIBILITY
835 if (nsAccessibilityService* accService = GetAccService()) {
836 accService->UpdateImageMap(mImageFrame);
838 #endif
841 void nsImageMap::ParentChainChanged(nsIContent* aContent) {
842 NS_ASSERTION(aContent == mMap, "Unexpected ParentChainChanged notification!");
843 if (mImageFrame) {
844 mImageFrame->DisconnectMap();
848 nsresult nsImageMap::HandleEvent(Event* aEvent) {
849 nsAutoString eventType;
850 aEvent->GetType(eventType);
851 bool focus = eventType.EqualsLiteral("focus");
852 MOZ_ASSERT(focus == !eventType.EqualsLiteral("blur"),
853 "Unexpected event type");
855 // Set which one of our areas changed focus
856 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(aEvent->GetTarget());
857 if (!targetContent) {
858 return NS_OK;
861 for (auto& area : mAreas) {
862 if (area->mArea == targetContent) {
863 // Set or Remove internal focus
864 area->HasFocus(focus);
865 // Now invalidate the rect
866 if (mImageFrame) {
867 mImageFrame->InvalidateFrame();
869 break;
872 return NS_OK;
875 void nsImageMap::Destroy() {
876 FreeAreas();
877 mImageFrame = nullptr;
878 mMap->RemoveMutationObserver(this);