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