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