/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 | } |