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