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 nsIDOMEvent::InternalDOMEvent()
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 "nsIStringBundle.h"
26 #include "nsContentUtils.h"
27 #include "ImageLayers.h"
28
29 #ifdef ACCESSIBILITY
30 #include "nsAccessibilityService.h"
31 #endif
32
33 using namespace mozilla;
34 using namespace mozilla::gfx;
35 using namespace mozilla::dom;
36
37 class Area {
38 public:
39 explicit Area(HTMLAreaElement* aArea);
40 virtual ~Area();
41
42 virtual void ParseCoords(const nsAString& aSpec);
43
44 virtual bool IsInside(nscoord x, nscoord y) const = 0;
45 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
46 const ColorPattern& aColor,
47 const StrokeOptions& aStrokeOptions) = 0;
48 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) = 0;
49
50 void HasFocus(bool aHasFocus);
51
52 RefPtr<HTMLAreaElement> mArea;
53 UniquePtr<nscoord[]> mCoords;
54 int32_t mNumCoords;
55 bool mHasFocus;
56 };
57
Area(HTMLAreaElement * aArea)58 Area::Area(HTMLAreaElement* aArea) : mArea(aArea) {
59 MOZ_COUNT_CTOR(Area);
60 MOZ_ASSERT(mArea, "How did that happen?");
61 mNumCoords = 0;
62 mHasFocus = false;
63 }
64
~Area()65 Area::~Area() { MOZ_COUNT_DTOR(Area); }
66
67 #include <stdlib.h>
68
is_space(char c)69 inline bool is_space(char c) {
70 return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ||
71 c == '\v');
72 }
73
logMessage(nsIContent * aContent,const nsAString & aCoordsSpec,int32_t aFlags,const char * aMessageName)74 static void logMessage(nsIContent* aContent, const nsAString& aCoordsSpec,
75 int32_t aFlags, const char* aMessageName) {
76 nsIDocument* doc = aContent->OwnerDoc();
77
78 nsContentUtils::ReportToConsole(
79 aFlags, NS_LITERAL_CSTRING("Layout: ImageMap"), doc,
80 nsContentUtils::eLAYOUT_PROPERTIES, aMessageName, nullptr, /* params */
81 0, /* params length */
82 nullptr,
83 PromiseFlatString(NS_LITERAL_STRING("coords=\"") + aCoordsSpec +
84 NS_LITERAL_STRING("\""))); /* source line */
85 }
86
ParseCoords(const nsAString & aSpec)87 void Area::ParseCoords(const nsAString& aSpec) {
88 char* cp = ToNewCString(aSpec);
89 if (cp) {
90 char* tptr;
91 char* n_str;
92 int32_t i, cnt;
93
94 /*
95 * Nothing in an empty list
96 */
97 mNumCoords = 0;
98 mCoords = nullptr;
99 if (*cp == '\0') {
100 free(cp);
101 return;
102 }
103
104 /*
105 * Skip beginning whitespace, all whitespace is empty list.
106 */
107 n_str = cp;
108 while (is_space(*n_str)) {
109 n_str++;
110 }
111 if (*n_str == '\0') {
112 free(cp);
113 return;
114 }
115
116 /*
117 * Make a pass where any two numbers separated by just whitespace
118 * are given a comma separator. Count entries while passing.
119 */
120 cnt = 0;
121 while (*n_str != '\0') {
122 bool has_comma;
123
124 /*
125 * Skip to a separator
126 */
127 tptr = n_str;
128 while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0') {
129 tptr++;
130 }
131 n_str = tptr;
132
133 /*
134 * If no more entries, break out here
135 */
136 if (*n_str == '\0') {
137 break;
138 }
139
140 /*
141 * Skip to the end of the separator, noting if we have a
142 * comma.
143 */
144 has_comma = false;
145 while (is_space(*tptr) || *tptr == ',') {
146 if (*tptr == ',') {
147 if (!has_comma) {
148 has_comma = true;
149 } else {
150 break;
151 }
152 }
153 tptr++;
154 }
155 /*
156 * If this was trailing whitespace we skipped, we are done.
157 */
158 if ((*tptr == '\0') && !has_comma) {
159 break;
160 }
161 /*
162 * Else if the separator is all whitespace, and this is not the
163 * end of the string, add a comma to the separator.
164 */
165 else if (!has_comma) {
166 *n_str = ',';
167 }
168
169 /*
170 * count the entry skipped.
171 */
172 cnt++;
173
174 n_str = tptr;
175 }
176 /*
177 * count the last entry in the list.
178 */
179 cnt++;
180
181 /*
182 * Allocate space for the coordinate array.
183 */
184 UniquePtr<nscoord[]> value_list = MakeUnique<nscoord[]>(cnt);
185 if (!value_list) {
186 free(cp);
187 return;
188 }
189
190 /*
191 * Second pass to copy integer values into list.
192 */
193 tptr = cp;
194 for (i = 0; i < cnt; i++) {
195 char* ptr;
196
197 ptr = strchr(tptr, ',');
198 if (ptr) {
199 *ptr = '\0';
200 }
201 /*
202 * Strip whitespace in front of number because I don't
203 * trust atoi to do it on all platforms.
204 */
205 while (is_space(*tptr)) {
206 tptr++;
207 }
208 if (*tptr == '\0') {
209 value_list[i] = 0;
210 } else {
211 value_list[i] = (nscoord)::atoi(tptr);
212 }
213 if (ptr) {
214 *ptr = ',';
215 tptr = ptr + 1;
216 }
217 }
218
219 mNumCoords = cnt;
220 mCoords = Move(value_list);
221
222 free(cp);
223 }
224 }
225
HasFocus(bool aHasFocus)226 void Area::HasFocus(bool aHasFocus) { mHasFocus = aHasFocus; }
227
228 //----------------------------------------------------------------------
229
230 class DefaultArea : public Area {
231 public:
232 explicit DefaultArea(HTMLAreaElement* aArea);
233
234 virtual bool IsInside(nscoord x, nscoord y) const override;
235 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
236 const ColorPattern& aColor,
237 const StrokeOptions& aStrokeOptions) override;
238 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
239 };
240
DefaultArea(HTMLAreaElement * aArea)241 DefaultArea::DefaultArea(HTMLAreaElement* aArea) : Area(aArea) {}
242
IsInside(nscoord x,nscoord y) const243 bool DefaultArea::IsInside(nscoord x, nscoord y) const { return true; }
244
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)245 void DefaultArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
246 const ColorPattern& aColor,
247 const StrokeOptions& aStrokeOptions) {
248 if (mHasFocus) {
249 nsRect r(nsPoint(0, 0), aFrame->GetSize());
250 const nscoord kOnePixel = nsPresContext::CSSPixelsToAppUnits(1);
251 r.width -= kOnePixel;
252 r.height -= kOnePixel;
253 Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
254 r, aFrame->PresContext()->AppUnitsPerDevPixel()));
255 StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
256 }
257 }
258
GetRect(nsIFrame * aFrame,nsRect & aRect)259 void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
260 aRect = aFrame->GetRect();
261 aRect.MoveTo(0, 0);
262 }
263
264 //----------------------------------------------------------------------
265
266 class RectArea : public Area {
267 public:
268 explicit RectArea(HTMLAreaElement* aArea);
269
270 virtual void ParseCoords(const nsAString& aSpec) override;
271 virtual bool IsInside(nscoord x, nscoord y) const override;
272 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
273 const ColorPattern& aColor,
274 const StrokeOptions& aStrokeOptions) override;
275 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
276 };
277
RectArea(HTMLAreaElement * aArea)278 RectArea::RectArea(HTMLAreaElement* aArea) : Area(aArea) {}
279
ParseCoords(const nsAString & aSpec)280 void RectArea::ParseCoords(const nsAString& aSpec) {
281 Area::ParseCoords(aSpec);
282
283 bool saneRect = true;
284 int32_t flag = nsIScriptError::warningFlag;
285 if (mNumCoords >= 4) {
286 if (mCoords[0] > mCoords[2]) {
287 // x-coords in reversed order
288 nscoord x = mCoords[2];
289 mCoords[2] = mCoords[0];
290 mCoords[0] = x;
291 saneRect = false;
292 }
293
294 if (mCoords[1] > mCoords[3]) {
295 // y-coords in reversed order
296 nscoord y = mCoords[3];
297 mCoords[3] = mCoords[1];
298 mCoords[1] = y;
299 saneRect = false;
300 }
301
302 if (mNumCoords > 4) {
303 // Someone missed the concept of a rect here
304 saneRect = false;
305 }
306 } else {
307 saneRect = false;
308 flag = nsIScriptError::errorFlag;
309 }
310
311 if (!saneRect) {
312 logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError");
313 }
314 }
315
IsInside(nscoord x,nscoord y) const316 bool RectArea::IsInside(nscoord x, nscoord y) const {
317 if (mNumCoords >= 4) { // Note: > is for nav compatibility
318 nscoord x1 = mCoords[0];
319 nscoord y1 = mCoords[1];
320 nscoord x2 = mCoords[2];
321 nscoord y2 = mCoords[3];
322 NS_ASSERTION(x1 <= x2 && y1 <= y2,
323 "Someone screwed up RectArea::ParseCoords");
324 if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) {
325 return true;
326 }
327 }
328 return false;
329 }
330
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)331 void RectArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
332 const ColorPattern& aColor,
333 const StrokeOptions& aStrokeOptions) {
334 if (mHasFocus) {
335 if (mNumCoords >= 4) {
336 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
337 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
338 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
339 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
340 NS_ASSERTION(x1 <= x2 && y1 <= y2,
341 "Someone screwed up RectArea::ParseCoords");
342 nsRect r(x1, y1, x2 - x1, y2 - y1);
343 Rect rect = ToRect(nsLayoutUtils::RectToGfxRect(
344 r, aFrame->PresContext()->AppUnitsPerDevPixel()));
345 StrokeSnappedEdgesOfRect(rect, aDrawTarget, aColor, aStrokeOptions);
346 }
347 }
348 }
349
GetRect(nsIFrame * aFrame,nsRect & aRect)350 void RectArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
351 if (mNumCoords >= 4) {
352 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
353 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
354 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
355 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
356 NS_ASSERTION(x1 <= x2 && y1 <= y2,
357 "Someone screwed up RectArea::ParseCoords");
358
359 aRect.SetRect(x1, y1, x2, y2);
360 }
361 }
362
363 //----------------------------------------------------------------------
364
365 class PolyArea : public Area {
366 public:
367 explicit PolyArea(HTMLAreaElement* aArea);
368
369 virtual void ParseCoords(const nsAString& aSpec) override;
370 virtual bool IsInside(nscoord x, nscoord y) const override;
371 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
372 const ColorPattern& aColor,
373 const StrokeOptions& aStrokeOptions) override;
374 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
375 };
376
PolyArea(HTMLAreaElement * aArea)377 PolyArea::PolyArea(HTMLAreaElement* aArea) : Area(aArea) {}
378
ParseCoords(const nsAString & aSpec)379 void PolyArea::ParseCoords(const nsAString& aSpec) {
380 Area::ParseCoords(aSpec);
381
382 if (mNumCoords >= 2) {
383 if (mNumCoords & 1U) {
384 logMessage(mArea, aSpec, nsIScriptError::warningFlag,
385 "ImageMapPolyOddNumberOfCoords");
386 }
387 } else {
388 logMessage(mArea, aSpec, nsIScriptError::errorFlag,
389 "ImageMapPolyWrongNumberOfCoords");
390 }
391 }
392
IsInside(nscoord x,nscoord y) const393 bool PolyArea::IsInside(nscoord x, nscoord y) const {
394 if (mNumCoords >= 6) {
395 int32_t intersects = 0;
396 nscoord wherex = x;
397 nscoord wherey = y;
398 int32_t totalv = mNumCoords / 2;
399 int32_t totalc = totalv * 2;
400 nscoord xval = mCoords[totalc - 2];
401 nscoord yval = mCoords[totalc - 1];
402 int32_t end = totalc;
403 int32_t pointer = 1;
404
405 if ((yval >= wherey) != (mCoords[pointer] >= wherey)) {
406 if ((xval >= wherex) == (mCoords[0] >= wherex)) {
407 intersects += (xval >= wherex) ? 1 : 0;
408 } else {
409 intersects += ((xval - (yval - wherey) * (mCoords[0] - xval) /
410 (mCoords[pointer] - yval)) >= wherex)
411 ? 1
412 : 0;
413 }
414 }
415
416 // XXX I wonder what this is doing; this is a translation of ptinpoly.c
417 while (pointer < end) {
418 yval = mCoords[pointer];
419 pointer += 2;
420 if (yval >= wherey) {
421 while ((pointer < end) && (mCoords[pointer] >= wherey)) pointer += 2;
422 if (pointer >= end) break;
423 if ((mCoords[pointer - 3] >= wherex) ==
424 (mCoords[pointer - 1] >= wherex)) {
425 intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
426 } else {
427 intersects +=
428 ((mCoords[pointer - 3] -
429 (mCoords[pointer - 2] - wherey) *
430 (mCoords[pointer - 1] - mCoords[pointer - 3]) /
431 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
432 ? 1
433 : 0;
434 }
435 } else {
436 while ((pointer < end) && (mCoords[pointer] < wherey)) pointer += 2;
437 if (pointer >= end) break;
438 if ((mCoords[pointer - 3] >= wherex) ==
439 (mCoords[pointer - 1] >= wherex)) {
440 intersects += (mCoords[pointer - 3] >= wherex) ? 1 : 0;
441 } else {
442 intersects +=
443 ((mCoords[pointer - 3] -
444 (mCoords[pointer - 2] - wherey) *
445 (mCoords[pointer - 1] - mCoords[pointer - 3]) /
446 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex)
447 ? 1
448 : 0;
449 }
450 }
451 }
452 if ((intersects & 1) != 0) {
453 return true;
454 }
455 }
456 return false;
457 }
458
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)459 void PolyArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
460 const ColorPattern& aColor,
461 const StrokeOptions& aStrokeOptions) {
462 if (mHasFocus) {
463 if (mNumCoords >= 6) {
464 // Where possible, we want all horizontal and vertical lines to align on
465 // pixel rows or columns, and to start at pixel boundaries so that one
466 // pixel dashing neatly sits on pixels to give us neat lines. To achieve
467 // that we draw each line segment as a separate path, snapping it to
468 // device pixels if applicable.
469 nsPresContext* pc = aFrame->PresContext();
470 Point p1(pc->CSSPixelsToDevPixels(mCoords[0]),
471 pc->CSSPixelsToDevPixels(mCoords[1]));
472 Point p2, p1snapped, p2snapped;
473 for (int32_t i = 2; i < mNumCoords; i += 2) {
474 p2.x = pc->CSSPixelsToDevPixels(mCoords[i]);
475 p2.y = pc->CSSPixelsToDevPixels(mCoords[i + 1]);
476 p1snapped = p1;
477 p2snapped = p2;
478 SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
479 aStrokeOptions.mLineWidth);
480 aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
481 p1 = p2;
482 }
483 p2.x = pc->CSSPixelsToDevPixels(mCoords[0]);
484 p2.y = pc->CSSPixelsToDevPixels(mCoords[1]);
485 p1snapped = p1;
486 p2snapped = p2;
487 SnapLineToDevicePixelsForStroking(p1snapped, p2snapped, aDrawTarget,
488 aStrokeOptions.mLineWidth);
489 aDrawTarget.StrokeLine(p1snapped, p2snapped, aColor, aStrokeOptions);
490 }
491 }
492 }
493
GetRect(nsIFrame * aFrame,nsRect & aRect)494 void PolyArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
495 if (mNumCoords >= 6) {
496 nscoord x1, x2, y1, y2, xtmp, ytmp;
497 x1 = x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
498 y1 = y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
499 for (int32_t i = 2; i < mNumCoords; i += 2) {
500 xtmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i]);
501 ytmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i + 1]);
502 x1 = x1 < xtmp ? x1 : xtmp;
503 y1 = y1 < ytmp ? y1 : ytmp;
504 x2 = x2 > xtmp ? x2 : xtmp;
505 y2 = y2 > ytmp ? y2 : ytmp;
506 }
507
508 aRect.SetRect(x1, y1, x2, y2);
509 }
510 }
511
512 //----------------------------------------------------------------------
513
514 class CircleArea : public Area {
515 public:
516 explicit CircleArea(HTMLAreaElement* aArea);
517
518 virtual void ParseCoords(const nsAString& aSpec) override;
519 virtual bool IsInside(nscoord x, nscoord y) const override;
520 virtual void Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
521 const ColorPattern& aColor,
522 const StrokeOptions& aStrokeOptions) override;
523 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) override;
524 };
525
CircleArea(HTMLAreaElement * aArea)526 CircleArea::CircleArea(HTMLAreaElement* aArea) : Area(aArea) {}
527
ParseCoords(const nsAString & aSpec)528 void CircleArea::ParseCoords(const nsAString& aSpec) {
529 Area::ParseCoords(aSpec);
530
531 bool wrongNumberOfCoords = false;
532 int32_t flag = nsIScriptError::warningFlag;
533 if (mNumCoords >= 3) {
534 if (mCoords[2] < 0) {
535 logMessage(mArea, aSpec, nsIScriptError::errorFlag,
536 "ImageMapCircleNegativeRadius");
537 }
538
539 if (mNumCoords > 3) {
540 wrongNumberOfCoords = true;
541 }
542 } else {
543 wrongNumberOfCoords = true;
544 flag = nsIScriptError::errorFlag;
545 }
546
547 if (wrongNumberOfCoords) {
548 logMessage(mArea, aSpec, flag, "ImageMapCircleWrongNumberOfCoords");
549 }
550 }
551
IsInside(nscoord x,nscoord y) const552 bool CircleArea::IsInside(nscoord x, nscoord y) const {
553 // Note: > is for nav compatibility
554 if (mNumCoords >= 3) {
555 nscoord x1 = mCoords[0];
556 nscoord y1 = mCoords[1];
557 nscoord radius = mCoords[2];
558 if (radius < 0) {
559 return false;
560 }
561 nscoord dx = x1 - x;
562 nscoord dy = y1 - y;
563 nscoord dist = (dx * dx) + (dy * dy);
564 if (dist <= (radius * radius)) {
565 return true;
566 }
567 }
568 return false;
569 }
570
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)571 void CircleArea::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
572 const ColorPattern& aColor,
573 const StrokeOptions& aStrokeOptions) {
574 if (mHasFocus) {
575 if (mNumCoords >= 3) {
576 Point center(aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[0]),
577 aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[1]));
578 Float diameter =
579 2 * aFrame->PresContext()->CSSPixelsToDevPixels(mCoords[2]);
580 if (diameter <= 0) {
581 return;
582 }
583 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
584 AppendEllipseToPath(builder, center, Size(diameter, diameter));
585 RefPtr<Path> circle = builder->Finish();
586 aDrawTarget.Stroke(circle, aColor, aStrokeOptions);
587 }
588 }
589 }
590
GetRect(nsIFrame * aFrame,nsRect & aRect)591 void CircleArea::GetRect(nsIFrame* aFrame, nsRect& aRect) {
592 if (mNumCoords >= 3) {
593 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
594 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
595 nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
596 if (radius < 0) {
597 return;
598 }
599
600 aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius);
601 }
602 }
603
604 //----------------------------------------------------------------------
605
nsImageMap()606 nsImageMap::nsImageMap() : mImageFrame(nullptr), mConsiderWholeSubtree(false) {}
607
~nsImageMap()608 nsImageMap::~nsImageMap() {
609 NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called");
610 }
611
NS_IMPL_ISUPPORTS(nsImageMap,nsIMutationObserver,nsIDOMEventListener)612 NS_IMPL_ISUPPORTS(nsImageMap, nsIMutationObserver, nsIDOMEventListener)
613
614 nsresult nsImageMap::GetBoundsForAreaContent(nsIContent* aContent,
615 nsRect& aBounds) {
616 NS_ENSURE_TRUE(aContent && mImageFrame, NS_ERROR_INVALID_ARG);
617
618 // Find the Area struct associated with this content node, and return bounds
619 for (auto& area : mAreas) {
620 if (area->mArea == aContent) {
621 aBounds = nsRect();
622 area->GetRect(mImageFrame, aBounds);
623 return NS_OK;
624 }
625 }
626 return NS_ERROR_FAILURE;
627 }
628
AreaRemoved(HTMLAreaElement * aArea)629 void nsImageMap::AreaRemoved(HTMLAreaElement* aArea) {
630 if (aArea->IsInUncomposedDoc()) {
631 NS_ASSERTION(aArea->GetPrimaryFrame() == mImageFrame,
632 "Unexpected primary frame");
633
634 aArea->SetPrimaryFrame(nullptr);
635 }
636
637 aArea->RemoveSystemEventListener(NS_LITERAL_STRING("focus"), this, false);
638 aArea->RemoveSystemEventListener(NS_LITERAL_STRING("blur"), this, false);
639 }
640
FreeAreas()641 void nsImageMap::FreeAreas() {
642 for (UniquePtr<Area>& area : mAreas) {
643 AreaRemoved(area->mArea);
644 }
645
646 mAreas.Clear();
647 }
648
Init(nsImageFrame * aImageFrame,nsIContent * aMap)649 void nsImageMap::Init(nsImageFrame* aImageFrame, nsIContent* aMap) {
650 MOZ_ASSERT(aMap);
651 MOZ_ASSERT(aImageFrame);
652
653 mImageFrame = aImageFrame;
654 mMap = aMap;
655 mMap->AddMutationObserver(this);
656
657 // "Compile" the areas in the map into faster access versions
658 UpdateAreas();
659 }
660
SearchForAreas(nsIContent * aParent)661 void nsImageMap::SearchForAreas(nsIContent* aParent) {
662 // Look for <area> elements.
663 for (nsIContent* child = aParent->GetFirstChild(); child;
664 child = child->GetNextSibling()) {
665 if (auto* area = HTMLAreaElement::FromContent(child)) {
666 AddArea(area);
667
668 // Continue to next child. This stops mConsiderWholeSubtree from
669 // getting set. It also makes us ignore children of <area>s which
670 // is consistent with how we react to dynamic insertion of such
671 // children.
672 continue;
673 }
674
675 if (child->IsElement()) {
676 mConsiderWholeSubtree = true;
677 SearchForAreas(child);
678 }
679 }
680 }
681
UpdateAreas()682 void nsImageMap::UpdateAreas() {
683 // Get rid of old area data
684 FreeAreas();
685
686 mConsiderWholeSubtree = false;
687 SearchForAreas(mMap);
688
689 #ifdef ACCESSIBILITY
690 if (nsAccessibilityService* accService = GetAccService()) {
691 accService->UpdateImageMap(mImageFrame);
692 }
693 #endif
694 }
695
AddArea(HTMLAreaElement * aArea)696 void nsImageMap::AddArea(HTMLAreaElement* aArea) {
697 static Element::AttrValuesArray strings[] = {
698 &nsGkAtoms::rect, &nsGkAtoms::rectangle,
699 &nsGkAtoms::circle, &nsGkAtoms::circ,
700 &nsGkAtoms::_default, &nsGkAtoms::poly,
701 &nsGkAtoms::polygon, nullptr};
702
703 UniquePtr<Area> area;
704 switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape, strings,
705 eIgnoreCase)) {
706 case Element::ATTR_VALUE_NO_MATCH:
707 case Element::ATTR_MISSING:
708 case 0:
709 case 1:
710 area = MakeUnique<RectArea>(aArea);
711 break;
712 case 2:
713 case 3:
714 area = MakeUnique<CircleArea>(aArea);
715 break;
716 case 4:
717 area = MakeUnique<DefaultArea>(aArea);
718 break;
719 case 5:
720 case 6:
721 area = MakeUnique<PolyArea>(aArea);
722 break;
723 default:
724 area = nullptr;
725 MOZ_ASSERT_UNREACHABLE("FindAttrValueIn returned an unexpected value.");
726 break;
727 }
728
729 // Add focus listener to track area focus changes
730 aArea->AddSystemEventListener(NS_LITERAL_STRING("focus"), this, false, false);
731 aArea->AddSystemEventListener(NS_LITERAL_STRING("blur"), this, false, false);
732
733 // This is a nasty hack. It needs to go away: see bug 135040. Once this is
734 // removed, the code added to RestyleManager::RestyleElement,
735 // nsCSSFrameConstructor::ContentRemoved (both hacks there), and
736 // RestyleManager::ProcessRestyledFrames to work around this issue can
737 // be removed.
738 aArea->SetPrimaryFrame(mImageFrame);
739
740 nsAutoString coords;
741 aArea->GetAttr(kNameSpaceID_None, nsGkAtoms::coords, coords);
742 area->ParseCoords(coords);
743 mAreas.AppendElement(Move(area));
744 }
745
GetArea(nscoord aX,nscoord aY) const746 nsIContent* nsImageMap::GetArea(nscoord aX, nscoord aY) const {
747 NS_ASSERTION(mMap, "Not initialized");
748 for (const auto& area : mAreas) {
749 if (area->IsInside(aX, aY)) {
750 return area->mArea;
751 }
752 }
753
754 return nullptr;
755 }
756
GetAreaAt(uint32_t aIndex) const757 nsIContent* nsImageMap::GetAreaAt(uint32_t aIndex) const {
758 return mAreas.ElementAt(aIndex)->mArea;
759 }
760
Draw(nsIFrame * aFrame,DrawTarget & aDrawTarget,const ColorPattern & aColor,const StrokeOptions & aStrokeOptions)761 void nsImageMap::Draw(nsIFrame* aFrame, DrawTarget& aDrawTarget,
762 const ColorPattern& aColor,
763 const StrokeOptions& aStrokeOptions) {
764 for (auto& area : mAreas) {
765 area->Draw(aFrame, aDrawTarget, aColor, aStrokeOptions);
766 }
767 }
768
MaybeUpdateAreas(nsIContent * aContent)769 void nsImageMap::MaybeUpdateAreas(nsIContent* aContent) {
770 if (aContent == mMap || mConsiderWholeSubtree) {
771 UpdateAreas();
772 }
773 }
774
AttributeChanged(dom::Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)775 void nsImageMap::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
776 nsAtom* aAttribute, int32_t aModType,
777 const nsAttrValue* aOldValue) {
778 // If the parent of the changing content node is our map then update
779 // the map. But only do this if the node is an HTML <area> or <a>
780 // and the attribute that's changing is "shape" or "coords" -- those
781 // are the only cases we care about.
782 if ((aElement->NodeInfo()->Equals(nsGkAtoms::area) ||
783 aElement->NodeInfo()->Equals(nsGkAtoms::a)) &&
784 aElement->IsHTMLElement() && aNameSpaceID == kNameSpaceID_None &&
785 (aAttribute == nsGkAtoms::shape || aAttribute == nsGkAtoms::coords)) {
786 MaybeUpdateAreas(aElement->GetParent());
787 } else if (aElement == mMap && aNameSpaceID == kNameSpaceID_None &&
788 (aAttribute == nsGkAtoms::name || aAttribute == nsGkAtoms::id) &&
789 mImageFrame) {
790 // ID or name has changed. Let ImageFrame recreate ImageMap.
791 mImageFrame->DisconnectMap();
792 }
793 }
794
ContentAppended(nsIContent * aFirstNewContent)795 void nsImageMap::ContentAppended(nsIContent* aFirstNewContent) {
796 MaybeUpdateAreas(aFirstNewContent->GetParent());
797 }
798
ContentInserted(nsIContent * aChild)799 void nsImageMap::ContentInserted(nsIContent* aChild) {
800 MaybeUpdateAreas(aChild->GetParent());
801 }
802
TakeArea(nsImageMap::AreaList & aAreas,HTMLAreaElement * aArea)803 static UniquePtr<Area> TakeArea(nsImageMap::AreaList& aAreas,
804 HTMLAreaElement* aArea) {
805 UniquePtr<Area> result;
806 size_t index = 0;
807 for (UniquePtr<Area>& area : aAreas) {
808 if (area->mArea == aArea) {
809 result = Move(area);
810 break;
811 }
812 index++;
813 }
814
815 if (result) {
816 aAreas.RemoveElementAt(index);
817 }
818
819 return result;
820 }
821
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)822 void nsImageMap::ContentRemoved(nsIContent* aChild,
823 nsIContent* aPreviousSibling) {
824 if (aChild->GetParent() != mMap && !mConsiderWholeSubtree) {
825 return;
826 }
827
828 auto* areaElement = HTMLAreaElement::FromContent(aChild);
829 if (!areaElement) {
830 return;
831 }
832
833 UniquePtr<Area> area = TakeArea(mAreas, areaElement);
834 if (!area) {
835 return;
836 }
837
838 AreaRemoved(area->mArea);
839
840 #ifdef ACCESSIBILITY
841 if (nsAccessibilityService* accService = GetAccService()) {
842 accService->UpdateImageMap(mImageFrame);
843 }
844 #endif
845 }
846
ParentChainChanged(nsIContent * aContent)847 void nsImageMap::ParentChainChanged(nsIContent* aContent) {
848 NS_ASSERTION(aContent == mMap, "Unexpected ParentChainChanged notification!");
849 if (mImageFrame) {
850 mImageFrame->DisconnectMap();
851 }
852 }
853
HandleEvent(nsIDOMEvent * aEvent)854 nsresult nsImageMap::HandleEvent(nsIDOMEvent* aEvent) {
855 nsAutoString eventType;
856 aEvent->GetType(eventType);
857 bool focus = eventType.EqualsLiteral("focus");
858 MOZ_ASSERT(focus == !eventType.EqualsLiteral("blur"),
859 "Unexpected event type");
860
861 // Set which one of our areas changed focus
862 nsCOMPtr<nsIContent> targetContent =
863 do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
864 if (!targetContent) {
865 return NS_OK;
866 }
867
868 for (auto& area : mAreas) {
869 if (area->mArea == targetContent) {
870 // Set or Remove internal focus
871 area->HasFocus(focus);
872 // Now invalidate the rect
873 if (mImageFrame) {
874 mImageFrame->InvalidateFrame();
875 }
876 break;
877 }
878 }
879 return NS_OK;
880 }
881
Destroy()882 void nsImageMap::Destroy() {
883 FreeAreas();
884 mImageFrame = nullptr;
885 mMap->RemoveMutationObserver(this);
886 }
887