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