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/. */
6 
7 /* code for HTML client-side image maps */
8 
9 #include "nsImageMap.h"
10 
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"
27 
28 #ifdef ACCESSIBILITY
29 #  include "nsAccessibilityService.h"
30 #endif
31 
32 using namespace mozilla;
33 using namespace mozilla::gfx;
34 using namespace mozilla::dom;
35 
36 class Area {
37  public:
38   explicit Area(HTMLAreaElement* aArea);
39   virtual ~Area();
40 
41   virtual void ParseCoords(const nsAString& aSpec);
42 
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;
48 
49   void HasFocus(bool aHasFocus);
50 
51   RefPtr<HTMLAreaElement> mArea;
52   UniquePtr<nscoord[]> mCoords;
53   int32_t mNumCoords;
54   bool mHasFocus;
55 };
56 
Area(HTMLAreaElement * aArea)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;
62 }
63 
~Area()64 Area::~Area() { MOZ_COUNT_DTOR(Area); }
65 
66 #include <stdlib.h>
67 
is_space(char c)68 inline bool is_space(char c) {
69   return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ||
70           c == '\v');
71 }
72 
logMessage(nsIContent * aContent,const nsAString & aCoordsSpec,int32_t aFlags,const char * aMessageName)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 */
82 }
83 
ParseCoords(const nsAString & aSpec)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;
90 
91     /*
92      * Nothing in an empty list
93      */
94     mNumCoords = 0;
95     mCoords = nullptr;
96     if (*cp == '\0') {
97       free(cp);
98       return;
99     }
100 
101     /*
102      * Skip beginning whitespace, all whitespace is empty list.
103      */
104     n_str = cp;
105     while (is_space(*n_str)) {
106       n_str++;
107     }
108     if (*n_str == '\0') {
109       free(cp);
110       return;
111     }
112 
113     /*
114      * Make a pass where any two numbers separated by just whitespace
115      * are given a comma separator.  Count entries while passing.
116      */
117     cnt = 0;
118     while (*n_str != '\0') {
119       bool has_comma;
120 
121       /*
122        * Skip to a separator
123        */
124       tptr = n_str;
125       while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') {
126         tptr++;
127       }
128       n_str = tptr;
129 
130       /*
131        * If no more entries, break out here
132        */
133       if (*n_str == '\0') {
134         break;
135       }
136 
137       /*
138        * Skip to the end of the separator, noting if we have a
139        * comma.
140        */
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;
148           }
149         }
150         tptr++;
151       }
152       /*
153        * If this was trailing whitespace we skipped, we are done.
154        */
155       if ((*tptr == '\0') && !has_comma) {
156         break;
157       }
158       /*
159        * Else if the separator is all whitespace, and this is not the
160        * end of the string, add a comma to the separator.
161        */
162       else if (!has_comma) {
163         *n_str = ',';
164       }
165 
166       /*
167        * count the entry skipped.
168        */
169       cnt++;
170 
171       n_str = tptr;
172     }
173     /*
174      * count the last entry in the list.
175      */
176     cnt++;
177 
178     /*
179      * Allocate space for the coordinate array.
180      */
181     UniquePtr<nscoord[]> value_list = MakeUnique<nscoord[]>(cnt);
182     if (!value_list) {
183       free(cp);
184       return;
185     }
186 
187     /*
188      * Second pass to copy integer values into list.
189      */
190     tptr = cp;
191     for (i = 0; i < cnt; i++) {
192       char* ptr;
193 
194       ptr = strchr(tptr, ',');
195       if (ptr) {
196         *ptr = '\0';
197       }
198       /*
199        * Strip whitespace in front of number because I don't
200        * trust atoi to do it on all platforms.
201        */
202       while (is_space(*tptr)) {
203         tptr++;
204       }
205       if (*tptr == '\0') {
206         value_list[i] = 0;
207       } else {
208         value_list[i] = (nscoord)::atoi(tptr);
209       }
210       if (ptr) {
211         *ptr = ',';
212         tptr = ptr + 1;
213       }
214     }
215 
216     mNumCoords = cnt;
217     mCoords = std::move(value_list);
218 
219     free(cp);
220   }
221 }
222 
HasFocus(bool aHasFocus)223 void Area::HasFocus(bool aHasFocus) { mHasFocus = aHasFocus; }
224 
225 //----------------------------------------------------------------------
226 
227 class DefaultArea final : public Area {
228  public:
229   explicit DefaultArea(HTMLAreaElement* aArea);
230 
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;
236 };
237 
DefaultArea(HTMLAreaElement * aArea)238 DefaultArea::DefaultArea(HTMLAreaElement* aArea) : Area(aArea) {}
239 
IsInside(nscoord x,nscoord y) const240 bool DefaultArea::IsInside(nscoord x, nscoord y) const { return true; }
241 
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)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);
253   }
254 }
255 
GetRect(nsIFrame * aFrame,nsRect & aRect)256 void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
257   aRect = aFrame->GetRect();
258   aRect.MoveTo(0, 0);
259 }
260 
261 //----------------------------------------------------------------------
262 
263 class RectArea final : public Area {
264  public:
265   explicit RectArea(HTMLAreaElement* aArea);
266 
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;
273 };
274 
RectArea(HTMLAreaElement * aArea)275 RectArea::RectArea(HTMLAreaElement* aArea) : Area(aArea) {}
276 
ParseCoords(const nsAString & aSpec)277 void RectArea::ParseCoords(const nsAString& aSpec) {
278   Area::ParseCoords(aSpec);
279 
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;
289     }
290 
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;
297     }
298 
299     if (mNumCoords > 4) {
300       // Someone missed the concept of a rect here
301       saneRect = false;
302     }
303   } else {
304     saneRect = false;
305     flag = nsIScriptError::errorFlag;
306   }
307 
308   if (!saneRect) {
309     logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError");
310   }
311 }
312 
IsInside(nscoord x,nscoord y) const313 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;
323     }
324   }
325   return false;
326 }
327 
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)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);
343     }
344   }
345 }
346 
GetRect(nsIFrame * aFrame,nsRect & aRect)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");
355 
356     aRect.SetRect(x1, y1, x2, y2);
357   }
358 }
359 
360 //----------------------------------------------------------------------
361 
362 class PolyArea final : public Area {
363  public:
364   explicit PolyArea(HTMLAreaElement* aArea);
365 
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;
372 };
373 
PolyArea(HTMLAreaElement * aArea)374 PolyArea::PolyArea(HTMLAreaElement* aArea) : Area(aArea) {}
375 
ParseCoords(const nsAString & aSpec)376 void PolyArea::ParseCoords(const nsAString& aSpec) {
377   Area::ParseCoords(aSpec);
378 
379   if (mNumCoords >= 2) {
380     if (mNumCoords & 1U) {
381       logMessage(mArea, aSpec, nsIScriptError::warningFlag,
382                  "ImageMapPolyOddNumberOfCoords");
383     }
384   } else {
385     logMessage(mArea, aSpec, nsIScriptError::errorFlag,
386                "ImageMapPolyWrongNumberOfCoords");
387   }
388 }
389 
IsInside(nscoord x,nscoord y) const390 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;
401 
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)
408                           ? 1
409                           : 0;
410       }
411     }
412 
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)
429                   ? 1
430                   : 0;
431         }
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)
444                   ? 1
445                   : 0;
446         }
447       }
448     }
449     if ((intersects & 1) != 0) {
450       return true;
451     }
452   }
453   return false;
454 }
455 
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)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;
479       }
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);
487     }
488   }
489 }
490 
GetRect(nsIFrame * aFrame,nsRect & aRect)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;
503     }
504 
505     aRect.SetRect(x1, y1, x2, y2);
506   }
507 }
508 
509 //----------------------------------------------------------------------
510 
511 class CircleArea final : public Area {
512  public:
513   explicit CircleArea(HTMLAreaElement* aArea);
514 
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;
521 };
522 
CircleArea(HTMLAreaElement * aArea)523 CircleArea::CircleArea(HTMLAreaElement* aArea) : Area(aArea) {}
524 
ParseCoords(const nsAString & aSpec)525 void CircleArea::ParseCoords(const nsAString& aSpec) {
526   Area::ParseCoords(aSpec);
527 
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");
534     }
535 
536     if (mNumCoords > 3) {
537       wrongNumberOfCoords = true;
538     }
539   } else {
540     wrongNumberOfCoords = true;
541     flag = nsIScriptError::errorFlag;
542   }
543 
544   if (wrongNumberOfCoords) {
545     logMessage(mArea, aSpec, flag, "ImageMapCircleWrongNumberOfCoords");
546   }
547 }
548 
IsInside(nscoord x,nscoord y) const549 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;
557     }
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;
563     }
564   }
565   return false;
566 }
567 
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)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;
579       }
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);
584     }
585   }
586 }
587 
GetRect(nsIFrame * aFrame,nsRect & aRect)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;
595     }
596 
597     aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius);
598   }
599 }
600 
601 //----------------------------------------------------------------------
602 
nsImageMap()603 nsImageMap::nsImageMap() : mImageFrame(nullptr), mConsiderWholeSubtree(false) {}
604 
~nsImageMap()605 nsImageMap::~nsImageMap() {
606   NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called");
607 }
608 
NS_IMPL_ISUPPORTS(nsImageMap,nsIMutationObserver,nsIDOMEventListener)609 NS_IMPL_ISUPPORTS(nsImageMap, nsIMutationObserver, nsIDOMEventListener)
610 
611 nsresult nsImageMap::GetBoundsForAreaContent(nsIContent* aContent,
612                                              nsRect& aBounds) {
613   NS_ENSURE_TRUE(aContent && mImageFrame, NS_ERROR_INVALID_ARG);
614 
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;
621     }
622   }
623   return NS_ERROR_FAILURE;
624 }
625 
AreaRemoved(HTMLAreaElement * aArea)626 void nsImageMap::AreaRemoved(HTMLAreaElement* aArea) {
627   if (aArea->GetPrimaryFrame() == mImageFrame) {
628     aArea->SetPrimaryFrame(nullptr);
629   }
630 
631   aArea->RemoveSystemEventListener(u"focus"_ns, this, false);
632   aArea->RemoveSystemEventListener(u"blur"_ns, this, false);
633 }
634 
FreeAreas()635 void nsImageMap::FreeAreas() {
636   for (UniquePtr<Area>& area : mAreas) {
637     AreaRemoved(area->mArea);
638   }
639 
640   mAreas.Clear();
641 }
642 
Init(nsImageFrame * aImageFrame,nsIContent * aMap)643 void nsImageMap::Init(nsImageFrame* aImageFrame, nsIContent* aMap) {
644   MOZ_ASSERT(aMap);
645   MOZ_ASSERT(aImageFrame);
646 
647   mImageFrame = aImageFrame;
648   mMap = aMap;
649   mMap->AddMutationObserver(this);
650 
651   // "Compile" the areas in the map into faster access versions
652   UpdateAreas();
653 }
654 
SearchForAreas(nsIContent * aParent)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);
661 
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;
667     }
668 
669     if (child->IsElement()) {
670       mConsiderWholeSubtree = true;
671       SearchForAreas(child);
672     }
673   }
674 }
675 
UpdateAreas()676 void nsImageMap::UpdateAreas() {
677   // Get rid of old area data
678   FreeAreas();
679 
680   mConsiderWholeSubtree = false;
681   SearchForAreas(mMap);
682 
683 #ifdef ACCESSIBILITY
684   if (nsAccessibilityService* accService = GetAccService()) {
685     accService->UpdateImageMap(mImageFrame);
686   }
687 #endif
688 }
689 
AddArea(HTMLAreaElement * aArea)690 void nsImageMap::AddArea(HTMLAreaElement* aArea) {
691   static Element::AttrValuesArray strings[] = {
692       nsGkAtoms::rect,     nsGkAtoms::rectangle,
693       nsGkAtoms::circle,   nsGkAtoms::circ,
694       nsGkAtoms::_default, nsGkAtoms::poly,
695       nsGkAtoms::polygon,  nullptr};
696 
697   UniquePtr<Area> area;
698   switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape, strings,
699                                  eIgnoreCase)) {
700     case Element::ATTR_VALUE_NO_MATCH:
701     case Element::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;
721   }
722 
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);
726 
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);
733 
734   nsAutoString coords;
735   aArea->GetAttr(kNameSpaceID_None, nsGkAtoms::coords, coords);
736   area->ParseCoords(coords);
737   mAreas.AppendElement(std::move(area));
738 }
739 
GetArea(const CSSIntPoint & aPt) const740 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;
745     }
746   }
747 
748   return nullptr;
749 }
750 
GetAreaAt(uint32_t aIndex) const751 HTMLAreaElement* nsImageMap::GetAreaAt(uint32_t aIndex) const {
752   return mAreas.ElementAt(aIndex)->mArea;
753 }
754 
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)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);
760   }
761 }
762 
MaybeUpdateAreas(nsIContent * aContent)763 void nsImageMap::MaybeUpdateAreas(nsIContent* aContent) {
764   if (aContent == mMap || mConsiderWholeSubtree) {
765     UpdateAreas();
766   }
767 }
768 
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)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();
786   }
787 }
788 
ContentAppended(nsIContent * aFirstNewContent)789 void nsImageMap::ContentAppended(nsIContent* aFirstNewContent) {
790   MaybeUpdateAreas(aFirstNewContent->GetParent());
791 }
792 
ContentInserted(nsIContent * aChild)793 void nsImageMap::ContentInserted(nsIContent* aChild) {
794   MaybeUpdateAreas(aChild->GetParent());
795 }
796 
TakeArea(nsImageMap::AreaList & aAreas,HTMLAreaElement * aArea)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;
805     }
806     index++;
807   }
808 
809   if (result) {
810     aAreas.RemoveElementAt(index);
811   }
812 
813   return result;
814 }
815 
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)816 void nsImageMap::ContentRemoved(nsIContent* aChild,
817                                 nsIContent* aPreviousSibling) {
818   if (aChild->GetParent() != mMap && !mConsiderWholeSubtree) {
819     return;
820   }
821 
822   auto* areaElement = HTMLAreaElement::FromNode(aChild);
823   if (!areaElement) {
824     return;
825   }
826 
827   UniquePtr<Area> area = TakeArea(mAreas, areaElement);
828   if (!area) {
829     return;
830   }
831 
832   AreaRemoved(area->mArea);
833 
834 #ifdef ACCESSIBILITY
835   if (nsAccessibilityService* accService = GetAccService()) {
836     accService->UpdateImageMap(mImageFrame);
837   }
838 #endif
839 }
840 
ParentChainChanged(nsIContent * aContent)841 void nsImageMap::ParentChainChanged(nsIContent* aContent) {
842   NS_ASSERTION(aContent == mMap, "Unexpected ParentChainChanged notification!");
843   if (mImageFrame) {
844     mImageFrame->DisconnectMap();
845   }
846 }
847 
HandleEvent(Event * aEvent)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");
854 
855   // Set which one of our areas changed focus
856   nsCOMPtr<nsIContent> targetContent = do_QueryInterface(aEvent->GetTarget());
857   if (!targetContent) {
858     return NS_OK;
859   }
860 
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();
868       }
869       break;
870     }
871   }
872   return NS_OK;
873 }
874 
Destroy()875 void nsImageMap::Destroy() {
876   FreeAreas();
877   mImageFrame = nullptr;
878   mMap->RemoveMutationObserver(this);
879 }
880