Coverage Report

Created: 2018-09-25 14:53

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