Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / generic / nsImageMap.cpp
blobda80842319affffd014d2c08407538778e2a7f54
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
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/
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.
15 * The Original Code is mozilla.org code.
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.
22 * Contributor(s):
23 * Mats Palmgren <mats.palmgren@bredband.net>
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.
37 * ***** END LICENSE BLOCK ***** */
39 /* code for HTML client-side image maps */
41 #include "nsImageMap.h"
42 #include "nsString.h"
43 #include "nsReadableUtils.h"
44 #include "nsIRenderingContext.h"
45 #include "nsPresContext.h"
46 #include "nsIURL.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 "nsIDOMHTMLElement.h"
53 #include "nsIDOMHTMLMapElement.h"
54 #include "nsIDOMHTMLAreaElement.h"
55 #include "nsIDOMHTMLAnchorElement.h"
56 #include "nsIDOMHTMLCollection.h"
57 #include "nsIDocument.h"
58 #include "nsINameSpaceManager.h"
59 #include "nsGkAtoms.h"
60 #include "nsIDOMEventTarget.h"
61 #include "nsIPresShell.h"
62 #include "nsIFrame.h"
63 #include "nsCoord.h"
64 #include "nsIImageMap.h"
65 #include "nsIConsoleService.h"
66 #include "nsIScriptError.h"
67 #include "nsIStringBundle.h"
68 #include "nsIDocument.h"
69 #include "nsContentUtils.h"
71 namespace dom = mozilla::dom;
73 static NS_DEFINE_CID(kCStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);
75 class Area {
76 public:
77 Area(nsIContent* aArea);
78 virtual ~Area();
80 virtual void ParseCoords(const nsAString& aSpec);
82 virtual PRBool IsInside(nscoord x, nscoord y) const = 0;
83 virtual void Draw(nsIFrame* aFrame, nsIRenderingContext& aRC) = 0;
84 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect) = 0;
86 void HasFocus(PRBool aHasFocus);
88 nsCOMPtr<nsIContent> mArea;
89 nscoord* mCoords;
90 PRInt32 mNumCoords;
91 PRPackedBool mHasFocus;
94 Area::Area(nsIContent* aArea)
95 : mArea(aArea)
97 MOZ_COUNT_CTOR(Area);
98 NS_PRECONDITION(mArea, "How did that happen?");
99 mCoords = nsnull;
100 mNumCoords = 0;
101 mHasFocus = PR_FALSE;
104 Area::~Area()
106 MOZ_COUNT_DTOR(Area);
107 delete [] mCoords;
110 #include <stdlib.h>
112 inline PRBool
113 is_space(char c)
115 return (c == ' ' ||
116 c == '\f' ||
117 c == '\n' ||
118 c == '\r' ||
119 c == '\t' ||
120 c == '\v');
123 static void logMessage(nsIContent* aContent,
124 const nsAString& aCoordsSpec,
125 PRInt32 aFlags,
126 const char* aMessageName) {
127 nsIDocument* doc = aContent->GetOwnerDoc();
129 nsContentUtils::ReportToConsole(
130 nsContentUtils::eLAYOUT_PROPERTIES,
131 aMessageName,
132 nsnull, /* params */
133 0, /* params length */
134 nsnull,
135 PromiseFlatString(NS_LITERAL_STRING("coords=\"") +
136 aCoordsSpec +
137 NS_LITERAL_STRING("\"")), /* source line */
138 0, /* line number */
139 0, /* column number */
140 aFlags,
141 "ImageMap", doc);
144 void Area::ParseCoords(const nsAString& aSpec)
146 char* cp = ToNewCString(aSpec);
147 if (cp) {
148 char *tptr;
149 char *n_str;
150 PRInt32 i, cnt;
151 PRInt32 *value_list;
154 * Nothing in an empty list
156 mNumCoords = 0;
157 mCoords = nsnull;
158 if (*cp == '\0')
160 return;
164 * Skip beginning whitespace, all whitespace is empty list.
166 n_str = cp;
167 while (is_space(*n_str))
169 n_str++;
171 if (*n_str == '\0')
173 return;
177 * Make a pass where any two numbers separated by just whitespace
178 * are given a comma separator. Count entries while passing.
180 cnt = 0;
181 while (*n_str != '\0')
183 PRBool has_comma;
186 * Skip to a separator
188 tptr = n_str;
189 while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0')
191 tptr++;
193 n_str = tptr;
196 * If no more entries, break out here
198 if (*n_str == '\0')
200 break;
204 * Skip to the end of the separator, noting if we have a
205 * comma.
207 has_comma = PR_FALSE;
208 while (is_space(*tptr) || *tptr == ',')
210 if (*tptr == ',')
212 if (!has_comma)
214 has_comma = PR_TRUE;
216 else
218 break;
221 tptr++;
224 * If this was trailing whitespace we skipped, we are done.
226 if ((*tptr == '\0') && !has_comma)
228 break;
231 * Else if the separator is all whitespace, and this is not the
232 * end of the string, add a comma to the separator.
234 else if (!has_comma)
236 *n_str = ',';
240 * count the entry skipped.
242 cnt++;
244 n_str = tptr;
247 * count the last entry in the list.
249 cnt++;
252 * Allocate space for the coordinate array.
254 value_list = new nscoord[cnt];
255 if (!value_list)
257 return;
261 * Second pass to copy integer values into list.
263 tptr = cp;
264 for (i=0; i<cnt; i++)
266 char *ptr;
268 ptr = strchr(tptr, ',');
269 if (ptr)
271 *ptr = '\0';
274 * Strip whitespace in front of number because I don't
275 * trust atoi to do it on all platforms.
277 while (is_space(*tptr))
279 tptr++;
281 if (*tptr == '\0')
283 value_list[i] = 0;
285 else
287 value_list[i] = (nscoord) ::atoi(tptr);
289 if (ptr)
291 *ptr = ',';
292 tptr = ptr + 1;
296 mNumCoords = cnt;
297 mCoords = value_list;
299 NS_Free(cp);
303 void Area::HasFocus(PRBool aHasFocus)
305 mHasFocus = aHasFocus;
308 //----------------------------------------------------------------------
310 class DefaultArea : public Area {
311 public:
312 DefaultArea(nsIContent* aArea);
314 virtual PRBool IsInside(nscoord x, nscoord y) const;
315 virtual void Draw(nsIFrame* aFrame, nsIRenderingContext& aRC);
316 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect);
319 DefaultArea::DefaultArea(nsIContent* aArea)
320 : Area(aArea)
324 PRBool DefaultArea::IsInside(nscoord x, nscoord y) const
326 return PR_TRUE;
329 void DefaultArea::Draw(nsIFrame* aFrame, nsIRenderingContext& aRC)
331 if (mHasFocus) {
332 nsRect r = aFrame->GetRect();
333 r.MoveTo(0, 0);
334 nscoord x1 = r.x;
335 nscoord y1 = r.y;
336 const nscoord kOnePixel = nsPresContext::CSSPixelsToAppUnits(1);
337 nscoord x2 = r.XMost() - kOnePixel;
338 nscoord y2 = r.YMost() - kOnePixel;
339 // XXX aRC.DrawRect(r) result is ugly, that's why we use DrawLine.
340 aRC.DrawLine(x1, y1, x1, y2);
341 aRC.DrawLine(x1, y2, x2, y2);
342 aRC.DrawLine(x1, y1, x2, y1);
343 aRC.DrawLine(x2, y1, x2, y2);
347 void DefaultArea::GetRect(nsIFrame* aFrame, nsRect& aRect)
349 aRect = aFrame->GetRect();
350 aRect.MoveTo(0, 0);
353 //----------------------------------------------------------------------
355 class RectArea : public Area {
356 public:
357 RectArea(nsIContent* aArea);
359 virtual void ParseCoords(const nsAString& aSpec);
360 virtual PRBool IsInside(nscoord x, nscoord y) const;
361 virtual void Draw(nsIFrame* aFrame, nsIRenderingContext& aRC);
362 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect);
365 RectArea::RectArea(nsIContent* aArea)
366 : Area(aArea)
370 void RectArea::ParseCoords(const nsAString& aSpec)
372 Area::ParseCoords(aSpec);
374 PRBool saneRect = PR_TRUE;
375 PRInt32 flag = nsIScriptError::warningFlag;
376 if (mNumCoords >= 4) {
377 if (mCoords[0] > mCoords[2]) {
378 // x-coords in reversed order
379 nscoord x = mCoords[2];
380 mCoords[2] = mCoords[0];
381 mCoords[0] = x;
382 saneRect = PR_FALSE;
385 if (mCoords[1] > mCoords[3]) {
386 // y-coords in reversed order
387 nscoord y = mCoords[3];
388 mCoords[3] = mCoords[1];
389 mCoords[1] = y;
390 saneRect = PR_FALSE;
393 if (mNumCoords > 4) {
394 // Someone missed the concept of a rect here
395 saneRect = PR_FALSE;
397 } else {
398 saneRect = PR_FALSE;
399 flag = nsIScriptError::errorFlag;
402 if (!saneRect) {
403 logMessage(mArea, aSpec, flag, "ImageMapRectBoundsError");
407 PRBool RectArea::IsInside(nscoord x, nscoord y) const
409 if (mNumCoords >= 4) { // Note: > is for nav compatability
410 nscoord x1 = mCoords[0];
411 nscoord y1 = mCoords[1];
412 nscoord x2 = mCoords[2];
413 nscoord y2 = mCoords[3];
414 NS_ASSERTION(x1 <= x2 && y1 <= y2,
415 "Someone screwed up RectArea::ParseCoords");
416 if ((x >= x1) && (x <= x2) && (y >= y1) && (y <= y2)) {
417 return PR_TRUE;
420 return PR_FALSE;
423 void RectArea::Draw(nsIFrame* aFrame, nsIRenderingContext& aRC)
425 if (mHasFocus) {
426 if (mNumCoords >= 4) {
427 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
428 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
429 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
430 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
431 NS_ASSERTION(x1 <= x2 && y1 <= y2,
432 "Someone screwed up RectArea::ParseCoords");
433 aRC.DrawLine(x1, y1, x1, y2);
434 aRC.DrawLine(x1, y2, x2, y2);
435 aRC.DrawLine(x1, y1, x2, y1);
436 aRC.DrawLine(x2, y1, x2, y2);
441 void RectArea::GetRect(nsIFrame* aFrame, nsRect& aRect)
443 if (mNumCoords >= 4) {
444 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
445 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
446 nscoord x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
447 nscoord y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[3]);
448 NS_ASSERTION(x1 <= x2 && y1 <= y2,
449 "Someone screwed up RectArea::ParseCoords");
451 aRect.SetRect(x1, y1, x2, y2);
455 //----------------------------------------------------------------------
457 class PolyArea : public Area {
458 public:
459 PolyArea(nsIContent* aArea);
461 virtual void ParseCoords(const nsAString& aSpec);
462 virtual PRBool IsInside(nscoord x, nscoord y) const;
463 virtual void Draw(nsIFrame* aFrame, nsIRenderingContext& aRC);
464 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect);
467 PolyArea::PolyArea(nsIContent* aArea)
468 : Area(aArea)
472 void PolyArea::ParseCoords(const nsAString& aSpec)
474 Area::ParseCoords(aSpec);
476 if (mNumCoords >= 2) {
477 if (mNumCoords & 1U) {
478 logMessage(mArea,
479 aSpec,
480 nsIScriptError::warningFlag,
481 "ImageMapPolyOddNumberOfCoords");
483 } else {
484 logMessage(mArea,
485 aSpec,
486 nsIScriptError::errorFlag,
487 "ImageMapPolyWrongNumberOfCoords");
491 PRBool PolyArea::IsInside(nscoord x, nscoord y) const
493 if (mNumCoords >= 6) {
494 PRInt32 intersects = 0;
495 nscoord wherex = x;
496 nscoord wherey = y;
497 PRInt32 totalv = mNumCoords / 2;
498 PRInt32 totalc = totalv * 2;
499 nscoord xval = mCoords[totalc - 2];
500 nscoord yval = mCoords[totalc - 1];
501 PRInt32 end = totalc;
502 PRInt32 pointer = 1;
504 if ((yval >= wherey) != (mCoords[pointer] >= wherey)) {
505 if ((xval >= wherex) == (mCoords[0] >= wherex)) {
506 intersects += (xval >= wherex) ? 1 : 0;
507 } else {
508 intersects += ((xval - (yval - wherey) *
509 (mCoords[0] - xval) /
510 (mCoords[pointer] - yval)) >= wherex) ? 1 : 0;
514 // XXX I wonder what this is doing; this is a translation of ptinpoly.c
515 while (pointer < end) {
516 yval = mCoords[pointer];
517 pointer += 2;
518 if (yval >= wherey) {
519 while((pointer < end) && (mCoords[pointer] >= wherey))
520 pointer+=2;
521 if (pointer >= end)
522 break;
523 if ((mCoords[pointer-3] >= wherex) ==
524 (mCoords[pointer-1] >= wherex)) {
525 intersects += (mCoords[pointer-3] >= wherex) ? 1 : 0;
526 } else {
527 intersects +=
528 ((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) *
529 (mCoords[pointer-1] - mCoords[pointer-3]) /
530 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0;
532 } else {
533 while((pointer < end) && (mCoords[pointer] < wherey))
534 pointer+=2;
535 if (pointer >= end)
536 break;
537 if ((mCoords[pointer-3] >= wherex) ==
538 (mCoords[pointer-1] >= wherex)) {
539 intersects += (mCoords[pointer-3] >= wherex) ? 1:0;
540 } else {
541 intersects +=
542 ((mCoords[pointer-3] - (mCoords[pointer-2] - wherey) *
543 (mCoords[pointer-1] - mCoords[pointer-3]) /
544 (mCoords[pointer] - mCoords[pointer - 2])) >= wherex) ? 1:0;
548 if ((intersects & 1) != 0) {
549 return PR_TRUE;
552 return PR_FALSE;
555 void PolyArea::Draw(nsIFrame* aFrame, nsIRenderingContext& aRC)
557 if (mHasFocus) {
558 if (mNumCoords >= 6) {
559 nscoord x0 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
560 nscoord y0 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
561 nscoord x1, y1;
562 for (PRInt32 i = 2; i < mNumCoords; i += 2) {
563 x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[i]);
564 y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[i+1]);
565 aRC.DrawLine(x0, y0, x1, y1);
566 x0 = x1;
567 y0 = y1;
569 x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
570 y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
571 aRC.DrawLine(x0, y0, x1, y1);
576 void PolyArea::GetRect(nsIFrame* aFrame, nsRect& aRect)
578 if (mNumCoords >= 6) {
579 nscoord x1, x2, y1, y2, xtmp, ytmp;
580 x1 = x2 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
581 y1 = y2 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
582 for (PRInt32 i = 2; i < mNumCoords; i += 2) {
583 xtmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i]);
584 ytmp = nsPresContext::CSSPixelsToAppUnits(mCoords[i+1]);
585 x1 = x1 < xtmp ? x1 : xtmp;
586 y1 = y1 < ytmp ? y1 : ytmp;
587 x2 = x2 > xtmp ? x2 : xtmp;
588 y2 = y2 > ytmp ? y2 : ytmp;
591 aRect.SetRect(x1, y1, x2, y2);
595 //----------------------------------------------------------------------
597 class CircleArea : public Area {
598 public:
599 CircleArea(nsIContent* aArea);
601 virtual void ParseCoords(const nsAString& aSpec);
602 virtual PRBool IsInside(nscoord x, nscoord y) const;
603 virtual void Draw(nsIFrame* aFrame, nsIRenderingContext& aRC);
604 virtual void GetRect(nsIFrame* aFrame, nsRect& aRect);
607 CircleArea::CircleArea(nsIContent* aArea)
608 : Area(aArea)
612 void CircleArea::ParseCoords(const nsAString& aSpec)
614 Area::ParseCoords(aSpec);
616 PRBool wrongNumberOfCoords = PR_FALSE;
617 PRInt32 flag = nsIScriptError::warningFlag;
618 if (mNumCoords >= 3) {
619 if (mCoords[2] < 0) {
620 logMessage(mArea,
621 aSpec,
622 nsIScriptError::errorFlag,
623 "ImageMapCircleNegativeRadius");
626 if (mNumCoords > 3) {
627 wrongNumberOfCoords = PR_TRUE;
629 } else {
630 wrongNumberOfCoords = PR_TRUE;
631 flag = nsIScriptError::errorFlag;
634 if (wrongNumberOfCoords) {
635 logMessage(mArea,
636 aSpec,
637 flag,
638 "ImageMapCircleWrongNumberOfCoords");
642 PRBool CircleArea::IsInside(nscoord x, nscoord y) const
644 // Note: > is for nav compatability
645 if (mNumCoords >= 3) {
646 nscoord x1 = mCoords[0];
647 nscoord y1 = mCoords[1];
648 nscoord radius = mCoords[2];
649 if (radius < 0) {
650 return PR_FALSE;
652 nscoord dx = x1 - x;
653 nscoord dy = y1 - y;
654 nscoord dist = (dx * dx) + (dy * dy);
655 if (dist <= (radius * radius)) {
656 return PR_TRUE;
659 return PR_FALSE;
662 void CircleArea::Draw(nsIFrame* aFrame, nsIRenderingContext& aRC)
664 if (mHasFocus) {
665 if (mNumCoords >= 3) {
666 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
667 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
668 nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
669 if (radius < 0) {
670 return;
672 nscoord x = x1 - radius;
673 nscoord y = y1 - radius;
674 nscoord w = 2 * radius;
675 aRC.DrawEllipse(x, y, w, w);
680 void CircleArea::GetRect(nsIFrame* aFrame, nsRect& aRect)
682 if (mNumCoords >= 3) {
683 nscoord x1 = nsPresContext::CSSPixelsToAppUnits(mCoords[0]);
684 nscoord y1 = nsPresContext::CSSPixelsToAppUnits(mCoords[1]);
685 nscoord radius = nsPresContext::CSSPixelsToAppUnits(mCoords[2]);
686 if (radius < 0) {
687 return;
690 aRect.SetRect(x1 - radius, y1 - radius, x1 + radius, y1 + radius);
694 //----------------------------------------------------------------------
697 nsImageMap::nsImageMap() :
698 mPresShell(nsnull),
699 mImageFrame(nsnull),
700 mContainsBlockContents(PR_FALSE)
704 nsImageMap::~nsImageMap()
706 NS_ASSERTION(mAreas.Length() == 0, "Destroy was not called");
709 NS_IMPL_ISUPPORTS4(nsImageMap,
710 nsIMutationObserver,
711 nsIDOMFocusListener,
712 nsIDOMEventListener,
713 nsIImageMap)
715 NS_IMETHODIMP
716 nsImageMap::GetBoundsForAreaContent(nsIContent *aContent,
717 nsRect& aBounds)
719 NS_ENSURE_TRUE(aContent, NS_ERROR_INVALID_ARG);
721 // Find the Area struct associated with this content node, and return bounds
722 PRUint32 i, n = mAreas.Length();
723 for (i = 0; i < n; i++) {
724 Area* area = mAreas.ElementAt(i);
725 if (area->mArea == aContent) {
726 aBounds = nsRect();
727 nsIFrame* frame = aContent->GetPrimaryFrame();
728 if (frame) {
729 area->GetRect(frame, aBounds);
731 return NS_OK;
734 return NS_ERROR_FAILURE;
737 void
738 nsImageMap::FreeAreas()
740 PRUint32 i, n = mAreas.Length();
741 for (i = 0; i < n; i++) {
742 Area* area = mAreas.ElementAt(i);
743 NS_ASSERTION(area->mArea->GetPrimaryFrame() == mImageFrame,
744 "Unexpected primary frame");
745 area->mArea->SetPrimaryFrame(nsnull);
747 area->mArea->RemoveEventListenerByIID(this, NS_GET_IID(nsIDOMFocusListener));
748 delete area;
750 mAreas.Clear();
753 nsresult
754 nsImageMap::Init(nsIPresShell* aPresShell, nsIFrame* aImageFrame, nsIDOMHTMLMapElement* aMap)
756 NS_PRECONDITION(nsnull != aMap, "null ptr");
757 if (nsnull == aMap) {
758 return NS_ERROR_NULL_POINTER;
760 mPresShell = aPresShell;
761 mImageFrame = aImageFrame;
763 mMap = do_QueryInterface(aMap);
764 NS_ASSERTION(mMap, "aMap is not an nsIContent!");
765 mMap->AddMutationObserver(this);
767 // "Compile" the areas in the map into faster access versions
768 return UpdateAreas();
772 nsresult
773 nsImageMap::SearchForAreas(nsIContent* aParent, PRBool& aFoundArea,
774 PRBool& aFoundAnchor)
776 nsresult rv = NS_OK;
777 PRUint32 i, n = aParent->GetChildCount();
779 // Look for <area> or <a> elements. We'll use whichever type we find first.
780 for (i = 0; i < n; i++) {
781 nsIContent *child = aParent->GetChildAt(i);
783 if (child->IsHTML()) {
784 // If we haven't determined that the map element contains an
785 // <a> element yet, then look for <area>.
786 if (!aFoundAnchor && child->Tag() == nsGkAtoms::area) {
787 aFoundArea = PR_TRUE;
788 rv = AddArea(child);
789 NS_ENSURE_SUCCESS(rv, rv);
791 // Continue to next child. This stops mContainsBlockContents from
792 // getting set. It also makes us ignore children of <area>s which
793 // is consistent with how we react to dynamic insertion of such
794 // children.
795 continue;
797 // If we haven't determined that the map element contains an
798 // <area> element yet, then look for <a>.
799 if (!aFoundArea && child->Tag() == nsGkAtoms::a) {
800 aFoundAnchor = PR_TRUE;
801 rv = AddArea(child);
802 NS_ENSURE_SUCCESS(rv, rv);
806 if (child->IsElement()) {
807 mContainsBlockContents = PR_TRUE;
808 rv = SearchForAreas(child, aFoundArea, aFoundAnchor);
809 NS_ENSURE_SUCCESS(rv, rv);
813 return NS_OK;
816 nsresult
817 nsImageMap::UpdateAreas()
819 // Get rid of old area data
820 FreeAreas();
822 PRBool foundArea = PR_FALSE;
823 PRBool foundAnchor = PR_FALSE;
824 mContainsBlockContents = PR_FALSE;
826 return SearchForAreas(mMap, foundArea, foundAnchor);
829 nsresult
830 nsImageMap::AddArea(nsIContent* aArea)
832 static nsIContent::AttrValuesArray strings[] =
833 {&nsGkAtoms::rect, &nsGkAtoms::rectangle,
834 &nsGkAtoms::circle, &nsGkAtoms::circ,
835 &nsGkAtoms::_default,
836 &nsGkAtoms::poly, &nsGkAtoms::polygon,
837 nsnull};
839 Area* area;
840 switch (aArea->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::shape,
841 strings, eIgnoreCase)) {
842 case nsIContent::ATTR_VALUE_NO_MATCH:
843 case nsIContent::ATTR_MISSING:
844 case 0:
845 case 1:
846 area = new RectArea(aArea);
847 break;
848 case 2:
849 case 3:
850 area = new CircleArea(aArea);
851 break;
852 case 4:
853 area = new DefaultArea(aArea);
854 break;
855 case 5:
856 case 6:
857 area = new PolyArea(aArea);
858 break;
859 default:
860 NS_NOTREACHED("FindAttrValueIn returned an unexpected value.");
861 break;
863 if (!area)
864 return NS_ERROR_OUT_OF_MEMORY;
866 //Add focus listener to track area focus changes
867 aArea->AddEventListenerByIID(this, NS_GET_IID(nsIDOMFocusListener));
869 // This is a nasty hack. It needs to go away: see bug 135040. Once this is
870 // removed, the code added to nsCSSFrameConstructor::RestyleElement,
871 // nsCSSFrameConstructor::ContentRemoved (both hacks there), and
872 // nsCSSFrameConstructor::ProcessRestyledFrames to work around this issue can
873 // be removed.
874 aArea->SetPrimaryFrame(mImageFrame);
876 nsAutoString coords;
877 aArea->GetAttr(kNameSpaceID_None, nsGkAtoms::coords, coords);
878 area->ParseCoords(coords);
879 mAreas.AppendElement(area);
880 return NS_OK;
883 PRBool
884 nsImageMap::IsInside(nscoord aX, nscoord aY,
885 nsIContent** aContent) const
887 NS_ASSERTION(mMap, "Not initialized");
888 PRUint32 i, n = mAreas.Length();
889 for (i = 0; i < n; i++) {
890 Area* area = mAreas.ElementAt(i);
891 if (area->IsInside(aX, aY)) {
892 NS_ADDREF(*aContent = area->mArea);
894 return PR_TRUE;
898 return PR_FALSE;
901 void
902 nsImageMap::Draw(nsIFrame* aFrame, nsIRenderingContext& aRC)
904 PRUint32 i, n = mAreas.Length();
905 for (i = 0; i < n; i++) {
906 Area* area = mAreas.ElementAt(i);
907 area->Draw(aFrame, aRC);
911 void
912 nsImageMap::MaybeUpdateAreas(nsIContent *aContent)
914 if (aContent == mMap || mContainsBlockContents) {
915 UpdateAreas();
919 void
920 nsImageMap::AttributeChanged(nsIDocument* aDocument,
921 dom::Element* aElement,
922 PRInt32 aNameSpaceID,
923 nsIAtom* aAttribute,
924 PRInt32 aModType)
926 // If the parent of the changing content node is our map then update
927 // the map. But only do this if the node is an HTML <area> or <a>
928 // and the attribute that's changing is "shape" or "coords" -- those
929 // are the only cases we care about.
930 if ((aElement->NodeInfo()->Equals(nsGkAtoms::area) ||
931 aElement->NodeInfo()->Equals(nsGkAtoms::a)) &&
932 aElement->IsHTML() &&
933 aNameSpaceID == kNameSpaceID_None &&
934 (aAttribute == nsGkAtoms::shape ||
935 aAttribute == nsGkAtoms::coords)) {
936 MaybeUpdateAreas(aElement->GetParent());
940 void
941 nsImageMap::ContentAppended(nsIDocument *aDocument,
942 nsIContent* aContainer,
943 nsIContent* aFirstNewContent,
944 PRInt32 /* unused */)
946 MaybeUpdateAreas(aContainer);
949 void
950 nsImageMap::ContentInserted(nsIDocument *aDocument,
951 nsIContent* aContainer,
952 nsIContent* aChild,
953 PRInt32 /* unused */)
955 MaybeUpdateAreas(aContainer);
958 void
959 nsImageMap::ContentRemoved(nsIDocument *aDocument,
960 nsIContent* aContainer,
961 nsIContent* aChild,
962 PRInt32 aIndexInContainer,
963 nsIContent* aPreviousSibling)
965 MaybeUpdateAreas(aContainer);
968 nsresult
969 nsImageMap::Focus(nsIDOMEvent* aEvent)
971 return ChangeFocus(aEvent, PR_TRUE);
974 nsresult
975 nsImageMap::Blur(nsIDOMEvent* aEvent)
977 return ChangeFocus(aEvent, PR_FALSE);
980 nsresult
981 nsImageMap::ChangeFocus(nsIDOMEvent* aEvent, PRBool aFocus)
983 //Set which one of our areas changed focus
984 nsCOMPtr<nsIDOMEventTarget> target;
985 if (NS_SUCCEEDED(aEvent->GetTarget(getter_AddRefs(target))) && target) {
986 nsCOMPtr<nsIContent> targetContent(do_QueryInterface(target));
987 if (targetContent) {
988 PRUint32 i, n = mAreas.Length();
989 for (i = 0; i < n; i++) {
990 Area* area = mAreas.ElementAt(i);
991 if (area->mArea == targetContent) {
992 //Set or Remove internal focus
993 area->HasFocus(aFocus);
994 //Now invalidate the rect
995 nsIFrame* imgFrame = targetContent->GetPrimaryFrame();
996 if (imgFrame) {
997 nsRect dmgRect;
998 area->GetRect(imgFrame, dmgRect);
999 imgFrame->Invalidate(dmgRect);
1001 break;
1006 return NS_OK;
1009 nsresult
1010 nsImageMap::HandleEvent(nsIDOMEvent* aEvent)
1012 return NS_OK;
1015 void
1016 nsImageMap::Destroy(void)
1018 FreeAreas();
1019 mMap->RemoveMutationObserver(this);