/src/mozilla-central/dom/canvas/CanvasRenderingContext2D.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #include "CanvasRenderingContext2D.h" |
7 | | |
8 | | #include "mozilla/gfx/Helpers.h" |
9 | | #include "nsXULElement.h" |
10 | | |
11 | | #include "nsAutoPtr.h" |
12 | | #include "nsIServiceManager.h" |
13 | | #include "nsMathUtils.h" |
14 | | #include "SVGImageContext.h" |
15 | | |
16 | | #include "nsContentUtils.h" |
17 | | |
18 | | #include "nsIDocument.h" |
19 | | #include "mozilla/dom/HTMLCanvasElement.h" |
20 | | #include "SVGObserverUtils.h" |
21 | | #include "nsPresContext.h" |
22 | | #include "nsIPresShell.h" |
23 | | |
24 | | #include "nsIInterfaceRequestorUtils.h" |
25 | | #include "nsIFrame.h" |
26 | | #include "nsError.h" |
27 | | |
28 | | #include "nsCSSPseudoElements.h" |
29 | | #include "nsComputedDOMStyle.h" |
30 | | |
31 | | #include "nsPrintfCString.h" |
32 | | |
33 | | #include "nsReadableUtils.h" |
34 | | |
35 | | #include "nsColor.h" |
36 | | #include "nsGfxCIID.h" |
37 | | #include "nsIDocShell.h" |
38 | | #include "nsIDOMWindow.h" |
39 | | #include "nsPIDOMWindow.h" |
40 | | #include "nsDisplayList.h" |
41 | | #include "nsFocusManager.h" |
42 | | #include "nsContentUtils.h" |
43 | | |
44 | | #include "nsTArray.h" |
45 | | |
46 | | #include "ImageEncoder.h" |
47 | | #include "ImageRegion.h" |
48 | | |
49 | | #include "gfxContext.h" |
50 | | #include "gfxPlatform.h" |
51 | | #include "gfxFont.h" |
52 | | #include "gfxBlur.h" |
53 | | #include "gfxPrefs.h" |
54 | | #include "gfxUtils.h" |
55 | | |
56 | | #include "nsFrameLoader.h" |
57 | | #include "nsBidiPresUtils.h" |
58 | | #include "Layers.h" |
59 | | #include "LayerUserData.h" |
60 | | #include "CanvasUtils.h" |
61 | | #include "nsIMemoryReporter.h" |
62 | | #include "nsStyleUtil.h" |
63 | | #include "CanvasImageCache.h" |
64 | | |
65 | | #include <algorithm> |
66 | | |
67 | | #include "jsapi.h" |
68 | | #include "jsfriendapi.h" |
69 | | #include "js/Conversions.h" |
70 | | #include "js/HeapAPI.h" |
71 | | |
72 | | #include "mozilla/Alignment.h" |
73 | | #include "mozilla/Assertions.h" |
74 | | #include "mozilla/CheckedInt.h" |
75 | | #include "mozilla/DebugOnly.h" |
76 | | #include "mozilla/dom/ContentParent.h" |
77 | | #include "mozilla/dom/ImageBitmap.h" |
78 | | #include "mozilla/dom/ImageData.h" |
79 | | #include "mozilla/dom/PBrowserParent.h" |
80 | | #include "mozilla/dom/ToJSValue.h" |
81 | | #include "mozilla/dom/TypedArray.h" |
82 | | #include "mozilla/EndianUtils.h" |
83 | | #include "mozilla/gfx/2D.h" |
84 | | #include "mozilla/gfx/Helpers.h" |
85 | | #include "mozilla/gfx/Tools.h" |
86 | | #include "mozilla/gfx/PathHelpers.h" |
87 | | #include "mozilla/gfx/DataSurfaceHelpers.h" |
88 | | #include "mozilla/gfx/PatternHelpers.h" |
89 | | #include "mozilla/gfx/Swizzle.h" |
90 | | #include "mozilla/layers/PersistentBufferProvider.h" |
91 | | #include "mozilla/MathAlgorithms.h" |
92 | | #include "mozilla/Preferences.h" |
93 | | #include "mozilla/ServoBindings.h" |
94 | | #include "mozilla/Telemetry.h" |
95 | | #include "mozilla/TimeStamp.h" |
96 | | #include "mozilla/UniquePtr.h" |
97 | | #include "mozilla/Unused.h" |
98 | | #include "nsCCUncollectableMarker.h" |
99 | | #include "nsWrapperCacheInlines.h" |
100 | | #include "mozilla/dom/CanvasRenderingContext2DBinding.h" |
101 | | #include "mozilla/dom/CanvasPath.h" |
102 | | #include "mozilla/dom/HTMLImageElement.h" |
103 | | #include "mozilla/dom/HTMLVideoElement.h" |
104 | | #include "mozilla/dom/SVGImageElement.h" |
105 | | #include "mozilla/dom/SVGMatrix.h" |
106 | | #include "mozilla/dom/TextMetrics.h" |
107 | | #include "mozilla/dom/SVGMatrix.h" |
108 | | #include "mozilla/FloatingPoint.h" |
109 | | #include "nsGlobalWindow.h" |
110 | | #include "GLContext.h" |
111 | | #include "GLContextProvider.h" |
112 | | #include "SVGContentUtils.h" |
113 | | #include "nsIScreenManager.h" |
114 | | #include "nsFilterInstance.h" |
115 | | #include "nsSVGLength2.h" |
116 | | #include "nsDeviceContext.h" |
117 | | #include "nsFontMetrics.h" |
118 | | #include "Units.h" |
119 | | #include "CanvasUtils.h" |
120 | | #include "mozilla/CycleCollectedJSRuntime.h" |
121 | | #include "mozilla/ServoStyleSet.h" |
122 | | #include "mozilla/layers/CanvasClient.h" |
123 | | #include "mozilla/layers/WebRenderUserData.h" |
124 | | #include "mozilla/layers/WebRenderCanvasRenderer.h" |
125 | | #include "mozilla/ServoCSSParser.h" |
126 | | |
127 | | #undef free // apparently defined by some windows header, clashing with a free() |
128 | | // method in SkTypes.h |
129 | | #include "SkiaGLGlue.h" |
130 | | #ifdef USE_SKIA |
131 | | #include "SurfaceTypes.h" |
132 | | #include "GLBlitHelper.h" |
133 | | #include "ScopedGLHelpers.h" |
134 | | #endif |
135 | | |
136 | | using mozilla::gl::GLContext; |
137 | | using mozilla::gl::SkiaGLGlue; |
138 | | using mozilla::gl::GLContextProvider; |
139 | | |
140 | | #ifdef XP_WIN |
141 | | #include "gfxWindowsPlatform.h" |
142 | | #endif |
143 | | |
144 | | // windows.h (included by chromium code) defines this, in its infinite wisdom |
145 | | #undef DrawText |
146 | | |
147 | | using namespace mozilla; |
148 | | using namespace mozilla::CanvasUtils; |
149 | | using namespace mozilla::css; |
150 | | using namespace mozilla::gfx; |
151 | | using namespace mozilla::image; |
152 | | using namespace mozilla::ipc; |
153 | | using namespace mozilla::layers; |
154 | | |
155 | | namespace mozilla { |
156 | | namespace dom { |
157 | | |
158 | | // Cap sigma to avoid overly large temp surfaces. |
159 | | const Float SIGMA_MAX = 100; |
160 | | |
161 | | const size_t MAX_STYLE_STACK_SIZE = 1024; |
162 | | |
163 | | /* Memory reporter stuff */ |
164 | | static int64_t gCanvasAzureMemoryUsed = 0; |
165 | | |
166 | | // Adds Save() / Restore() calls to the scope. |
167 | | class MOZ_RAII AutoSaveRestore |
168 | | { |
169 | | public: |
170 | | explicit AutoSaveRestore(CanvasRenderingContext2D* aCtx |
171 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
172 | | : mCtx(aCtx) |
173 | 0 | { |
174 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
175 | 0 | mCtx->Save(); |
176 | 0 | } |
177 | 0 | ~AutoSaveRestore() { mCtx->Restore(); } |
178 | | private: |
179 | | RefPtr<CanvasRenderingContext2D> mCtx; |
180 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
181 | | }; |
182 | | |
183 | | // This is KIND_OTHER because it's not always clear where in memory the pixels |
184 | | // of a canvas are stored. Furthermore, this memory will be tracked by the |
185 | | // underlying surface implementations. See bug 655638 for details. |
186 | | class Canvas2dPixelsReporter final : public nsIMemoryReporter |
187 | | { |
188 | 0 | ~Canvas2dPixelsReporter() {} |
189 | | public: |
190 | | NS_DECL_ISUPPORTS |
191 | | |
192 | | NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, |
193 | | nsISupports* aData, bool aAnonymize) override |
194 | 0 | { |
195 | 0 | MOZ_COLLECT_REPORT( |
196 | 0 | "canvas-2d-pixels", KIND_OTHER, UNITS_BYTES, gCanvasAzureMemoryUsed, |
197 | 0 | "Memory used by 2D canvases. Each canvas requires " |
198 | 0 | "(width * height * 4) bytes."); |
199 | 0 |
|
200 | 0 | return NS_OK; |
201 | 0 | } |
202 | | }; |
203 | | |
204 | | NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter) |
205 | | |
206 | | class CanvasRadialGradient : public CanvasGradient |
207 | | { |
208 | | public: |
209 | | CanvasRadialGradient(CanvasRenderingContext2D* aContext, |
210 | | const Point& aBeginOrigin, Float aBeginRadius, |
211 | | const Point& aEndOrigin, Float aEndRadius) |
212 | | : CanvasGradient(aContext, Type::RADIAL) |
213 | | , mCenter1(aBeginOrigin) |
214 | | , mCenter2(aEndOrigin) |
215 | | , mRadius1(aBeginRadius) |
216 | | , mRadius2(aEndRadius) |
217 | 0 | { |
218 | 0 | } |
219 | | |
220 | | Point mCenter1; |
221 | | Point mCenter2; |
222 | | Float mRadius1; |
223 | | Float mRadius2; |
224 | | }; |
225 | | |
226 | | class CanvasLinearGradient : public CanvasGradient |
227 | | { |
228 | | public: |
229 | | CanvasLinearGradient(CanvasRenderingContext2D* aContext, |
230 | | const Point& aBegin, const Point& aEnd) |
231 | | : CanvasGradient(aContext, Type::LINEAR) |
232 | | , mBegin(aBegin) |
233 | | , mEnd(aEnd) |
234 | 0 | { |
235 | 0 | } |
236 | | |
237 | | protected: |
238 | | friend struct CanvasBidiProcessor; |
239 | | friend class CanvasGeneralPattern; |
240 | | |
241 | | // Beginning of linear gradient. |
242 | | Point mBegin; |
243 | | // End of linear gradient. |
244 | | Point mEnd; |
245 | | }; |
246 | | |
247 | | bool |
248 | | CanvasRenderingContext2D::PatternIsOpaque(CanvasRenderingContext2D::Style aStyle) const |
249 | 0 | { |
250 | 0 | const ContextState& state = CurrentState(); |
251 | 0 | if (state.globalAlpha < 1.0) { |
252 | 0 | return false; |
253 | 0 | } |
254 | 0 | |
255 | 0 | if (state.patternStyles[aStyle] && state.patternStyles[aStyle]->mSurface) { |
256 | 0 | return IsOpaqueFormat(state.patternStyles[aStyle]->mSurface->GetFormat()); |
257 | 0 | } |
258 | 0 | |
259 | 0 | // TODO: for gradient patterns we could check that all stops are opaque |
260 | 0 | // colors. |
261 | 0 | |
262 | 0 | if (!state.gradientStyles[aStyle]) { |
263 | 0 | // it's a color pattern. |
264 | 0 | return Color::FromABGR(state.colorStyles[aStyle]).a >= 1.0; |
265 | 0 | } |
266 | 0 | |
267 | 0 | return false; |
268 | 0 | } |
269 | | |
270 | | // This class is named 'GeneralCanvasPattern' instead of just |
271 | | // 'GeneralPattern' to keep Windows PGO builds from confusing the |
272 | | // GeneralPattern class in gfxContext.cpp with this one. |
273 | | class CanvasGeneralPattern |
274 | | { |
275 | | public: |
276 | | typedef CanvasRenderingContext2D::Style Style; |
277 | | typedef CanvasRenderingContext2D::ContextState ContextState; |
278 | | |
279 | | Pattern& ForStyle(CanvasRenderingContext2D* aCtx, |
280 | | Style aStyle, |
281 | | DrawTarget* aRT) |
282 | 0 | { |
283 | 0 | // This should only be called once or the mPattern destructor will |
284 | 0 | // not be executed. |
285 | 0 | NS_ASSERTION(!mPattern.GetPattern(), "ForStyle() should only be called once on CanvasGeneralPattern!"); |
286 | 0 |
|
287 | 0 | const ContextState& state = aCtx->CurrentState(); |
288 | 0 |
|
289 | 0 | if (state.StyleIsColor(aStyle)) { |
290 | 0 | mPattern.InitColorPattern(ToDeviceColor(state.colorStyles[aStyle])); |
291 | 0 | } else if (state.gradientStyles[aStyle] && |
292 | 0 | state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::LINEAR) { |
293 | 0 | auto gradient = |
294 | 0 | static_cast<CanvasLinearGradient*>(state.gradientStyles[aStyle].get()); |
295 | 0 |
|
296 | 0 | mPattern.InitLinearGradientPattern(gradient->mBegin, gradient->mEnd, |
297 | 0 | gradient->GetGradientStopsForTarget(aRT)); |
298 | 0 | } else if (state.gradientStyles[aStyle] && |
299 | 0 | state.gradientStyles[aStyle]->GetType() == CanvasGradient::Type::RADIAL) { |
300 | 0 | auto gradient = |
301 | 0 | static_cast<CanvasRadialGradient*>(state.gradientStyles[aStyle].get()); |
302 | 0 |
|
303 | 0 | mPattern.InitRadialGradientPattern(gradient->mCenter1, gradient->mCenter2, |
304 | 0 | gradient->mRadius1, gradient->mRadius2, |
305 | 0 | gradient->GetGradientStopsForTarget(aRT)); |
306 | 0 | } else if (state.patternStyles[aStyle]) { |
307 | 0 | if (aCtx->mCanvasElement) { |
308 | 0 | CanvasUtils::DoDrawImageSecurityCheck(aCtx->mCanvasElement, |
309 | 0 | state.patternStyles[aStyle]->mPrincipal, |
310 | 0 | state.patternStyles[aStyle]->mForceWriteOnly, |
311 | 0 | state.patternStyles[aStyle]->mCORSUsed); |
312 | 0 | } |
313 | 0 |
|
314 | 0 | ExtendMode mode; |
315 | 0 | if (state.patternStyles[aStyle]->mRepeat == CanvasPattern::RepeatMode::NOREPEAT) { |
316 | 0 | mode = ExtendMode::CLAMP; |
317 | 0 | } else { |
318 | 0 | mode = ExtendMode::REPEAT; |
319 | 0 | } |
320 | 0 |
|
321 | 0 | SamplingFilter samplingFilter; |
322 | 0 | if (state.imageSmoothingEnabled) { |
323 | 0 | samplingFilter = SamplingFilter::GOOD; |
324 | 0 | } else { |
325 | 0 | samplingFilter = SamplingFilter::POINT; |
326 | 0 | } |
327 | 0 |
|
328 | 0 | mPattern.InitSurfacePattern(state.patternStyles[aStyle]->mSurface, mode, |
329 | 0 | state.patternStyles[aStyle]->mTransform, |
330 | 0 | samplingFilter); |
331 | 0 | } |
332 | 0 |
|
333 | 0 | return *mPattern.GetPattern(); |
334 | 0 | } |
335 | | |
336 | | GeneralPattern mPattern; |
337 | | }; |
338 | | |
339 | | /* This is an RAII based class that can be used as a drawtarget for |
340 | | * operations that need to have a filter applied to their results. |
341 | | * All coordinates passed to the constructor are in device space. |
342 | | */ |
343 | | class AdjustedTargetForFilter |
344 | | { |
345 | | public: |
346 | | typedef CanvasRenderingContext2D::ContextState ContextState; |
347 | | |
348 | | AdjustedTargetForFilter(CanvasRenderingContext2D* aCtx, |
349 | | DrawTarget* aFinalTarget, |
350 | | const gfx::IntPoint& aFilterSpaceToTargetOffset, |
351 | | const gfx::IntRect& aPreFilterBounds, |
352 | | const gfx::IntRect& aPostFilterBounds, |
353 | | gfx::CompositionOp aCompositionOp) |
354 | | : mFinalTarget(aFinalTarget) |
355 | | , mCtx(aCtx) |
356 | | , mPostFilterBounds(aPostFilterBounds) |
357 | | , mOffset(aFilterSpaceToTargetOffset) |
358 | | , mCompositionOp(aCompositionOp) |
359 | 0 | { |
360 | 0 | nsIntRegion sourceGraphicNeededRegion; |
361 | 0 | nsIntRegion fillPaintNeededRegion; |
362 | 0 | nsIntRegion strokePaintNeededRegion; |
363 | 0 |
|
364 | 0 | FilterSupport::ComputeSourceNeededRegions( |
365 | 0 | aCtx->CurrentState().filter, mPostFilterBounds, |
366 | 0 | sourceGraphicNeededRegion, fillPaintNeededRegion, |
367 | 0 | strokePaintNeededRegion); |
368 | 0 |
|
369 | 0 | mSourceGraphicRect = sourceGraphicNeededRegion.GetBounds(); |
370 | 0 | mFillPaintRect = fillPaintNeededRegion.GetBounds(); |
371 | 0 | mStrokePaintRect = strokePaintNeededRegion.GetBounds(); |
372 | 0 |
|
373 | 0 | mSourceGraphicRect = mSourceGraphicRect.Intersect(aPreFilterBounds); |
374 | 0 |
|
375 | 0 | if (mSourceGraphicRect.IsEmpty()) { |
376 | 0 | // The filter might not make any use of the source graphic. We need to |
377 | 0 | // create a DrawTarget that we can return from DT() anyway, so we'll |
378 | 0 | // just use a 1x1-sized one. |
379 | 0 | mSourceGraphicRect.SizeTo(1, 1); |
380 | 0 | } |
381 | 0 |
|
382 | 0 | mTarget = mFinalTarget->CreateSimilarDrawTarget(mSourceGraphicRect.Size(), |
383 | 0 | SurfaceFormat::B8G8R8A8); |
384 | 0 |
|
385 | 0 | if (!mTarget) { |
386 | 0 | // XXX - Deal with the situation where our temp size is too big to |
387 | 0 | // fit in a texture (bug 1066622). |
388 | 0 | mTarget = mFinalTarget; |
389 | 0 | mCtx = nullptr; |
390 | 0 | mFinalTarget = nullptr; |
391 | 0 | return; |
392 | 0 | } |
393 | 0 | |
394 | 0 | mTarget->SetTransform( |
395 | 0 | mFinalTarget->GetTransform().PostTranslate(-mSourceGraphicRect.TopLeft() + mOffset)); |
396 | 0 | } |
397 | | |
398 | | // Return a SourceSurface that contains the FillPaint or StrokePaint source. |
399 | | already_AddRefed<SourceSurface> |
400 | | DoSourcePaint(gfx::IntRect& aRect, CanvasRenderingContext2D::Style aStyle) |
401 | 0 | { |
402 | 0 | if (aRect.IsEmpty()) { |
403 | 0 | return nullptr; |
404 | 0 | } |
405 | 0 | |
406 | 0 | RefPtr<DrawTarget> dt = |
407 | 0 | mFinalTarget->CreateSimilarDrawTarget(aRect.Size(), SurfaceFormat::B8G8R8A8); |
408 | 0 | if (!dt) { |
409 | 0 | aRect.SetEmpty(); |
410 | 0 | return nullptr; |
411 | 0 | } |
412 | 0 | |
413 | 0 | Matrix transform = |
414 | 0 | mFinalTarget->GetTransform().PostTranslate(-aRect.TopLeft() + mOffset); |
415 | 0 |
|
416 | 0 | dt->SetTransform(transform); |
417 | 0 |
|
418 | 0 | if (transform.Invert()) { |
419 | 0 | gfx::Rect dtBounds(0, 0, aRect.width, aRect.height); |
420 | 0 | gfx::Rect fillRect = transform.TransformBounds(dtBounds); |
421 | 0 | dt->FillRect(fillRect, CanvasGeneralPattern().ForStyle(mCtx, aStyle, dt)); |
422 | 0 | } |
423 | 0 | return dt->Snapshot(); |
424 | 0 | } |
425 | | |
426 | | ~AdjustedTargetForFilter() |
427 | 0 | { |
428 | 0 | if (!mCtx) { |
429 | 0 | return; |
430 | 0 | } |
431 | 0 | |
432 | 0 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
433 | 0 |
|
434 | 0 | RefPtr<SourceSurface> fillPaint = |
435 | 0 | DoSourcePaint(mFillPaintRect, CanvasRenderingContext2D::Style::FILL); |
436 | 0 | RefPtr<SourceSurface> strokePaint = |
437 | 0 | DoSourcePaint(mStrokePaintRect, CanvasRenderingContext2D::Style::STROKE); |
438 | 0 |
|
439 | 0 | AutoRestoreTransform autoRestoreTransform(mFinalTarget); |
440 | 0 | mFinalTarget->SetTransform(Matrix()); |
441 | 0 |
|
442 | 0 | MOZ_RELEASE_ASSERT(!mCtx->CurrentState().filter.mPrimitives.IsEmpty()); |
443 | 0 | gfx::FilterSupport::RenderFilterDescription( |
444 | 0 | mFinalTarget, mCtx->CurrentState().filter, |
445 | 0 | gfx::Rect(mPostFilterBounds), |
446 | 0 | snapshot, mSourceGraphicRect, |
447 | 0 | fillPaint, mFillPaintRect, |
448 | 0 | strokePaint, mStrokePaintRect, |
449 | 0 | mCtx->CurrentState().filterAdditionalImages, |
450 | 0 | mPostFilterBounds.TopLeft() - mOffset, |
451 | 0 | DrawOptions(1.0f, mCompositionOp)); |
452 | 0 |
|
453 | 0 | const gfx::FilterDescription& filter = mCtx->CurrentState().filter; |
454 | 0 | MOZ_RELEASE_ASSERT(!filter.mPrimitives.IsEmpty()); |
455 | 0 | if (filter.mPrimitives.LastElement().IsTainted() && mCtx->mCanvasElement) { |
456 | 0 | mCtx->mCanvasElement->SetWriteOnly(); |
457 | 0 | } |
458 | 0 | } |
459 | | |
460 | | DrawTarget* DT() |
461 | 0 | { |
462 | 0 | return mTarget; |
463 | 0 | } |
464 | | |
465 | | private: |
466 | | RefPtr<DrawTarget> mTarget; |
467 | | RefPtr<DrawTarget> mFinalTarget; |
468 | | CanvasRenderingContext2D* mCtx; |
469 | | gfx::IntRect mSourceGraphicRect; |
470 | | gfx::IntRect mFillPaintRect; |
471 | | gfx::IntRect mStrokePaintRect; |
472 | | gfx::IntRect mPostFilterBounds; |
473 | | gfx::IntPoint mOffset; |
474 | | gfx::CompositionOp mCompositionOp; |
475 | | }; |
476 | | |
477 | | /* This is an RAII based class that can be used as a drawtarget for |
478 | | * operations that need to have a shadow applied to their results. |
479 | | * All coordinates passed to the constructor are in device space. |
480 | | */ |
481 | | class AdjustedTargetForShadow |
482 | | { |
483 | | public: |
484 | | typedef CanvasRenderingContext2D::ContextState ContextState; |
485 | | |
486 | | AdjustedTargetForShadow(CanvasRenderingContext2D* aCtx, |
487 | | DrawTarget* aFinalTarget, |
488 | | const gfx::Rect& aBounds, |
489 | | gfx::CompositionOp aCompositionOp) |
490 | | : mFinalTarget(aFinalTarget) |
491 | | , mCtx(aCtx) |
492 | | , mCompositionOp(aCompositionOp) |
493 | 0 | { |
494 | 0 | const ContextState& state = mCtx->CurrentState(); |
495 | 0 | mSigma = state.ShadowBlurSigma(); |
496 | 0 |
|
497 | 0 | // We actually include the bounds of the shadow blur, this makes it |
498 | 0 | // easier to execute the actual blur on hardware, and shouldn't affect |
499 | 0 | // the amount of pixels that need to be touched. |
500 | 0 | gfx::Rect bounds = aBounds; |
501 | 0 | int32_t blurRadius = state.ShadowBlurRadius(); |
502 | 0 | bounds.Inflate(blurRadius); |
503 | 0 | bounds.RoundOut(); |
504 | 0 | bounds.ToIntRect(&mTempRect); |
505 | 0 |
|
506 | 0 | mTarget = |
507 | 0 | mFinalTarget->CreateShadowDrawTarget(mTempRect.Size(), |
508 | 0 | SurfaceFormat::B8G8R8A8, mSigma); |
509 | 0 |
|
510 | 0 | if (!mTarget) { |
511 | 0 | // XXX - Deal with the situation where our temp size is too big to |
512 | 0 | // fit in a texture (bug 1066622). |
513 | 0 | mTarget = mFinalTarget; |
514 | 0 | mCtx = nullptr; |
515 | 0 | mFinalTarget = nullptr; |
516 | 0 | } else { |
517 | 0 | mTarget->SetTransform( |
518 | 0 | mFinalTarget->GetTransform().PostTranslate(-mTempRect.TopLeft())); |
519 | 0 | } |
520 | 0 | } |
521 | | |
522 | | ~AdjustedTargetForShadow() |
523 | 0 | { |
524 | 0 | if (!mCtx) { |
525 | 0 | return; |
526 | 0 | } |
527 | 0 | |
528 | 0 | RefPtr<SourceSurface> snapshot = mTarget->Snapshot(); |
529 | 0 |
|
530 | 0 | mFinalTarget->DrawSurfaceWithShadow(snapshot, mTempRect.TopLeft(), |
531 | 0 | Color::FromABGR(mCtx->CurrentState().shadowColor), |
532 | 0 | mCtx->CurrentState().shadowOffset, mSigma, |
533 | 0 | mCompositionOp); |
534 | 0 | } |
535 | | |
536 | | DrawTarget* DT() |
537 | 0 | { |
538 | 0 | return mTarget; |
539 | 0 | } |
540 | | |
541 | | gfx::IntPoint OffsetToFinalDT() |
542 | 0 | { |
543 | 0 | return mTempRect.TopLeft(); |
544 | 0 | } |
545 | | |
546 | | private: |
547 | | RefPtr<DrawTarget> mTarget; |
548 | | RefPtr<DrawTarget> mFinalTarget; |
549 | | CanvasRenderingContext2D* mCtx; |
550 | | Float mSigma; |
551 | | gfx::IntRect mTempRect; |
552 | | gfx::CompositionOp mCompositionOp; |
553 | | }; |
554 | | |
555 | | /* |
556 | | * This is an RAII based class that can be used as a drawtarget for |
557 | | * operations that need a shadow or a filter drawn. It will automatically |
558 | | * provide a temporary target when needed, and if so blend it back with a |
559 | | * shadow, filter, or both. |
560 | | * If both a shadow and a filter are needed, the filter is applied first, |
561 | | * and the shadow is applied to the filtered results. |
562 | | * |
563 | | * aBounds specifies the bounds of the drawing operation that will be |
564 | | * drawn to the target, it is given in device space! If this is nullptr the |
565 | | * drawing operation will be assumed to cover the whole canvas. |
566 | | */ |
567 | | class AdjustedTarget |
568 | | { |
569 | | public: |
570 | | typedef CanvasRenderingContext2D::ContextState ContextState; |
571 | | |
572 | | explicit AdjustedTarget(CanvasRenderingContext2D* aCtx, |
573 | | const gfx::Rect* aBounds = nullptr) |
574 | 0 | { |
575 | 0 | // All rects in this function are in the device space of ctx->mTarget. |
576 | 0 |
|
577 | 0 | // In order to keep our temporary surfaces as small as possible, we first |
578 | 0 | // calculate what their maximum required bounds would need to be if we |
579 | 0 | // were to fill the whole canvas. Everything outside those bounds we don't |
580 | 0 | // need to render. |
581 | 0 | gfx::Rect r(0, 0, aCtx->mWidth, aCtx->mHeight); |
582 | 0 | gfx::Rect maxSourceNeededBoundsForShadow = |
583 | 0 | MaxSourceNeededBoundsForShadow(r, aCtx); |
584 | 0 | gfx::Rect maxSourceNeededBoundsForFilter = |
585 | 0 | MaxSourceNeededBoundsForFilter(maxSourceNeededBoundsForShadow, aCtx); |
586 | 0 | if (!aCtx->IsTargetValid()) { |
587 | 0 | return; |
588 | 0 | } |
589 | 0 | |
590 | 0 | gfx::Rect bounds = maxSourceNeededBoundsForFilter; |
591 | 0 | if (aBounds) { |
592 | 0 | bounds = bounds.Intersect(*aBounds); |
593 | 0 | } |
594 | 0 | gfx::Rect boundsAfterFilter = BoundsAfterFilter(bounds, aCtx); |
595 | 0 | if (!aCtx->IsTargetValid()) { |
596 | 0 | return; |
597 | 0 | } |
598 | 0 | |
599 | 0 | mozilla::gfx::CompositionOp op = aCtx->CurrentState().op; |
600 | 0 |
|
601 | 0 | gfx::IntPoint offsetToFinalDT; |
602 | 0 |
|
603 | 0 | // First set up the shadow draw target, because the shadow goes outside. |
604 | 0 | // It applies to the post-filter results, if both a filter and a shadow |
605 | 0 | // are used. |
606 | 0 | if (aCtx->NeedToDrawShadow()) { |
607 | 0 | mShadowTarget = MakeUnique<AdjustedTargetForShadow>( |
608 | 0 | aCtx, aCtx->mTarget, boundsAfterFilter, op); |
609 | 0 | mTarget = mShadowTarget->DT(); |
610 | 0 | offsetToFinalDT = mShadowTarget->OffsetToFinalDT(); |
611 | 0 |
|
612 | 0 | // If we also have a filter, the filter needs to be drawn with OP_OVER |
613 | 0 | // because shadow drawing already applies op on the result. |
614 | 0 | op = gfx::CompositionOp::OP_OVER; |
615 | 0 | } |
616 | 0 |
|
617 | 0 | // Now set up the filter draw target. |
618 | 0 | const bool applyFilter = aCtx->NeedToApplyFilter(); |
619 | 0 | if (!aCtx->IsTargetValid()) { |
620 | 0 | return; |
621 | 0 | } |
622 | 0 | if (applyFilter) { |
623 | 0 | bounds.RoundOut(); |
624 | 0 |
|
625 | 0 | if (!mTarget) { |
626 | 0 | mTarget = aCtx->mTarget; |
627 | 0 | } |
628 | 0 | gfx::IntRect intBounds; |
629 | 0 | if (!bounds.ToIntRect(&intBounds)) { |
630 | 0 | return; |
631 | 0 | } |
632 | 0 | mFilterTarget = MakeUnique<AdjustedTargetForFilter>( |
633 | 0 | aCtx, mTarget, offsetToFinalDT, intBounds, |
634 | 0 | gfx::RoundedToInt(boundsAfterFilter), op); |
635 | 0 | mTarget = mFilterTarget->DT(); |
636 | 0 | } |
637 | 0 | if (!mTarget) { |
638 | 0 | mTarget = aCtx->mTarget; |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | | ~AdjustedTarget() |
643 | 0 | { |
644 | 0 | // The order in which the targets are finalized is important. |
645 | 0 | // Filters are inside, any shadow applies to the post-filter results. |
646 | 0 | mFilterTarget.reset(); |
647 | 0 | mShadowTarget.reset(); |
648 | 0 | } |
649 | | |
650 | | operator DrawTarget*() |
651 | 0 | { |
652 | 0 | return mTarget; |
653 | 0 | } |
654 | | |
655 | | DrawTarget* operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN |
656 | 0 | { |
657 | 0 | return mTarget; |
658 | 0 | } |
659 | | |
660 | | private: |
661 | | |
662 | | gfx::Rect |
663 | | MaxSourceNeededBoundsForFilter(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx) |
664 | 0 | { |
665 | 0 | const bool applyFilter = aCtx->NeedToApplyFilter(); |
666 | 0 | if (!aCtx->IsTargetValid()) { |
667 | 0 | return aDestBounds; |
668 | 0 | } |
669 | 0 | if (!applyFilter) { |
670 | 0 | return aDestBounds; |
671 | 0 | } |
672 | 0 | |
673 | 0 | nsIntRegion sourceGraphicNeededRegion; |
674 | 0 | nsIntRegion fillPaintNeededRegion; |
675 | 0 | nsIntRegion strokePaintNeededRegion; |
676 | 0 |
|
677 | 0 | FilterSupport::ComputeSourceNeededRegions( |
678 | 0 | aCtx->CurrentState().filter, gfx::RoundedToInt(aDestBounds), |
679 | 0 | sourceGraphicNeededRegion, fillPaintNeededRegion, strokePaintNeededRegion); |
680 | 0 |
|
681 | 0 | return gfx::Rect(sourceGraphicNeededRegion.GetBounds()); |
682 | 0 | } |
683 | | |
684 | | gfx::Rect |
685 | | MaxSourceNeededBoundsForShadow(const gfx::Rect& aDestBounds, CanvasRenderingContext2D* aCtx) |
686 | 0 | { |
687 | 0 | if (!aCtx->NeedToDrawShadow()) { |
688 | 0 | return aDestBounds; |
689 | 0 | } |
690 | 0 | |
691 | 0 | const ContextState& state = aCtx->CurrentState(); |
692 | 0 | gfx::Rect sourceBounds = aDestBounds - state.shadowOffset; |
693 | 0 | sourceBounds.Inflate(state.ShadowBlurRadius()); |
694 | 0 |
|
695 | 0 | // Union the shadow source with the original rect because we're going to |
696 | 0 | // draw both. |
697 | 0 | return sourceBounds.Union(aDestBounds); |
698 | 0 | } |
699 | | |
700 | | gfx::Rect |
701 | | BoundsAfterFilter(const gfx::Rect& aBounds, CanvasRenderingContext2D* aCtx) |
702 | 0 | { |
703 | 0 | const bool applyFilter = aCtx->NeedToApplyFilter(); |
704 | 0 | if (!aCtx->IsTargetValid()) { |
705 | 0 | return aBounds; |
706 | 0 | } |
707 | 0 | if (!applyFilter) { |
708 | 0 | return aBounds; |
709 | 0 | } |
710 | 0 | |
711 | 0 | gfx::Rect bounds(aBounds); |
712 | 0 | bounds.RoundOut(); |
713 | 0 |
|
714 | 0 | gfx::IntRect intBounds; |
715 | 0 | if (!bounds.ToIntRect(&intBounds)) { |
716 | 0 | return gfx::Rect(); |
717 | 0 | } |
718 | 0 | |
719 | 0 | nsIntRegion extents = |
720 | 0 | gfx::FilterSupport::ComputePostFilterExtents(aCtx->CurrentState().filter, |
721 | 0 | intBounds); |
722 | 0 | return gfx::Rect(extents.GetBounds()); |
723 | 0 | } |
724 | | |
725 | | RefPtr<DrawTarget> mTarget; |
726 | | UniquePtr<AdjustedTargetForShadow> mShadowTarget; |
727 | | UniquePtr<AdjustedTargetForFilter> mFilterTarget; |
728 | | }; |
729 | | |
730 | | void |
731 | | CanvasPattern::SetTransform(SVGMatrix& aMatrix) |
732 | 0 | { |
733 | 0 | mTransform = ToMatrix(aMatrix.GetMatrix()); |
734 | 0 | } |
735 | | |
736 | | void |
737 | | CanvasGradient::AddColorStop(float aOffset, const nsAString& aColorstr, ErrorResult& aRv) |
738 | 0 | { |
739 | 0 | if (aOffset < 0.0 || aOffset > 1.0) { |
740 | 0 | aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
741 | 0 | return; |
742 | 0 | } |
743 | 0 | |
744 | 0 | nsIPresShell* shell = mContext ? mContext->GetPresShell() : nullptr; |
745 | 0 | ServoStyleSet* styleSet = shell ? shell->StyleSet() : nullptr; |
746 | 0 |
|
747 | 0 | nscolor color; |
748 | 0 | bool ok = ServoCSSParser::ComputeColor(styleSet, NS_RGB(0, 0, 0), |
749 | 0 | aColorstr, &color); |
750 | 0 |
|
751 | 0 | if (!ok) { |
752 | 0 | aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
753 | 0 | return; |
754 | 0 | } |
755 | 0 | |
756 | 0 | mStops = nullptr; |
757 | 0 |
|
758 | 0 | GradientStop newStop; |
759 | 0 |
|
760 | 0 | newStop.offset = aOffset; |
761 | 0 | newStop.color = Color::FromABGR(color); |
762 | 0 |
|
763 | 0 | mRawStops.AppendElement(newStop); |
764 | 0 | } |
765 | | |
766 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasGradient, AddRef) |
767 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasGradient, Release) |
768 | | |
769 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasGradient, mContext) |
770 | | |
771 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef) |
772 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release) |
773 | | |
774 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext) |
775 | | |
776 | | class CanvasShutdownObserver final : public nsIObserver |
777 | | { |
778 | | public: |
779 | | explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas) |
780 | | : mCanvas(aCanvas) |
781 | 0 | {} |
782 | | |
783 | | NS_DECL_ISUPPORTS |
784 | | NS_DECL_NSIOBSERVER |
785 | | private: |
786 | 0 | ~CanvasShutdownObserver() {} |
787 | | |
788 | | CanvasRenderingContext2D* mCanvas; |
789 | | }; |
790 | | |
791 | | NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver) |
792 | | |
793 | | NS_IMETHODIMP |
794 | | CanvasShutdownObserver::Observe(nsISupports* aSubject, |
795 | | const char* aTopic, |
796 | | const char16_t* aData) |
797 | 0 | { |
798 | 0 | if (mCanvas && strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { |
799 | 0 | mCanvas->OnShutdown(); |
800 | 0 | nsContentUtils::UnregisterShutdownObserver(this); |
801 | 0 | } |
802 | 0 |
|
803 | 0 | return NS_OK; |
804 | 0 | } |
805 | | |
806 | | class CanvasDrawObserver |
807 | | { |
808 | | public: |
809 | | explicit CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext); |
810 | | |
811 | | // Only enumerate draw calls that could affect the heuristic |
812 | | enum DrawCallType { |
813 | | PutImageData, |
814 | | GetImageData, |
815 | | DrawImage |
816 | | }; |
817 | | |
818 | | // This is the one that we call on relevant draw calls and count |
819 | | // GPU vs. CPU preferrable calls... |
820 | | void DidDrawCall(DrawCallType aType); |
821 | | |
822 | | // When this returns true, the observer is done making the decisions. |
823 | | // Right now, we expect to get rid of the observer after the FrameEnd |
824 | | // returns true, though the decision could eventually change if the |
825 | | // function calls shift. If we change to monitor the functions called |
826 | | // and make decisions to change more than once, we would probably want |
827 | | // FrameEnd to reset the timer and counters as it returns true. |
828 | | bool FrameEnd(); |
829 | | |
830 | | private: |
831 | | // These values will be picked up from preferences: |
832 | | int32_t mMinFramesBeforeDecision; |
833 | | float mMinSecondsBeforeDecision; |
834 | | int32_t mMinCallsBeforeDecision; |
835 | | |
836 | | CanvasRenderingContext2D* mCanvasContext; |
837 | | int32_t mSoftwarePreferredCalls; |
838 | | int32_t mGPUPreferredCalls; |
839 | | int32_t mFramesRendered; |
840 | | TimeStamp mCreationTime; |
841 | | }; |
842 | | |
843 | | // We are not checking for the validity of the preference values. For example, |
844 | | // negative values will have an effect of a quick exit, so no harm done. |
845 | | CanvasDrawObserver::CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext) |
846 | | : mMinFramesBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinFrames()) |
847 | | , mMinSecondsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinSeconds()) |
848 | | , mMinCallsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinCalls()) |
849 | | , mCanvasContext(aCanvasContext) |
850 | | , mSoftwarePreferredCalls(0) |
851 | | , mGPUPreferredCalls(0) |
852 | | , mFramesRendered(0) |
853 | | , mCreationTime(TimeStamp::NowLoRes()) |
854 | 0 | {} |
855 | | |
856 | | void |
857 | | CanvasDrawObserver::DidDrawCall(DrawCallType aType) |
858 | 0 | { |
859 | 0 | switch (aType) { |
860 | 0 | case PutImageData: |
861 | 0 | case GetImageData: |
862 | 0 | if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) { |
863 | 0 | mCreationTime = TimeStamp::NowLoRes(); |
864 | 0 | } |
865 | 0 | mSoftwarePreferredCalls++; |
866 | 0 | break; |
867 | 0 | case DrawImage: |
868 | 0 | if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) { |
869 | 0 | mCreationTime = TimeStamp::NowLoRes(); |
870 | 0 | } |
871 | 0 | mGPUPreferredCalls++; |
872 | 0 | break; |
873 | 0 | } |
874 | 0 | } |
875 | | |
876 | | // If we return true, the observer is done making the decisions... |
877 | | bool |
878 | | CanvasDrawObserver::FrameEnd() |
879 | 0 | { |
880 | 0 | mFramesRendered++; |
881 | 0 |
|
882 | 0 | // We log the first mMinFramesBeforeDecision frames of any |
883 | 0 | // canvas object then make a call to determine whether it should |
884 | 0 | // be GPU or CPU backed |
885 | 0 | if ((mFramesRendered >= mMinFramesBeforeDecision) || |
886 | 0 | ((TimeStamp::NowLoRes() - mCreationTime).ToSeconds()) > mMinSecondsBeforeDecision) { |
887 | 0 |
|
888 | 0 | // If we don't have enough data, don't bother changing... |
889 | 0 | if (mGPUPreferredCalls > mMinCallsBeforeDecision || |
890 | 0 | mSoftwarePreferredCalls > mMinCallsBeforeDecision) { |
891 | 0 | CanvasRenderingContext2D::RenderingMode switchToMode; |
892 | 0 | if (mGPUPreferredCalls >= mSoftwarePreferredCalls) { |
893 | 0 | switchToMode = CanvasRenderingContext2D::RenderingMode::OpenGLBackendMode; |
894 | 0 | } else { |
895 | 0 | switchToMode = CanvasRenderingContext2D::RenderingMode::SoftwareBackendMode; |
896 | 0 | } |
897 | 0 | if (switchToMode != mCanvasContext->mRenderingMode) { |
898 | 0 | if (!mCanvasContext->SwitchRenderingMode(switchToMode)) { |
899 | 0 | gfxDebug() << "Canvas acceleration failed mode switch to " << switchToMode; |
900 | 0 | } |
901 | 0 | } |
902 | 0 | } |
903 | 0 |
|
904 | 0 | // If we ever redesign this class to constantly monitor the functions |
905 | 0 | // and keep making decisions, we would probably want to reset the counters |
906 | 0 | // and the timers here... |
907 | 0 | return true; |
908 | 0 | } |
909 | 0 | return false; |
910 | 0 | } |
911 | | |
912 | | class CanvasRenderingContext2DUserData : public LayerUserData { |
913 | | public: |
914 | | explicit CanvasRenderingContext2DUserData(CanvasRenderingContext2D* aContext) |
915 | | : mContext(aContext) |
916 | 0 | { |
917 | 0 | aContext->mUserDatas.AppendElement(this); |
918 | 0 | } |
919 | | ~CanvasRenderingContext2DUserData() |
920 | 0 | { |
921 | 0 | if (mContext) { |
922 | 0 | mContext->mUserDatas.RemoveElement(this); |
923 | 0 | } |
924 | 0 | } |
925 | | |
926 | | static void PreTransactionCallback(void* aData) |
927 | 0 | { |
928 | 0 | CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(aData); |
929 | 0 | if (!context || !context->mTarget) |
930 | 0 | return; |
931 | 0 | |
932 | 0 | context->OnStableState(); |
933 | 0 | } |
934 | | |
935 | | static void DidTransactionCallback(void* aData) |
936 | 0 | { |
937 | 0 | CanvasRenderingContext2D* context = static_cast<CanvasRenderingContext2D*>(aData); |
938 | 0 | if (context) { |
939 | 0 | context->MarkContextClean(); |
940 | 0 | if (context->mDrawObserver) { |
941 | 0 | if (context->mDrawObserver->FrameEnd()) { |
942 | 0 | // Note that this call deletes and nulls out mDrawObserver: |
943 | 0 | context->RemoveDrawObserver(); |
944 | 0 | } |
945 | 0 | } |
946 | 0 | } |
947 | 0 | } |
948 | | bool IsForContext(CanvasRenderingContext2D* aContext) |
949 | 0 | { |
950 | 0 | return mContext == aContext; |
951 | 0 | } |
952 | | void Forget() |
953 | 0 | { |
954 | 0 | mContext = nullptr; |
955 | 0 | } |
956 | | |
957 | | private: |
958 | | CanvasRenderingContext2D* mContext; |
959 | | }; |
960 | | |
961 | | class SVGFilterObserverListForCanvas final : public SVGFilterObserverList |
962 | | { |
963 | | public: |
964 | | SVGFilterObserverListForCanvas(nsTArray<nsStyleFilter>& aFilters, |
965 | | Element* aCanvasElement, |
966 | | CanvasRenderingContext2D* aContext) |
967 | | : SVGFilterObserverList(aFilters, aCanvasElement) |
968 | | , mContext(aContext) |
969 | 0 | { |
970 | 0 | } |
971 | | |
972 | | virtual void OnRenderingChange() override |
973 | 0 | { |
974 | 0 | if (!mContext) { |
975 | 0 | MOZ_CRASH("GFX: This should never be called without a context"); |
976 | 0 | } |
977 | 0 | // Refresh the cached FilterDescription in mContext->CurrentState().filter. |
978 | 0 | // If this filter is not at the top of the state stack, we'll refresh the |
979 | 0 | // wrong filter, but that's ok, because we'll refresh the right filter |
980 | 0 | // when we pop the state stack in CanvasRenderingContext2D::Restore(). |
981 | 0 | RefPtr<CanvasRenderingContext2D> kungFuDeathGrip(mContext); |
982 | 0 | kungFuDeathGrip->UpdateFilter(); |
983 | 0 | } |
984 | | |
985 | 0 | void DetachFromContext() { mContext = nullptr; } |
986 | | |
987 | | private: |
988 | | CanvasRenderingContext2D* mContext; |
989 | | }; |
990 | | |
991 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) |
992 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) |
993 | | |
994 | | NS_IMPL_CYCLE_COLLECTION_CLASS(CanvasRenderingContext2D) |
995 | | |
996 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) |
997 | 0 | // Make sure we remove ourselves from the list of demotable contexts (raw pointers), |
998 | 0 | // since we're logically destructed at this point. |
999 | 0 | CanvasRenderingContext2D::RemoveDemotableContext(tmp); |
1000 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCanvasElement) |
1001 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) |
1002 | 0 | for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { |
1003 | 0 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::STROKE]); |
1004 | 0 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); |
1005 | 0 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]); |
1006 | 0 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); |
1007 | 0 | auto filterObserverList = |
1008 | 0 | static_cast<SVGFilterObserverListForCanvas*>(tmp->mStyleStack[i].filterObserverList.get()); |
1009 | 0 | if (filterObserverList) { |
1010 | 0 | filterObserverList->DetachFromContext(); |
1011 | 0 | } |
1012 | 0 | ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterObserverList); |
1013 | 0 | } |
1014 | 0 | for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { |
1015 | 0 | RegionInfo& info = tmp->mHitRegionsOptions[x]; |
1016 | 0 | if (info.mElement) { |
1017 | 0 | ImplCycleCollectionUnlink(info.mElement); |
1018 | 0 | } |
1019 | 0 | } |
1020 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
1021 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
1022 | | |
1023 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) |
1024 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCanvasElement) |
1025 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) |
1026 | 0 | for (uint32_t i = 0; i < tmp->mStyleStack.Length(); i++) { |
1027 | 0 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::STROKE], "Stroke CanvasPattern"); |
1028 | 0 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern"); |
1029 | 0 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient"); |
1030 | 0 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient"); |
1031 | 0 | ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterObserverList, "Filter Observer List"); |
1032 | 0 | } |
1033 | 0 | for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { |
1034 | 0 | RegionInfo& info = tmp->mHitRegionsOptions[x]; |
1035 | 0 | if (info.mElement) { |
1036 | 0 | ImplCycleCollectionTraverse(cb, info.mElement, "Hit region fallback element"); |
1037 | 0 | } |
1038 | 0 | } |
1039 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
1040 | | |
1041 | | NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(CanvasRenderingContext2D) |
1042 | | |
1043 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CanvasRenderingContext2D) |
1044 | 0 | if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) { |
1045 | 0 | dom::Element* canvasElement = tmp->mCanvasElement; |
1046 | 0 | if (canvasElement) { |
1047 | 0 | if (canvasElement->IsPurple()) { |
1048 | 0 | canvasElement->RemovePurple(); |
1049 | 0 | } |
1050 | 0 | dom::Element::MarkNodeChildren(canvasElement); |
1051 | 0 | } |
1052 | 0 | return true; |
1053 | 0 | } |
1054 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END |
1055 | 0 |
|
1056 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CanvasRenderingContext2D) |
1057 | 0 | return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); |
1058 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END |
1059 | | |
1060 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CanvasRenderingContext2D) |
1061 | 0 | return nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper(); |
1062 | 0 | NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END |
1063 | | |
1064 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanvasRenderingContext2D) |
1065 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
1066 | 0 | NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) |
1067 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
1068 | 0 | NS_INTERFACE_MAP_END |
1069 | | |
1070 | | /** |
1071 | | ** CanvasRenderingContext2D impl |
1072 | | **/ |
1073 | | |
1074 | | |
1075 | | // Initialize our static variables. |
1076 | | uintptr_t CanvasRenderingContext2D::sNumLivingContexts = 0; |
1077 | | DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr; |
1078 | | static bool sMaxContextsInitialized = false; |
1079 | | static int32_t sMaxContexts = 0; |
1080 | | |
1081 | | |
1082 | | |
1083 | | CanvasRenderingContext2D::CanvasRenderingContext2D(layers::LayersBackend aCompositorBackend) |
1084 | | : mRenderingMode(RenderingMode::OpenGLBackendMode) |
1085 | | , mCompositorBackend(aCompositorBackend) |
1086 | | // these are the default values from the Canvas spec |
1087 | | , mWidth(0), mHeight(0) |
1088 | | , mZero(false) |
1089 | | , mOpaqueAttrValue(false) |
1090 | | , mContextAttributesHasAlpha(true) |
1091 | | , mOpaque(false) |
1092 | | , mResetLayer(true) |
1093 | | , mIPC(false) |
1094 | | , mIsSkiaGL(false) |
1095 | | , mHasPendingStableStateCallback(false) |
1096 | | , mDrawObserver(nullptr) |
1097 | | , mIsEntireFrameInvalid(false) |
1098 | | , mPredictManyRedrawCalls(false) |
1099 | | , mIsCapturedFrameInvalid(false) |
1100 | | , mPathTransformWillUpdate(false) |
1101 | | , mInvalidateCount(0) |
1102 | 0 | { |
1103 | 0 | if (!sMaxContextsInitialized) { |
1104 | 0 | sMaxContexts = gfxPrefs::CanvasAzureAcceleratedLimit(); |
1105 | 0 | sMaxContextsInitialized = true; |
1106 | 0 | } |
1107 | 0 |
|
1108 | 0 | sNumLivingContexts++; |
1109 | 0 |
|
1110 | 0 | mShutdownObserver = new CanvasShutdownObserver(this); |
1111 | 0 | nsContentUtils::RegisterShutdownObserver(mShutdownObserver); |
1112 | 0 |
|
1113 | 0 | // The default is to use OpenGL mode |
1114 | 0 | if (AllowOpenGLCanvas()) { |
1115 | 0 | mDrawObserver = new CanvasDrawObserver(this); |
1116 | 0 | } else { |
1117 | 0 | mRenderingMode = RenderingMode::SoftwareBackendMode; |
1118 | 0 | } |
1119 | 0 | } |
1120 | | |
1121 | | CanvasRenderingContext2D::~CanvasRenderingContext2D() |
1122 | 0 | { |
1123 | 0 | RemoveDrawObserver(); |
1124 | 0 | RemovePostRefreshObserver(); |
1125 | 0 | RemoveShutdownObserver(); |
1126 | 0 | Reset(); |
1127 | 0 | // Drop references from all CanvasRenderingContext2DUserData to this context |
1128 | 0 | for (uint32_t i = 0; i < mUserDatas.Length(); ++i) { |
1129 | 0 | mUserDatas[i]->Forget(); |
1130 | 0 | } |
1131 | 0 | sNumLivingContexts--; |
1132 | 0 | if (!sNumLivingContexts) { |
1133 | 0 | NS_IF_RELEASE(sErrorTarget); |
1134 | 0 | } |
1135 | 0 | RemoveDemotableContext(this); |
1136 | 0 | } |
1137 | | |
1138 | | JSObject* |
1139 | | CanvasRenderingContext2D::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
1140 | 0 | { |
1141 | 0 | return CanvasRenderingContext2D_Binding::Wrap(aCx, this, aGivenProto); |
1142 | 0 | } |
1143 | | |
1144 | | bool |
1145 | | CanvasRenderingContext2D::ParseColor(const nsAString& aString, |
1146 | | nscolor* aColor) |
1147 | 0 | { |
1148 | 0 | nsIDocument* document = mCanvasElement ? mCanvasElement->OwnerDoc() : nullptr; |
1149 | 0 | css::Loader* loader = document ? document->CSSLoader() : nullptr; |
1150 | 0 |
|
1151 | 0 | nsIPresShell* presShell = GetPresShell(); |
1152 | 0 | ServoStyleSet* set = presShell ? presShell->StyleSet() : nullptr; |
1153 | 0 |
|
1154 | 0 | // First, try computing the color without handling currentcolor. |
1155 | 0 | bool wasCurrentColor = false; |
1156 | 0 | if (!ServoCSSParser::ComputeColor(set, NS_RGB(0, 0, 0), aString, aColor, |
1157 | 0 | &wasCurrentColor, loader)) { |
1158 | 0 | return false; |
1159 | 0 | } |
1160 | 0 | |
1161 | 0 | if (wasCurrentColor && mCanvasElement) { |
1162 | 0 | // Otherwise, get the value of the color property, flushing style |
1163 | 0 | // if necessary. |
1164 | 0 | RefPtr<ComputedStyle> canvasStyle = |
1165 | 0 | nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); |
1166 | 0 | if (canvasStyle) { |
1167 | 0 | *aColor = canvasStyle->StyleColor()->mColor; |
1168 | 0 | } |
1169 | 0 | // Beware that the presShell could be gone here. |
1170 | 0 | } |
1171 | 0 | return true; |
1172 | 0 | } |
1173 | | |
1174 | | nsresult |
1175 | | CanvasRenderingContext2D::Reset() |
1176 | 0 | { |
1177 | 0 | if (mCanvasElement) { |
1178 | 0 | mCanvasElement->InvalidateCanvas(); |
1179 | 0 | } |
1180 | 0 |
|
1181 | 0 | // only do this for non-docshell created contexts, |
1182 | 0 | // since those are the ones that we created a surface for |
1183 | 0 | if (mTarget && IsTargetValid() && !mDocShell) { |
1184 | 0 | gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; |
1185 | 0 | } |
1186 | 0 |
|
1187 | 0 | bool forceReset = true; |
1188 | 0 | ReturnTarget(forceReset); |
1189 | 0 | mTarget = nullptr; |
1190 | 0 | mBufferProvider = nullptr; |
1191 | 0 |
|
1192 | 0 | // reset hit regions |
1193 | 0 | mHitRegionsOptions.ClearAndRetainStorage(); |
1194 | 0 |
|
1195 | 0 | // Since the target changes the backing texture will change, and this will |
1196 | 0 | // no longer be valid. |
1197 | 0 | mIsEntireFrameInvalid = false; |
1198 | 0 | mPredictManyRedrawCalls = false; |
1199 | 0 | mIsCapturedFrameInvalid = false; |
1200 | 0 |
|
1201 | 0 | return NS_OK; |
1202 | 0 | } |
1203 | | |
1204 | | void |
1205 | | CanvasRenderingContext2D::OnShutdown() |
1206 | 0 | { |
1207 | 0 | mShutdownObserver = nullptr; |
1208 | 0 |
|
1209 | 0 | RefPtr<PersistentBufferProvider> provider = mBufferProvider; |
1210 | 0 |
|
1211 | 0 | Reset(); |
1212 | 0 |
|
1213 | 0 | if (provider) { |
1214 | 0 | provider->OnShutdown(); |
1215 | 0 | } |
1216 | 0 | } |
1217 | | |
1218 | | void |
1219 | | CanvasRenderingContext2D::RemoveShutdownObserver() |
1220 | 0 | { |
1221 | 0 | if (mShutdownObserver) { |
1222 | 0 | nsContentUtils::UnregisterShutdownObserver(mShutdownObserver); |
1223 | 0 | mShutdownObserver = nullptr; |
1224 | 0 | } |
1225 | 0 | } |
1226 | | |
1227 | | void |
1228 | | CanvasRenderingContext2D::SetStyleFromString(const nsAString& aStr, |
1229 | | Style aWhichStyle) |
1230 | 0 | { |
1231 | 0 | MOZ_ASSERT(!aStr.IsVoid()); |
1232 | 0 |
|
1233 | 0 | nscolor color; |
1234 | 0 | if (!ParseColor(aStr, &color)) { |
1235 | 0 | return; |
1236 | 0 | } |
1237 | 0 | |
1238 | 0 | CurrentState().SetColorStyle(aWhichStyle, color); |
1239 | 0 | } |
1240 | | |
1241 | | void |
1242 | | CanvasRenderingContext2D::GetStyleAsUnion(OwningStringOrCanvasGradientOrCanvasPattern& aValue, |
1243 | | Style aWhichStyle) |
1244 | 0 | { |
1245 | 0 | const ContextState& state = CurrentState(); |
1246 | 0 | if (state.patternStyles[aWhichStyle]) { |
1247 | 0 | aValue.SetAsCanvasPattern() = state.patternStyles[aWhichStyle]; |
1248 | 0 | } else if (state.gradientStyles[aWhichStyle]) { |
1249 | 0 | aValue.SetAsCanvasGradient() = state.gradientStyles[aWhichStyle]; |
1250 | 0 | } else { |
1251 | 0 | StyleColorToString(state.colorStyles[aWhichStyle], aValue.SetAsString()); |
1252 | 0 | } |
1253 | 0 | } |
1254 | | |
1255 | | // static |
1256 | | void |
1257 | | CanvasRenderingContext2D::StyleColorToString(const nscolor& aColor, nsAString& aStr) |
1258 | 0 | { |
1259 | 0 | // We can't reuse the normal CSS color stringification code, |
1260 | 0 | // because the spec calls for a different algorithm for canvas. |
1261 | 0 | if (NS_GET_A(aColor) == 255) { |
1262 | 0 | CopyUTF8toUTF16(nsPrintfCString("#%02x%02x%02x", |
1263 | 0 | NS_GET_R(aColor), |
1264 | 0 | NS_GET_G(aColor), |
1265 | 0 | NS_GET_B(aColor)), |
1266 | 0 | aStr); |
1267 | 0 | } else { |
1268 | 0 | CopyUTF8toUTF16(nsPrintfCString("rgba(%d, %d, %d, ", |
1269 | 0 | NS_GET_R(aColor), |
1270 | 0 | NS_GET_G(aColor), |
1271 | 0 | NS_GET_B(aColor)), |
1272 | 0 | aStr); |
1273 | 0 | aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor))); |
1274 | 0 | aStr.Append(')'); |
1275 | 0 | } |
1276 | 0 | } |
1277 | | |
1278 | | nsresult |
1279 | | CanvasRenderingContext2D::Redraw() |
1280 | 0 | { |
1281 | 0 | mIsCapturedFrameInvalid = true; |
1282 | 0 |
|
1283 | 0 | if (mIsEntireFrameInvalid) { |
1284 | 0 | return NS_OK; |
1285 | 0 | } |
1286 | 0 | |
1287 | 0 | mIsEntireFrameInvalid = true; |
1288 | 0 |
|
1289 | 0 | if (!mCanvasElement) { |
1290 | 0 | NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); |
1291 | 0 | return NS_OK; |
1292 | 0 | } |
1293 | 0 |
|
1294 | 0 | SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); |
1295 | 0 |
|
1296 | 0 | mCanvasElement->InvalidateCanvasContent(nullptr); |
1297 | 0 |
|
1298 | 0 | return NS_OK; |
1299 | 0 | } |
1300 | | |
1301 | | void |
1302 | | CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) |
1303 | 0 | { |
1304 | 0 | mIsCapturedFrameInvalid = true; |
1305 | 0 |
|
1306 | 0 | ++mInvalidateCount; |
1307 | 0 |
|
1308 | 0 | if (mIsEntireFrameInvalid) { |
1309 | 0 | return; |
1310 | 0 | } |
1311 | 0 | |
1312 | 0 | if (mPredictManyRedrawCalls || |
1313 | 0 | mInvalidateCount > kCanvasMaxInvalidateCount) { |
1314 | 0 | Redraw(); |
1315 | 0 | return; |
1316 | 0 | } |
1317 | 0 | |
1318 | 0 | if (!mCanvasElement) { |
1319 | 0 | NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!"); |
1320 | 0 | return; |
1321 | 0 | } |
1322 | 0 |
|
1323 | 0 | SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); |
1324 | 0 |
|
1325 | 0 | mCanvasElement->InvalidateCanvasContent(&aR); |
1326 | 0 | } |
1327 | | |
1328 | | void |
1329 | | CanvasRenderingContext2D::DidRefresh() |
1330 | 0 | { |
1331 | 0 | if (IsTargetValid() && mIsSkiaGL) { |
1332 | 0 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
1333 | 0 | MOZ_ASSERT(glue); |
1334 | 0 |
|
1335 | 0 | auto gl = glue->GetGLContext(); |
1336 | 0 | gl->FlushIfHeavyGLCallsSinceLastFlush(); |
1337 | 0 | } |
1338 | 0 | } |
1339 | | |
1340 | | void |
1341 | | CanvasRenderingContext2D::RedrawUser(const gfxRect& aR) |
1342 | 0 | { |
1343 | 0 | mIsCapturedFrameInvalid = true; |
1344 | 0 |
|
1345 | 0 | if (mIsEntireFrameInvalid) { |
1346 | 0 | ++mInvalidateCount; |
1347 | 0 | return; |
1348 | 0 | } |
1349 | 0 | |
1350 | 0 | gfx::Rect newr = mTarget->GetTransform().TransformBounds(ToRect(aR)); |
1351 | 0 | Redraw(newr); |
1352 | 0 | } |
1353 | | |
1354 | | bool |
1355 | | CanvasRenderingContext2D::AllowOpenGLCanvas() const |
1356 | 0 | { |
1357 | 0 | // If we somehow didn't have the correct compositor in the constructor, |
1358 | 0 | // we could do something like this to get it: |
1359 | 0 | // |
1360 | 0 | // HTMLCanvasElement* el = GetCanvas(); |
1361 | 0 | // if (el) { |
1362 | 0 | // mCompositorBackend = el->GetCompositorBackendType(); |
1363 | 0 | // } |
1364 | 0 | // |
1365 | 0 | // We could have LAYERS_NONE if there was no widget at the time of |
1366 | 0 | // canvas creation, but in that case the |
1367 | 0 | // HTMLCanvasElement::GetCompositorBackendType would return LAYERS_NONE |
1368 | 0 | // as well, so it wouldn't help much. |
1369 | 0 |
|
1370 | 0 | return (mCompositorBackend == LayersBackend::LAYERS_OPENGL || |
1371 | 0 | mCompositorBackend == LayersBackend::LAYERS_WR) && |
1372 | 0 | gfxPlatform::GetPlatform()->AllowOpenGLCanvas(); |
1373 | 0 | } |
1374 | | |
1375 | | bool CanvasRenderingContext2D::SwitchRenderingMode(RenderingMode aRenderingMode) |
1376 | 0 | { |
1377 | 0 | if (!(IsTargetValid() || mBufferProvider) || mRenderingMode == aRenderingMode) { |
1378 | 0 | return false; |
1379 | 0 | } |
1380 | 0 | |
1381 | 0 | MOZ_ASSERT(mBufferProvider); |
1382 | 0 |
|
1383 | 0 | #ifdef USE_SKIA_GPU |
1384 | 0 | // Do not attempt to switch into GL mode if the platform doesn't allow it. |
1385 | 0 | if ((aRenderingMode == RenderingMode::OpenGLBackendMode) && |
1386 | 0 | !AllowOpenGLCanvas()) { |
1387 | 0 | return false; |
1388 | 0 | } |
1389 | 0 | #endif |
1390 | 0 | |
1391 | 0 | RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider; |
1392 | 0 |
|
1393 | 0 | // Return the old target to the buffer provider. |
1394 | 0 | // We need to do this before calling EnsureTarget. |
1395 | 0 | ReturnTarget(); |
1396 | 0 | mTarget = nullptr; |
1397 | 0 | mBufferProvider = nullptr; |
1398 | 0 | mResetLayer = true; |
1399 | 0 |
|
1400 | 0 | // Recreate mTarget using the new rendering mode |
1401 | 0 | RenderingMode attemptedMode = EnsureTarget(nullptr, aRenderingMode); |
1402 | 0 | if (!IsTargetValid()) { |
1403 | 0 | return false; |
1404 | 0 | } |
1405 | 0 | |
1406 | 0 | if (oldBufferProvider && mTarget) { |
1407 | 0 | CopyBufferProvider(*oldBufferProvider, *mTarget, IntRect(0, 0, mWidth, mHeight)); |
1408 | 0 | } |
1409 | 0 |
|
1410 | 0 | // We succeeded, so update mRenderingMode to reflect reality |
1411 | 0 | mRenderingMode = attemptedMode; |
1412 | 0 |
|
1413 | 0 | return true; |
1414 | 0 | } |
1415 | | |
1416 | | bool |
1417 | | CanvasRenderingContext2D::CopyBufferProvider(PersistentBufferProvider& aOld, |
1418 | | DrawTarget& aTarget, |
1419 | | IntRect aCopyRect) |
1420 | 0 | { |
1421 | 0 | // Borrowing the snapshot must be done after ReturnTarget. |
1422 | 0 | RefPtr<SourceSurface> snapshot = aOld.BorrowSnapshot(); |
1423 | 0 |
|
1424 | 0 | if (!snapshot) { |
1425 | 0 | return false; |
1426 | 0 | } |
1427 | 0 | |
1428 | 0 | aTarget.CopySurface(snapshot, aCopyRect, IntPoint()); |
1429 | 0 | aOld.ReturnSnapshot(snapshot.forget()); |
1430 | 0 | return true; |
1431 | 0 | } |
1432 | | |
1433 | | void CanvasRenderingContext2D::Demote() |
1434 | 0 | { |
1435 | 0 | if (SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) { |
1436 | 0 | RemoveDemotableContext(this); |
1437 | 0 | } |
1438 | 0 | } |
1439 | | |
1440 | | std::vector<CanvasRenderingContext2D*>& |
1441 | | CanvasRenderingContext2D::DemotableContexts() |
1442 | 0 | { |
1443 | 0 | // This is a list of raw pointers to cycle-collected objects. We need to ensure |
1444 | 0 | // that we remove elements from it during UNLINK (which can happen considerably before |
1445 | 0 | // the actual destructor) since the object is logically destroyed at that point |
1446 | 0 | // and will be in an inconsistant state. |
1447 | 0 | static std::vector<CanvasRenderingContext2D*> contexts; |
1448 | 0 | return contexts; |
1449 | 0 | } |
1450 | | |
1451 | | void |
1452 | | CanvasRenderingContext2D::DemoteOldestContextIfNecessary() |
1453 | 0 | { |
1454 | 0 | MOZ_ASSERT(sMaxContextsInitialized); |
1455 | 0 | if (sMaxContexts <= 0) { |
1456 | 0 | return; |
1457 | 0 | } |
1458 | 0 | |
1459 | 0 | std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts(); |
1460 | 0 | if (contexts.size() < (size_t)sMaxContexts) |
1461 | 0 | return; |
1462 | 0 | |
1463 | 0 | CanvasRenderingContext2D* oldest = contexts.front(); |
1464 | 0 | if (oldest->SwitchRenderingMode(RenderingMode::SoftwareBackendMode)) { |
1465 | 0 | RemoveDemotableContext(oldest); |
1466 | 0 | } |
1467 | 0 | } |
1468 | | |
1469 | | void |
1470 | | CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* aContext) |
1471 | 0 | { |
1472 | 0 | MOZ_ASSERT(sMaxContextsInitialized); |
1473 | 0 | if (sMaxContexts <= 0) |
1474 | 0 | return; |
1475 | 0 | |
1476 | 0 | std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext); |
1477 | 0 | if (iter != DemotableContexts().end()) |
1478 | 0 | return; |
1479 | 0 | |
1480 | 0 | DemotableContexts().push_back(aContext); |
1481 | 0 | } |
1482 | | |
1483 | | void |
1484 | | CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* aContext) |
1485 | 0 | { |
1486 | 0 | MOZ_ASSERT(sMaxContextsInitialized); |
1487 | 0 | if (sMaxContexts <= 0) |
1488 | 0 | return; |
1489 | 0 | |
1490 | 0 | std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), aContext); |
1491 | 0 | if (iter != DemotableContexts().end()) |
1492 | 0 | DemotableContexts().erase(iter); |
1493 | 0 | } |
1494 | | |
1495 | 0 | #define MIN_SKIA_GL_DIMENSION 16 |
1496 | | |
1497 | | bool |
1498 | 0 | CanvasRenderingContext2D::CheckSizeForSkiaGL(IntSize aSize) { |
1499 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1500 | 0 |
|
1501 | 0 | int minsize = Preferences::GetInt("gfx.canvas.min-size-for-skia-gl", 128); |
1502 | 0 | if (aSize.width < MIN_SKIA_GL_DIMENSION || aSize.height < MIN_SKIA_GL_DIMENSION || |
1503 | 0 | (aSize.width * aSize.height < minsize * minsize)) { |
1504 | 0 | return false; |
1505 | 0 | } |
1506 | 0 | |
1507 | 0 | // Maximum pref allows 3 different options: |
1508 | 0 | // 0 means unlimited size |
1509 | 0 | // > 0 means use value as an absolute threshold |
1510 | 0 | // < 0 means use the number of screen pixels as a threshold |
1511 | 0 | int maxsize = Preferences::GetInt("gfx.canvas.max-size-for-skia-gl", 0); |
1512 | 0 |
|
1513 | 0 | // unlimited max size |
1514 | 0 | if (!maxsize) { |
1515 | 0 | return true; |
1516 | 0 | } |
1517 | 0 | |
1518 | 0 | // absolute max size threshold |
1519 | 0 | if (maxsize > 0) { |
1520 | 0 | return aSize.width <= maxsize && aSize.height <= maxsize; |
1521 | 0 | } |
1522 | 0 |
|
1523 | 0 | // Cache the number of pixels on the primary screen |
1524 | 0 | static int32_t gScreenPixels = -1; |
1525 | 0 | if (gScreenPixels < 0) { |
1526 | 0 | // Default to historical mobile screen size of 980x480, like FishIEtank. |
1527 | 0 | // In addition, allow skia use up to this size even if the screen is smaller. |
1528 | 0 | // A lot content expects this size to work well. |
1529 | 0 | // See Bug 999841 |
1530 | 0 | if (gfxPlatform::GetPlatform()->HasEnoughTotalSystemMemoryForSkiaGL()) { |
1531 | 0 | gScreenPixels = 980 * 480; |
1532 | 0 | } |
1533 | 0 |
|
1534 | 0 | nsCOMPtr<nsIScreenManager> screenManager = |
1535 | 0 | do_GetService("@mozilla.org/gfx/screenmanager;1"); |
1536 | 0 | if (screenManager) { |
1537 | 0 | nsCOMPtr<nsIScreen> primaryScreen; |
1538 | 0 | screenManager->GetPrimaryScreen(getter_AddRefs(primaryScreen)); |
1539 | 0 | if (primaryScreen) { |
1540 | 0 | int32_t x, y, width, height; |
1541 | 0 | primaryScreen->GetRect(&x, &y, &width, &height); |
1542 | 0 |
|
1543 | 0 | gScreenPixels = std::max(gScreenPixels, width * height); |
1544 | 0 | } |
1545 | 0 | } |
1546 | 0 | } |
1547 | 0 |
|
1548 | 0 | // Just always use a scale of 1.0. It can be changed if a lot of contents need it. |
1549 | 0 | static double gDefaultScale = 1.0; |
1550 | 0 |
|
1551 | 0 | double scale = gDefaultScale > 0 ? gDefaultScale : 1.0; |
1552 | 0 | int32_t threshold = ceil(scale * scale * gScreenPixels); |
1553 | 0 |
|
1554 | 0 | // screen size acts as max threshold |
1555 | 0 | return threshold < 0 || (aSize.width * aSize.height) <= threshold; |
1556 | 0 | } |
1557 | | |
1558 | | void |
1559 | | CanvasRenderingContext2D::ScheduleStableStateCallback() |
1560 | 0 | { |
1561 | 0 | if (mHasPendingStableStateCallback) { |
1562 | 0 | return; |
1563 | 0 | } |
1564 | 0 | mHasPendingStableStateCallback = true; |
1565 | 0 |
|
1566 | 0 | nsContentUtils::RunInStableState( |
1567 | 0 | NewRunnableMethod("dom::CanvasRenderingContext2D::OnStableState", |
1568 | 0 | this, |
1569 | 0 | &CanvasRenderingContext2D::OnStableState)); |
1570 | 0 | } |
1571 | | |
1572 | | void |
1573 | | CanvasRenderingContext2D::OnStableState() |
1574 | 0 | { |
1575 | 0 | if (!mHasPendingStableStateCallback) { |
1576 | 0 | return; |
1577 | 0 | } |
1578 | 0 | |
1579 | 0 | ReturnTarget(); |
1580 | 0 |
|
1581 | 0 | mHasPendingStableStateCallback = false; |
1582 | 0 | } |
1583 | | |
1584 | | void |
1585 | | CanvasRenderingContext2D::RestoreClipsAndTransformToTarget() |
1586 | 0 | { |
1587 | 0 | // Restore clips and transform. |
1588 | 0 | mTarget->SetTransform(Matrix()); |
1589 | 0 |
|
1590 | 0 | if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) { |
1591 | 0 | // Cairo doesn't play well with huge clips. When given a very big clip it |
1592 | 0 | // will try to allocate big mask surface without taking the target |
1593 | 0 | // size into account which can cause OOM. See bug 1034593. |
1594 | 0 | // This limits the clip extents to the size of the canvas. |
1595 | 0 | // A fix in Cairo would probably be preferable, but requires somewhat |
1596 | 0 | // invasive changes. |
1597 | 0 | mTarget->PushClipRect(gfx::Rect(0, 0, mWidth, mHeight)); |
1598 | 0 | } |
1599 | 0 |
|
1600 | 0 | for (const auto& style : mStyleStack) { |
1601 | 0 | for (const auto& clipOrTransform : style.clipsAndTransforms) { |
1602 | 0 | if (clipOrTransform.IsClip()) { |
1603 | 0 | mTarget->PushClip(clipOrTransform.clip); |
1604 | 0 | } else { |
1605 | 0 | mTarget->SetTransform(clipOrTransform.transform); |
1606 | 0 | } |
1607 | 0 | } |
1608 | 0 | } |
1609 | 0 | } |
1610 | | |
1611 | | CanvasRenderingContext2D::RenderingMode |
1612 | | CanvasRenderingContext2D::EnsureTarget(const gfx::Rect* aCoveredRect, |
1613 | | RenderingMode aRenderingMode) |
1614 | 0 | { |
1615 | 0 | if (AlreadyShutDown()) { |
1616 | 0 | gfxCriticalError() << "Attempt to render into a Canvas2d after shutdown."; |
1617 | 0 | SetErrorState(); |
1618 | 0 | return aRenderingMode; |
1619 | 0 | } |
1620 | 0 |
|
1621 | 0 | // This would make no sense, so make sure we don't get ourselves in a mess |
1622 | 0 | MOZ_ASSERT(mRenderingMode != RenderingMode::DefaultBackendMode); |
1623 | 0 |
|
1624 | 0 | RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode; |
1625 | 0 |
|
1626 | 0 | if (mTarget && mode == mRenderingMode) { |
1627 | 0 | return mRenderingMode; |
1628 | 0 | } |
1629 | 0 | |
1630 | 0 | // Check that the dimensions are sane |
1631 | 0 | if (mWidth > gfxPrefs::MaxCanvasSize() || |
1632 | 0 | mHeight > gfxPrefs::MaxCanvasSize() || |
1633 | 0 | mWidth < 0 || mHeight < 0) { |
1634 | 0 |
|
1635 | 0 | SetErrorState(); |
1636 | 0 | return aRenderingMode; |
1637 | 0 | } |
1638 | 0 | |
1639 | 0 | // If the next drawing command covers the entire canvas, we can skip copying |
1640 | 0 | // from the previous frame and/or clearing the canvas. |
1641 | 0 | gfx::Rect canvasRect(0, 0, mWidth, mHeight); |
1642 | 0 | bool canDiscardContent = aCoveredRect && |
1643 | 0 | CurrentState().transform.TransformBounds(*aCoveredRect).Contains(canvasRect); |
1644 | 0 |
|
1645 | 0 | // If a clip is active we don't know for sure that the next drawing command |
1646 | 0 | // will really cover the entire canvas. |
1647 | 0 | for (const auto& style : mStyleStack) { |
1648 | 0 | if (!canDiscardContent) { |
1649 | 0 | break; |
1650 | 0 | } |
1651 | 0 | for (const auto& clipOrTransform : style.clipsAndTransforms) { |
1652 | 0 | if (clipOrTransform.IsClip()) { |
1653 | 0 | canDiscardContent = false; |
1654 | 0 | break; |
1655 | 0 | } |
1656 | 0 | } |
1657 | 0 | } |
1658 | 0 |
|
1659 | 0 | ScheduleStableStateCallback(); |
1660 | 0 |
|
1661 | 0 | IntRect persistedRect = canDiscardContent ? IntRect() |
1662 | 0 | : IntRect(0, 0, mWidth, mHeight); |
1663 | 0 |
|
1664 | 0 | if (mBufferProvider && mode == mRenderingMode) { |
1665 | 0 | mTarget = mBufferProvider->BorrowDrawTarget(persistedRect); |
1666 | 0 |
|
1667 | 0 | if (mTarget && !mBufferProvider->PreservesDrawingState()) { |
1668 | 0 | RestoreClipsAndTransformToTarget(); |
1669 | 0 | } |
1670 | 0 |
|
1671 | 0 | if (mTarget) { |
1672 | 0 | return mode; |
1673 | 0 | } |
1674 | 0 | } |
1675 | 0 | |
1676 | 0 | RefPtr<DrawTarget> newTarget; |
1677 | 0 | RefPtr<PersistentBufferProvider> newProvider; |
1678 | 0 |
|
1679 | 0 | if (mode == RenderingMode::OpenGLBackendMode && |
1680 | 0 | !TrySkiaGLTarget(newTarget, newProvider)) { |
1681 | 0 | // Fall back to software. |
1682 | 0 | mode = RenderingMode::SoftwareBackendMode; |
1683 | 0 | } |
1684 | 0 |
|
1685 | 0 | if (mode == RenderingMode::SoftwareBackendMode && |
1686 | 0 | !TrySharedTarget(newTarget, newProvider) && |
1687 | 0 | !TryBasicTarget(newTarget, newProvider)) { |
1688 | 0 |
|
1689 | 0 | gfxCriticalError( |
1690 | 0 | CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(GetSize())) |
1691 | 0 | ) << "Failed borrow shared and basic targets."; |
1692 | 0 |
|
1693 | 0 | SetErrorState(); |
1694 | 0 | return mode; |
1695 | 0 | } |
1696 | 0 |
|
1697 | 0 |
|
1698 | 0 | MOZ_ASSERT(newTarget); |
1699 | 0 | MOZ_ASSERT(newProvider); |
1700 | 0 |
|
1701 | 0 | bool needsClear = !canDiscardContent; |
1702 | 0 | if (newTarget->GetBackendType() == gfx::BackendType::SKIA) { |
1703 | 0 | // Skia expects the unused X channel to contains 0xFF even for opaque operations |
1704 | 0 | // so we can't skip clearing in that case, even if we are going to cover the |
1705 | 0 | // entire canvas in the next drawing operation. |
1706 | 0 | newTarget->ClearRect(canvasRect); |
1707 | 0 | needsClear = false; |
1708 | 0 | } |
1709 | 0 |
|
1710 | 0 | // Try to copy data from the previous buffer provider if there is one. |
1711 | 0 | if (!canDiscardContent && mBufferProvider && CopyBufferProvider(*mBufferProvider, *newTarget, persistedRect)) { |
1712 | 0 | needsClear = false; |
1713 | 0 | } |
1714 | 0 |
|
1715 | 0 | if (needsClear) { |
1716 | 0 | newTarget->ClearRect(canvasRect); |
1717 | 0 | } |
1718 | 0 |
|
1719 | 0 | mTarget = newTarget.forget(); |
1720 | 0 | mBufferProvider = newProvider.forget(); |
1721 | 0 |
|
1722 | 0 | RegisterAllocation(); |
1723 | 0 |
|
1724 | 0 | RestoreClipsAndTransformToTarget(); |
1725 | 0 |
|
1726 | 0 | // Force a full layer transaction since we didn't have a layer before |
1727 | 0 | // and now we might need one. |
1728 | 0 | if (mCanvasElement) { |
1729 | 0 | mCanvasElement->InvalidateCanvas(); |
1730 | 0 | } |
1731 | 0 | // Calling Redraw() tells our invalidation machinery that the entire |
1732 | 0 | // canvas is already invalid, which can speed up future drawing. |
1733 | 0 | Redraw(); |
1734 | 0 |
|
1735 | 0 | return mode; |
1736 | 0 | } |
1737 | | |
1738 | | void |
1739 | | CanvasRenderingContext2D::SetInitialState() |
1740 | 0 | { |
1741 | 0 | // Set up the initial canvas defaults |
1742 | 0 | mPathBuilder = nullptr; |
1743 | 0 | mPath = nullptr; |
1744 | 0 | mDSPathBuilder = nullptr; |
1745 | 0 | mPathTransformWillUpdate = false; |
1746 | 0 |
|
1747 | 0 | mStyleStack.Clear(); |
1748 | 0 | ContextState* state = mStyleStack.AppendElement(); |
1749 | 0 | state->globalAlpha = 1.0; |
1750 | 0 |
|
1751 | 0 | state->colorStyles[Style::FILL] = NS_RGB(0,0,0); |
1752 | 0 | state->colorStyles[Style::STROKE] = NS_RGB(0,0,0); |
1753 | 0 | state->shadowColor = NS_RGBA(0,0,0,0); |
1754 | 0 | } |
1755 | | |
1756 | | void |
1757 | | CanvasRenderingContext2D::SetErrorState() |
1758 | 0 | { |
1759 | 0 | EnsureErrorTarget(); |
1760 | 0 |
|
1761 | 0 | if (mTarget && mTarget != sErrorTarget) { |
1762 | 0 | gCanvasAzureMemoryUsed -= mWidth * mHeight * 4; |
1763 | 0 | } |
1764 | 0 |
|
1765 | 0 | mTarget = sErrorTarget; |
1766 | 0 | mBufferProvider = nullptr; |
1767 | 0 |
|
1768 | 0 | // clear transforms, clips, etc. |
1769 | 0 | SetInitialState(); |
1770 | 0 | } |
1771 | | |
1772 | | void |
1773 | | CanvasRenderingContext2D::RegisterAllocation() |
1774 | 0 | { |
1775 | 0 | // XXX - It would make more sense to track the allocation in |
1776 | 0 | // PeristentBufferProvider, rather than here. |
1777 | 0 | static bool registered = false; |
1778 | 0 | // FIXME: Disable the reporter for now, see bug 1241865 |
1779 | 0 | if (!registered && false) { |
1780 | 0 | registered = true; |
1781 | 0 | RegisterStrongMemoryReporter(new Canvas2dPixelsReporter()); |
1782 | 0 | } |
1783 | 0 |
|
1784 | 0 | JSObject* wrapper = GetWrapperPreserveColor(); |
1785 | 0 | if (wrapper) { |
1786 | 0 | CycleCollectedJSRuntime::Get()-> |
1787 | 0 | AddZoneWaitingForGC(JS::GetObjectZone(wrapper)); |
1788 | 0 | } |
1789 | 0 | } |
1790 | | |
1791 | | static already_AddRefed<LayerManager> |
1792 | | LayerManagerFromCanvasElement(nsINode* aCanvasElement) |
1793 | 0 | { |
1794 | 0 | if (!aCanvasElement) { |
1795 | 0 | return nullptr; |
1796 | 0 | } |
1797 | 0 | |
1798 | 0 | return nsContentUtils::PersistentLayerManagerForDocument(aCanvasElement->OwnerDoc()); |
1799 | 0 | } |
1800 | | |
1801 | | bool |
1802 | | CanvasRenderingContext2D::TrySkiaGLTarget(RefPtr<gfx::DrawTarget>& aOutDT, |
1803 | | RefPtr<layers::PersistentBufferProvider>& aOutProvider) |
1804 | 0 | { |
1805 | 0 | aOutDT = nullptr; |
1806 | 0 | aOutProvider = nullptr; |
1807 | 0 |
|
1808 | 0 | mIsSkiaGL = false; |
1809 | 0 |
|
1810 | 0 | IntSize size(mWidth, mHeight); |
1811 | 0 | if (!AllowOpenGLCanvas() || !CheckSizeForSkiaGL(size)) { |
1812 | 0 | return false; |
1813 | 0 | } |
1814 | 0 | |
1815 | 0 | |
1816 | 0 | RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement); |
1817 | 0 |
|
1818 | 0 | if (!layerManager) { |
1819 | 0 | return false; |
1820 | 0 | } |
1821 | 0 | |
1822 | 0 | DemoteOldestContextIfNecessary(); |
1823 | 0 | mBufferProvider = nullptr; |
1824 | 0 |
|
1825 | 0 | #ifdef USE_SKIA_GPU |
1826 | 0 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
1827 | 0 | if (!glue || !glue->GetGrContext() || !glue->GetGLContext()) { |
1828 | 0 | return false; |
1829 | 0 | } |
1830 | 0 | |
1831 | 0 | SurfaceFormat format = GetSurfaceFormat(); |
1832 | 0 | aOutDT = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), |
1833 | 0 | size, format); |
1834 | 0 | if (!aOutDT) { |
1835 | 0 | gfxCriticalNote << "Failed to create a SkiaGL DrawTarget, falling back to software\n"; |
1836 | 0 | return false; |
1837 | 0 | } |
1838 | 0 |
|
1839 | 0 | MOZ_ASSERT(aOutDT->GetType() == DrawTargetType::HARDWARE_RASTER); |
1840 | 0 |
|
1841 | 0 | AddDemotableContext(this); |
1842 | 0 | aOutProvider = new PersistentBufferProviderBasic(aOutDT); |
1843 | 0 | mIsSkiaGL = true; |
1844 | 0 | // Drop a note in the debug builds if we ever use accelerated Skia canvas. |
1845 | 0 | gfxWarningOnce() << "Using SkiaGL canvas."; |
1846 | 0 | #endif |
1847 | 0 |
|
1848 | 0 | // could still be null if USE_SKIA_GPU is not #defined. |
1849 | 0 | return !!aOutDT; |
1850 | 0 | } |
1851 | | |
1852 | | bool |
1853 | | CanvasRenderingContext2D::TrySharedTarget(RefPtr<gfx::DrawTarget>& aOutDT, |
1854 | | RefPtr<layers::PersistentBufferProvider>& aOutProvider) |
1855 | 0 | { |
1856 | 0 | aOutDT = nullptr; |
1857 | 0 | aOutProvider = nullptr; |
1858 | 0 |
|
1859 | 0 | if (!mCanvasElement) { |
1860 | 0 | return false; |
1861 | 0 | } |
1862 | 0 | |
1863 | 0 | if (mBufferProvider && |
1864 | 0 | (mBufferProvider->GetType() == LayersBackend::LAYERS_CLIENT || |
1865 | 0 | mBufferProvider->GetType() == LayersBackend::LAYERS_WR)) { |
1866 | 0 | // we are already using a shared buffer provider, we are allocating a new one |
1867 | 0 | // because the current one failed so let's just fall back to the basic provider. |
1868 | 0 | return false; |
1869 | 0 | } |
1870 | 0 | |
1871 | 0 | RefPtr<LayerManager> layerManager = LayerManagerFromCanvasElement(mCanvasElement); |
1872 | 0 |
|
1873 | 0 | if (!layerManager) { |
1874 | 0 | return false; |
1875 | 0 | } |
1876 | 0 | |
1877 | 0 | aOutProvider = layerManager->CreatePersistentBufferProvider(GetSize(), GetSurfaceFormat()); |
1878 | 0 |
|
1879 | 0 | if (!aOutProvider) { |
1880 | 0 | return false; |
1881 | 0 | } |
1882 | 0 | |
1883 | 0 | // We can pass an empty persisted rect since we just created the buffer |
1884 | 0 | // provider (nothing to restore). |
1885 | 0 | aOutDT = aOutProvider->BorrowDrawTarget(IntRect()); |
1886 | 0 | MOZ_ASSERT(aOutDT); |
1887 | 0 |
|
1888 | 0 | return !!aOutDT; |
1889 | 0 | } |
1890 | | |
1891 | | bool |
1892 | | CanvasRenderingContext2D::TryBasicTarget(RefPtr<gfx::DrawTarget>& aOutDT, |
1893 | | RefPtr<layers::PersistentBufferProvider>& aOutProvider) |
1894 | 0 | { |
1895 | 0 | aOutDT = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(GetSize(), |
1896 | 0 | GetSurfaceFormat()); |
1897 | 0 | if (!aOutDT) { |
1898 | 0 | return false; |
1899 | 0 | } |
1900 | 0 | |
1901 | 0 | aOutProvider = new PersistentBufferProviderBasic(aOutDT); |
1902 | 0 | return true; |
1903 | 0 | } |
1904 | | |
1905 | | NS_IMETHODIMP |
1906 | | CanvasRenderingContext2D::SetDimensions(int32_t aWidth, int32_t aHeight) |
1907 | 0 | { |
1908 | 0 | // Zero sized surfaces can cause problems. |
1909 | 0 | mZero = false; |
1910 | 0 | if (aHeight == 0) { |
1911 | 0 | aHeight = 1; |
1912 | 0 | mZero = true; |
1913 | 0 | } |
1914 | 0 | if (aWidth == 0) { |
1915 | 0 | aWidth = 1; |
1916 | 0 | mZero = true; |
1917 | 0 | } |
1918 | 0 |
|
1919 | 0 | ClearTarget(aWidth, aHeight); |
1920 | 0 |
|
1921 | 0 | return NS_OK; |
1922 | 0 | } |
1923 | | |
1924 | | void |
1925 | | CanvasRenderingContext2D::ClearTarget(int32_t aWidth, int32_t aHeight) |
1926 | 0 | { |
1927 | 0 | Reset(); |
1928 | 0 |
|
1929 | 0 | mResetLayer = true; |
1930 | 0 |
|
1931 | 0 | SetInitialState(); |
1932 | 0 |
|
1933 | 0 | // Update dimensions only if new (strictly positive) values were passed. |
1934 | 0 | if (aWidth > 0 && aHeight > 0) { |
1935 | 0 | mWidth = aWidth; |
1936 | 0 | mHeight = aHeight; |
1937 | 0 | } |
1938 | 0 |
|
1939 | 0 | if (!mCanvasElement || !mCanvasElement->IsInComposedDoc()) { |
1940 | 0 | return; |
1941 | 0 | } |
1942 | 0 | |
1943 | 0 | // For vertical writing-mode, unless text-orientation is sideways, |
1944 | 0 | // we'll modify the initial value of textBaseline to 'middle'. |
1945 | 0 | RefPtr<ComputedStyle> canvasStyle = |
1946 | 0 | nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); |
1947 | 0 | if (canvasStyle) { |
1948 | 0 | WritingMode wm(canvasStyle); |
1949 | 0 | if (wm.IsVertical() && !wm.IsSideways()) { |
1950 | 0 | CurrentState().textBaseline = TextBaseline::MIDDLE; |
1951 | 0 | } |
1952 | 0 | } |
1953 | 0 | } |
1954 | | |
1955 | | void |
1956 | | CanvasRenderingContext2D::ReturnTarget(bool aForceReset) |
1957 | 0 | { |
1958 | 0 | if (mTarget && mBufferProvider && mTarget != sErrorTarget) { |
1959 | 0 | CurrentState().transform = mTarget->GetTransform(); |
1960 | 0 | if (aForceReset || !mBufferProvider->PreservesDrawingState()) { |
1961 | 0 | for (const auto& style : mStyleStack) { |
1962 | 0 | for (const auto& clipOrTransform : style.clipsAndTransforms) { |
1963 | 0 | if (clipOrTransform.IsClip()) { |
1964 | 0 | mTarget->PopClip(); |
1965 | 0 | } |
1966 | 0 | } |
1967 | 0 | } |
1968 | 0 |
|
1969 | 0 | if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) { |
1970 | 0 | // With the cairo backend we pushed an extra clip rect which we have to |
1971 | 0 | // balance out here. See the comment in RestoreClipsAndTransformToTarget. |
1972 | 0 | mTarget->PopClip(); |
1973 | 0 | } |
1974 | 0 |
|
1975 | 0 | mTarget->SetTransform(Matrix()); |
1976 | 0 | } |
1977 | 0 |
|
1978 | 0 | mBufferProvider->ReturnDrawTarget(mTarget.forget()); |
1979 | 0 | } |
1980 | 0 | } |
1981 | | |
1982 | | NS_IMETHODIMP |
1983 | | CanvasRenderingContext2D::InitializeWithDrawTarget(nsIDocShell* aShell, |
1984 | | NotNull<gfx::DrawTarget*> aTarget) |
1985 | 0 | { |
1986 | 0 | RemovePostRefreshObserver(); |
1987 | 0 | mDocShell = aShell; |
1988 | 0 | AddPostRefreshObserverIfNecessary(); |
1989 | 0 |
|
1990 | 0 | IntSize size = aTarget->GetSize(); |
1991 | 0 | SetDimensions(size.width, size.height); |
1992 | 0 |
|
1993 | 0 | mTarget = aTarget; |
1994 | 0 | mBufferProvider = new PersistentBufferProviderBasic(aTarget); |
1995 | 0 |
|
1996 | 0 | if (mTarget->GetBackendType() == gfx::BackendType::CAIRO) { |
1997 | 0 | // Cf comment in EnsureTarget |
1998 | 0 | mTarget->PushClipRect(gfx::Rect(Point(0, 0), Size(mWidth, mHeight))); |
1999 | 0 | } |
2000 | 0 |
|
2001 | 0 | return NS_OK; |
2002 | 0 | } |
2003 | | |
2004 | | void |
2005 | | CanvasRenderingContext2D::SetOpaqueValueFromOpaqueAttr(bool aOpaqueAttrValue) |
2006 | 0 | { |
2007 | 0 | if (aOpaqueAttrValue != mOpaqueAttrValue) { |
2008 | 0 | mOpaqueAttrValue = aOpaqueAttrValue; |
2009 | 0 | UpdateIsOpaque(); |
2010 | 0 | } |
2011 | 0 | } |
2012 | | |
2013 | | void |
2014 | | CanvasRenderingContext2D::UpdateIsOpaque() |
2015 | 0 | { |
2016 | 0 | mOpaque = !mContextAttributesHasAlpha || mOpaqueAttrValue; |
2017 | 0 | ClearTarget(); |
2018 | 0 | } |
2019 | | |
2020 | | NS_IMETHODIMP |
2021 | | CanvasRenderingContext2D::SetIsIPC(bool aIsIPC) |
2022 | 0 | { |
2023 | 0 | if (aIsIPC != mIPC) { |
2024 | 0 | mIPC = aIsIPC; |
2025 | 0 | ClearTarget(); |
2026 | 0 | } |
2027 | 0 |
|
2028 | 0 | return NS_OK; |
2029 | 0 | } |
2030 | | |
2031 | | NS_IMETHODIMP |
2032 | | CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, |
2033 | | JS::Handle<JS::Value> aOptions, |
2034 | | ErrorResult& aRvForDictionaryInit) |
2035 | 0 | { |
2036 | 0 | if (aOptions.isNullOrUndefined()) { |
2037 | 0 | return NS_OK; |
2038 | 0 | } |
2039 | 0 | |
2040 | 0 | // This shouldn't be called before drawing starts, so there should be no drawtarget yet |
2041 | 0 | MOZ_ASSERT(!mTarget); |
2042 | 0 |
|
2043 | 0 | ContextAttributes2D attributes; |
2044 | 0 | if (!attributes.Init(aCx, aOptions)) { |
2045 | 0 | aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED); |
2046 | 0 | return NS_ERROR_UNEXPECTED; |
2047 | 0 | } |
2048 | 0 | |
2049 | 0 | if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) { |
2050 | 0 | // Use software when there is going to be a lot of readback |
2051 | 0 | if (attributes.mWillReadFrequently) { |
2052 | 0 |
|
2053 | 0 | // We want to lock into software, so remove the observer that |
2054 | 0 | // may potentially change that... |
2055 | 0 | RemoveDrawObserver(); |
2056 | 0 | mRenderingMode = RenderingMode::SoftwareBackendMode; |
2057 | 0 | } |
2058 | 0 | } |
2059 | 0 |
|
2060 | 0 | mContextAttributesHasAlpha = attributes.mAlpha; |
2061 | 0 | UpdateIsOpaque(); |
2062 | 0 |
|
2063 | 0 | return NS_OK; |
2064 | 0 | } |
2065 | | |
2066 | | UniquePtr<uint8_t[]> |
2067 | | CanvasRenderingContext2D::GetImageBuffer(int32_t* aFormat) |
2068 | 0 | { |
2069 | 0 | UniquePtr<uint8_t[]> ret; |
2070 | 0 |
|
2071 | 0 | *aFormat = 0; |
2072 | 0 |
|
2073 | 0 | RefPtr<SourceSurface> snapshot; |
2074 | 0 | if (mTarget) { |
2075 | 0 | snapshot = mTarget->Snapshot(); |
2076 | 0 | } else if (mBufferProvider) { |
2077 | 0 | snapshot = mBufferProvider->BorrowSnapshot(); |
2078 | 0 | } else { |
2079 | 0 | EnsureTarget(); |
2080 | 0 | if (!IsTargetValid()) { |
2081 | 0 | return nullptr; |
2082 | 0 | } |
2083 | 0 | snapshot = mTarget->Snapshot(); |
2084 | 0 | } |
2085 | 0 |
|
2086 | 0 | if (snapshot) { |
2087 | 0 | RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); |
2088 | 0 | if (data && data->GetSize() == GetSize()) { |
2089 | 0 | *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; |
2090 | 0 | ret = SurfaceToPackedBGRA(data); |
2091 | 0 | } |
2092 | 0 | } |
2093 | 0 |
|
2094 | 0 | if (!mTarget && mBufferProvider) { |
2095 | 0 | mBufferProvider->ReturnSnapshot(snapshot.forget()); |
2096 | 0 | } |
2097 | 0 |
|
2098 | 0 | return ret; |
2099 | 0 | } |
2100 | | |
2101 | | nsString CanvasRenderingContext2D::GetHitRegion(const mozilla::gfx::Point& aPoint) |
2102 | 0 | { |
2103 | 0 | for (size_t x = 0 ; x < mHitRegionsOptions.Length(); x++) { |
2104 | 0 | RegionInfo& info = mHitRegionsOptions[x]; |
2105 | 0 | if (info.mPath->ContainsPoint(aPoint, Matrix())) { |
2106 | 0 | return info.mId; |
2107 | 0 | } |
2108 | 0 | } |
2109 | 0 | return nsString(); |
2110 | 0 | } |
2111 | | |
2112 | | NS_IMETHODIMP |
2113 | | CanvasRenderingContext2D::GetInputStream(const char* aMimeType, |
2114 | | const char16_t* aEncoderOptions, |
2115 | | nsIInputStream** aStream) |
2116 | 0 | { |
2117 | 0 | nsCString enccid("@mozilla.org/image/encoder;2?type="); |
2118 | 0 | enccid += aMimeType; |
2119 | 0 | nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get()); |
2120 | 0 | if (!encoder) { |
2121 | 0 | return NS_ERROR_FAILURE; |
2122 | 0 | } |
2123 | 0 | |
2124 | 0 | int32_t format = 0; |
2125 | 0 | UniquePtr<uint8_t[]> imageBuffer = GetImageBuffer(&format); |
2126 | 0 | if (!imageBuffer) { |
2127 | 0 | return NS_ERROR_FAILURE; |
2128 | 0 | } |
2129 | 0 | |
2130 | 0 | return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer.get(), |
2131 | 0 | format, encoder, aEncoderOptions, |
2132 | 0 | aStream); |
2133 | 0 | } |
2134 | | |
2135 | | SurfaceFormat |
2136 | | CanvasRenderingContext2D::GetSurfaceFormat() const |
2137 | 0 | { |
2138 | 0 | return mOpaque ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8; |
2139 | 0 | } |
2140 | | |
2141 | | // |
2142 | | // state |
2143 | | // |
2144 | | |
2145 | | void |
2146 | | CanvasRenderingContext2D::Save() |
2147 | 0 | { |
2148 | 0 | EnsureTarget(); |
2149 | 0 | if (MOZ_UNLIKELY(!mTarget || mStyleStack.IsEmpty())) { |
2150 | 0 | SetErrorState(); |
2151 | 0 | return; |
2152 | 0 | } |
2153 | 0 | mStyleStack[mStyleStack.Length() - 1].transform = mTarget->GetTransform(); |
2154 | 0 | mStyleStack.SetCapacity(mStyleStack.Length() + 1); |
2155 | 0 | mStyleStack.AppendElement(CurrentState()); |
2156 | 0 |
|
2157 | 0 | if (mStyleStack.Length() > MAX_STYLE_STACK_SIZE) { |
2158 | 0 | // This is not fast, but is better than OOMing and shouldn't be hit by |
2159 | 0 | // reasonable code. |
2160 | 0 | mStyleStack.RemoveElementAt(0); |
2161 | 0 | } |
2162 | 0 | } |
2163 | | |
2164 | | void |
2165 | | CanvasRenderingContext2D::Restore() |
2166 | 0 | { |
2167 | 0 | if (MOZ_UNLIKELY(mStyleStack.Length() < 2)) { |
2168 | 0 | return; |
2169 | 0 | } |
2170 | 0 | |
2171 | 0 | TransformWillUpdate(); |
2172 | 0 | if (!IsTargetValid()) { |
2173 | 0 | return; |
2174 | 0 | } |
2175 | 0 | |
2176 | 0 | for (const auto& clipOrTransform : CurrentState().clipsAndTransforms) { |
2177 | 0 | if (clipOrTransform.IsClip()) { |
2178 | 0 | mTarget->PopClip(); |
2179 | 0 | } |
2180 | 0 | } |
2181 | 0 |
|
2182 | 0 | mStyleStack.RemoveLastElement(); |
2183 | 0 |
|
2184 | 0 | mTarget->SetTransform(CurrentState().transform); |
2185 | 0 | } |
2186 | | |
2187 | | // |
2188 | | // transformations |
2189 | | // |
2190 | | |
2191 | | void |
2192 | | CanvasRenderingContext2D::Scale(double aX, double aY, ErrorResult& aError) |
2193 | 0 | { |
2194 | 0 | TransformWillUpdate(); |
2195 | 0 | if (!IsTargetValid()) { |
2196 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2197 | 0 | return; |
2198 | 0 | } |
2199 | 0 | |
2200 | 0 | Matrix newMatrix = mTarget->GetTransform(); |
2201 | 0 | newMatrix.PreScale(aX, aY); |
2202 | 0 |
|
2203 | 0 | SetTransformInternal(newMatrix); |
2204 | 0 | } |
2205 | | |
2206 | | void |
2207 | | CanvasRenderingContext2D::Rotate(double aAngle, ErrorResult& aError) |
2208 | 0 | { |
2209 | 0 | TransformWillUpdate(); |
2210 | 0 | if (!IsTargetValid()) { |
2211 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2212 | 0 | return; |
2213 | 0 | } |
2214 | 0 | |
2215 | 0 | Matrix newMatrix = Matrix::Rotation(aAngle) * mTarget->GetTransform(); |
2216 | 0 |
|
2217 | 0 | SetTransformInternal(newMatrix); |
2218 | 0 | } |
2219 | | |
2220 | | void |
2221 | | CanvasRenderingContext2D::Translate(double aX, double aY, ErrorResult& aError) |
2222 | 0 | { |
2223 | 0 | TransformWillUpdate(); |
2224 | 0 | if (!IsTargetValid()) { |
2225 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2226 | 0 | return; |
2227 | 0 | } |
2228 | 0 | |
2229 | 0 | Matrix newMatrix = mTarget->GetTransform(); |
2230 | 0 | newMatrix.PreTranslate(aX, aY); |
2231 | 0 |
|
2232 | 0 | SetTransformInternal(newMatrix); |
2233 | 0 | } |
2234 | | |
2235 | | void |
2236 | | CanvasRenderingContext2D::Transform(double aM11, double aM12, double aM21, |
2237 | | double aM22, double aDx, double aDy, |
2238 | | ErrorResult& aError) |
2239 | 0 | { |
2240 | 0 | TransformWillUpdate(); |
2241 | 0 | if (!IsTargetValid()) { |
2242 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2243 | 0 | return; |
2244 | 0 | } |
2245 | 0 | |
2246 | 0 | Matrix newMatrix(aM11, aM12, aM21, aM22, aDx, aDy); |
2247 | 0 | newMatrix *= mTarget->GetTransform(); |
2248 | 0 |
|
2249 | 0 | SetTransformInternal(newMatrix); |
2250 | 0 | } |
2251 | | |
2252 | | void |
2253 | | CanvasRenderingContext2D::SetTransform(double aM11, double aM12, |
2254 | | double aM21, double aM22, |
2255 | | double aDx, double aDy, |
2256 | | ErrorResult& aError) |
2257 | 0 | { |
2258 | 0 | TransformWillUpdate(); |
2259 | 0 | if (!IsTargetValid()) { |
2260 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2261 | 0 | return; |
2262 | 0 | } |
2263 | 0 | |
2264 | 0 | SetTransformInternal(Matrix(aM11, aM12, aM21, aM22, aDx, aDy)); |
2265 | 0 | } |
2266 | | |
2267 | | void |
2268 | | CanvasRenderingContext2D::SetTransformInternal(const Matrix& aTransform) |
2269 | 0 | { |
2270 | 0 | if (!aTransform.IsFinite()) { |
2271 | 0 | return; |
2272 | 0 | } |
2273 | 0 | |
2274 | 0 | // Save the transform in the clip stack to be able to replay clips properly. |
2275 | 0 | auto& clipsAndTransforms = CurrentState().clipsAndTransforms; |
2276 | 0 | if (clipsAndTransforms.IsEmpty() || clipsAndTransforms.LastElement().IsClip()) { |
2277 | 0 | clipsAndTransforms.AppendElement(ClipState(aTransform)); |
2278 | 0 | } else { |
2279 | 0 | // If the last item is a transform we can replace it instead of appending |
2280 | 0 | // a new item. |
2281 | 0 | clipsAndTransforms.LastElement().transform = aTransform; |
2282 | 0 | } |
2283 | 0 | mTarget->SetTransform(aTransform); |
2284 | 0 | } |
2285 | | |
2286 | | void |
2287 | | CanvasRenderingContext2D::ResetTransform(ErrorResult& aError) |
2288 | 0 | { |
2289 | 0 | SetTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, aError); |
2290 | 0 | } |
2291 | | |
2292 | | static void |
2293 | | MatrixToJSObject(JSContext* aCx, const Matrix& aMatrix, |
2294 | | JS::MutableHandle<JSObject*> aResult, ErrorResult& aError) |
2295 | 0 | { |
2296 | 0 | double elts[6] = { aMatrix._11, aMatrix._12, |
2297 | 0 | aMatrix._21, aMatrix._22, |
2298 | 0 | aMatrix._31, aMatrix._32 }; |
2299 | 0 |
|
2300 | 0 | // XXX Should we enter GetWrapper()'s compartment? |
2301 | 0 | JS::Rooted<JS::Value> val(aCx); |
2302 | 0 | if (!ToJSValue(aCx, elts, &val)) { |
2303 | 0 | aError.Throw(NS_ERROR_OUT_OF_MEMORY); |
2304 | 0 | } else { |
2305 | 0 | aResult.set(&val.toObject()); |
2306 | 0 | } |
2307 | 0 | } |
2308 | | |
2309 | | static bool |
2310 | | ObjectToMatrix(JSContext* aCx, JS::Handle<JSObject*> aObj, Matrix& aMatrix, |
2311 | | ErrorResult& aError) |
2312 | 0 | { |
2313 | 0 | uint32_t length; |
2314 | 0 | if (!JS_GetArrayLength(aCx, aObj, &length) || length != 6) { |
2315 | 0 | // Not an array-like thing or wrong size |
2316 | 0 | aError.Throw(NS_ERROR_INVALID_ARG); |
2317 | 0 | return false; |
2318 | 0 | } |
2319 | 0 | |
2320 | 0 | Float* elts[] = { &aMatrix._11, &aMatrix._12, &aMatrix._21, &aMatrix._22, |
2321 | 0 | &aMatrix._31, &aMatrix._32 }; |
2322 | 0 | for (uint32_t i = 0; i < 6; ++i) { |
2323 | 0 | JS::Rooted<JS::Value> elt(aCx); |
2324 | 0 | double d; |
2325 | 0 | if (!JS_GetElement(aCx, aObj, i, &elt)) { |
2326 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2327 | 0 | return false; |
2328 | 0 | } |
2329 | 0 | if (!CoerceDouble(elt, &d)) { |
2330 | 0 | aError.Throw(NS_ERROR_INVALID_ARG); |
2331 | 0 | return false; |
2332 | 0 | } |
2333 | 0 | if (!FloatValidate(d)) { |
2334 | 0 | // This is weird, but it's the behavior of SetTransform() |
2335 | 0 | return false; |
2336 | 0 | } |
2337 | 0 | *elts[i] = Float(d); |
2338 | 0 | } |
2339 | 0 | return true; |
2340 | 0 | } |
2341 | | |
2342 | | void |
2343 | | CanvasRenderingContext2D::SetMozCurrentTransform(JSContext* aCx, |
2344 | | JS::Handle<JSObject*> aCurrentTransform, |
2345 | | ErrorResult& aError) |
2346 | 0 | { |
2347 | 0 | EnsureTarget(); |
2348 | 0 | if (!IsTargetValid()) { |
2349 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2350 | 0 | return; |
2351 | 0 | } |
2352 | 0 | |
2353 | 0 | Matrix newCTM; |
2354 | 0 | if (ObjectToMatrix(aCx, aCurrentTransform, newCTM, aError) && newCTM.IsFinite()) { |
2355 | 0 | mTarget->SetTransform(newCTM); |
2356 | 0 | } |
2357 | 0 | } |
2358 | | |
2359 | | void |
2360 | | CanvasRenderingContext2D::GetMozCurrentTransform(JSContext* aCx, |
2361 | | JS::MutableHandle<JSObject*> aResult, |
2362 | | ErrorResult& aError) |
2363 | 0 | { |
2364 | 0 | EnsureTarget(); |
2365 | 0 |
|
2366 | 0 | MatrixToJSObject(aCx, mTarget ? mTarget->GetTransform() : Matrix(), |
2367 | 0 | aResult, aError); |
2368 | 0 | } |
2369 | | |
2370 | | void |
2371 | | CanvasRenderingContext2D::SetMozCurrentTransformInverse(JSContext* aCx, |
2372 | | JS::Handle<JSObject*> aCurrentTransform, |
2373 | | ErrorResult& aError) |
2374 | 0 | { |
2375 | 0 | EnsureTarget(); |
2376 | 0 | if (!IsTargetValid()) { |
2377 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2378 | 0 | return; |
2379 | 0 | } |
2380 | 0 | |
2381 | 0 | Matrix newCTMInverse; |
2382 | 0 | if (ObjectToMatrix(aCx, aCurrentTransform, newCTMInverse, aError)) { |
2383 | 0 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
2384 | 0 | if (newCTMInverse.Invert() && newCTMInverse.IsFinite()) { |
2385 | 0 | mTarget->SetTransform(newCTMInverse); |
2386 | 0 | } |
2387 | 0 | } |
2388 | 0 | } |
2389 | | |
2390 | | void |
2391 | | CanvasRenderingContext2D::GetMozCurrentTransformInverse(JSContext* aCx, |
2392 | | JS::MutableHandle<JSObject*> aResult, |
2393 | | ErrorResult& aError) |
2394 | 0 | { |
2395 | 0 | EnsureTarget(); |
2396 | 0 |
|
2397 | 0 | if (!mTarget) { |
2398 | 0 | MatrixToJSObject(aCx, Matrix(), aResult, aError); |
2399 | 0 | return; |
2400 | 0 | } |
2401 | 0 | |
2402 | 0 | Matrix ctm = mTarget->GetTransform(); |
2403 | 0 |
|
2404 | 0 | if (!ctm.Invert()) { |
2405 | 0 | double NaN = JS_GetNaNValue(aCx).toDouble(); |
2406 | 0 | ctm = Matrix(NaN, NaN, NaN, NaN, NaN, NaN); |
2407 | 0 | } |
2408 | 0 |
|
2409 | 0 | MatrixToJSObject(aCx, ctm, aResult, aError); |
2410 | 0 | } |
2411 | | |
2412 | | // |
2413 | | // colors |
2414 | | // |
2415 | | |
2416 | | void |
2417 | | CanvasRenderingContext2D::SetStyleFromUnion(const StringOrCanvasGradientOrCanvasPattern& aValue, |
2418 | | Style aWhichStyle) |
2419 | 0 | { |
2420 | 0 | if (aValue.IsString()) { |
2421 | 0 | SetStyleFromString(aValue.GetAsString(), aWhichStyle); |
2422 | 0 | return; |
2423 | 0 | } |
2424 | 0 | |
2425 | 0 | if (aValue.IsCanvasGradient()) { |
2426 | 0 | SetStyleFromGradient(aValue.GetAsCanvasGradient(), aWhichStyle); |
2427 | 0 | return; |
2428 | 0 | } |
2429 | 0 | |
2430 | 0 | if (aValue.IsCanvasPattern()) { |
2431 | 0 | SetStyleFromPattern(aValue.GetAsCanvasPattern(), aWhichStyle); |
2432 | 0 | return; |
2433 | 0 | } |
2434 | 0 | |
2435 | 0 | MOZ_ASSERT_UNREACHABLE("Invalid union value"); |
2436 | 0 | } |
2437 | | |
2438 | | void |
2439 | | CanvasRenderingContext2D::SetFillRule(const nsAString& aString) |
2440 | 0 | { |
2441 | 0 | FillRule rule; |
2442 | 0 |
|
2443 | 0 | if (aString.EqualsLiteral("evenodd")) |
2444 | 0 | rule = FillRule::FILL_EVEN_ODD; |
2445 | 0 | else if (aString.EqualsLiteral("nonzero")) |
2446 | 0 | rule = FillRule::FILL_WINDING; |
2447 | 0 | else |
2448 | 0 | return; |
2449 | 0 | |
2450 | 0 | CurrentState().fillRule = rule; |
2451 | 0 | } |
2452 | | |
2453 | | void |
2454 | | CanvasRenderingContext2D::GetFillRule(nsAString& aString) |
2455 | | { |
2456 | | switch (CurrentState().fillRule) { |
2457 | | case FillRule::FILL_WINDING: |
2458 | | aString.AssignLiteral("nonzero"); break; |
2459 | | case FillRule::FILL_EVEN_ODD: |
2460 | | aString.AssignLiteral("evenodd"); break; |
2461 | | } |
2462 | | } |
2463 | | // |
2464 | | // gradients and patterns |
2465 | | // |
2466 | | already_AddRefed<CanvasGradient> |
2467 | | CanvasRenderingContext2D::CreateLinearGradient(double aX0, double aY0, double aX1, double aY1) |
2468 | 0 | { |
2469 | 0 | RefPtr<CanvasGradient> grad = |
2470 | 0 | new CanvasLinearGradient(this, Point(aX0, aY0), Point(aX1, aY1)); |
2471 | 0 |
|
2472 | 0 | return grad.forget(); |
2473 | 0 | } |
2474 | | |
2475 | | already_AddRefed<CanvasGradient> |
2476 | | CanvasRenderingContext2D::CreateRadialGradient(double aX0, double aY0, double aR0, |
2477 | | double aX1, double aY1, double aR1, |
2478 | | ErrorResult& aError) |
2479 | 0 | { |
2480 | 0 | if (aR0 < 0.0 || aR1 < 0.0) { |
2481 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
2482 | 0 | return nullptr; |
2483 | 0 | } |
2484 | 0 | |
2485 | 0 | RefPtr<CanvasGradient> grad = |
2486 | 0 | new CanvasRadialGradient(this, Point(aX0, aY0), aR0, Point(aX1, aY1), aR1); |
2487 | 0 |
|
2488 | 0 | return grad.forget(); |
2489 | 0 | } |
2490 | | |
2491 | | already_AddRefed<CanvasPattern> |
2492 | | CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& aSource, |
2493 | | const nsAString& aRepeat, |
2494 | | ErrorResult& aError) |
2495 | 0 | { |
2496 | 0 | CanvasPattern::RepeatMode repeatMode = |
2497 | 0 | CanvasPattern::RepeatMode::NOREPEAT; |
2498 | 0 |
|
2499 | 0 | if (aRepeat.IsEmpty() || aRepeat.EqualsLiteral("repeat")) { |
2500 | 0 | repeatMode = CanvasPattern::RepeatMode::REPEAT; |
2501 | 0 | } else if (aRepeat.EqualsLiteral("repeat-x")) { |
2502 | 0 | repeatMode = CanvasPattern::RepeatMode::REPEATX; |
2503 | 0 | } else if (aRepeat.EqualsLiteral("repeat-y")) { |
2504 | 0 | repeatMode = CanvasPattern::RepeatMode::REPEATY; |
2505 | 0 | } else if (aRepeat.EqualsLiteral("no-repeat")) { |
2506 | 0 | repeatMode = CanvasPattern::RepeatMode::NOREPEAT; |
2507 | 0 | } else { |
2508 | 0 | aError.Throw(NS_ERROR_DOM_SYNTAX_ERR); |
2509 | 0 | return nullptr; |
2510 | 0 | } |
2511 | 0 | |
2512 | 0 | Element* element; |
2513 | 0 | if (aSource.IsHTMLCanvasElement()) { |
2514 | 0 | HTMLCanvasElement* canvas = &aSource.GetAsHTMLCanvasElement(); |
2515 | 0 | element = canvas; |
2516 | 0 |
|
2517 | 0 | nsIntSize size = canvas->GetSize(); |
2518 | 0 | if (size.width == 0 || size.height == 0) { |
2519 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2520 | 0 | return nullptr; |
2521 | 0 | } |
2522 | 0 | |
2523 | 0 | // Special case for Canvas, which could be an Azure canvas! |
2524 | 0 | nsICanvasRenderingContextInternal* srcCanvas = canvas->GetContextAtIndex(0); |
2525 | 0 | if (srcCanvas) { |
2526 | 0 | // This might not be an Azure canvas! |
2527 | 0 | RefPtr<SourceSurface> srcSurf = srcCanvas->GetSurfaceSnapshot(); |
2528 | 0 | if (!srcSurf) { |
2529 | 0 | JSContext* context = nsContentUtils::GetCurrentJSContext(); |
2530 | 0 | if (context) { |
2531 | 0 | JS_ReportWarningASCII(context, |
2532 | 0 | "CanvasRenderingContext2D.createPattern()" |
2533 | 0 | " failed to snapshot source canvas."); |
2534 | 0 | } |
2535 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2536 | 0 | return nullptr; |
2537 | 0 | } |
2538 | 0 |
|
2539 | 0 | RefPtr<CanvasPattern> pat = |
2540 | 0 | new CanvasPattern(this, srcSurf, repeatMode, element->NodePrincipal(), canvas->IsWriteOnly(), false); |
2541 | 0 |
|
2542 | 0 | return pat.forget(); |
2543 | 0 | } |
2544 | 0 | } else if (aSource.IsHTMLImageElement()) { |
2545 | 0 | HTMLImageElement* img = &aSource.GetAsHTMLImageElement(); |
2546 | 0 | if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) { |
2547 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2548 | 0 | return nullptr; |
2549 | 0 | } |
2550 | 0 | |
2551 | 0 | element = img; |
2552 | 0 | } else if (aSource.IsSVGImageElement()) { |
2553 | 0 | SVGImageElement* img = &aSource.GetAsSVGImageElement(); |
2554 | 0 | if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) { |
2555 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2556 | 0 | return nullptr; |
2557 | 0 | } |
2558 | 0 | |
2559 | 0 | element = img; |
2560 | 0 | } else if (aSource.IsHTMLVideoElement()) { |
2561 | 0 | auto& video = aSource.GetAsHTMLVideoElement(); |
2562 | 0 | video.MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::CREATE_PATTERN); |
2563 | 0 | element = &video; |
2564 | 0 | } else { |
2565 | 0 | // Special case for ImageBitmap |
2566 | 0 | ImageBitmap& imgBitmap = aSource.GetAsImageBitmap(); |
2567 | 0 | EnsureTarget(); |
2568 | 0 | if (!IsTargetValid()) { |
2569 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2570 | 0 | return nullptr; |
2571 | 0 | } |
2572 | 0 | RefPtr<SourceSurface> srcSurf = imgBitmap.PrepareForDrawTarget(mTarget); |
2573 | 0 | if (!srcSurf) { |
2574 | 0 | JSContext* context = nsContentUtils::GetCurrentJSContext(); |
2575 | 0 | if (context) { |
2576 | 0 | JS_ReportWarningASCII(context, |
2577 | 0 | "CanvasRenderingContext2D.createPattern()" |
2578 | 0 | " failed to prepare source ImageBitmap."); |
2579 | 0 | } |
2580 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2581 | 0 | return nullptr; |
2582 | 0 | } |
2583 | 0 |
|
2584 | 0 | // An ImageBitmap never taints others so we set principalForSecurityCheck to |
2585 | 0 | // nullptr and set CORSUsed to true for passing the security check in |
2586 | 0 | // CanvasUtils::DoDrawImageSecurityCheck(). |
2587 | 0 | RefPtr<CanvasPattern> pat = |
2588 | 0 | new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true); |
2589 | 0 |
|
2590 | 0 | return pat.forget(); |
2591 | 0 | } |
2592 | 0 | |
2593 | 0 | EnsureTarget(); |
2594 | 0 | if (!IsTargetValid()) { |
2595 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
2596 | 0 | return nullptr; |
2597 | 0 | } |
2598 | 0 | |
2599 | 0 | // The canvas spec says that createPattern should use the first frame |
2600 | 0 | // of animated images |
2601 | 0 | nsLayoutUtils::SurfaceFromElementResult res = |
2602 | 0 | nsLayoutUtils::SurfaceFromElement(element, |
2603 | 0 | nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE, mTarget); |
2604 | 0 |
|
2605 | 0 | if (!res.GetSourceSurface()) { |
2606 | 0 | return nullptr; |
2607 | 0 | } |
2608 | 0 | |
2609 | 0 | RefPtr<CanvasPattern> pat = new CanvasPattern(this, res.GetSourceSurface(), repeatMode, |
2610 | 0 | res.mPrincipal, res.mIsWriteOnly, |
2611 | 0 | res.mCORSUsed); |
2612 | 0 | return pat.forget(); |
2613 | 0 | } |
2614 | | |
2615 | | // |
2616 | | // shadows |
2617 | | // |
2618 | | void |
2619 | | CanvasRenderingContext2D::SetShadowColor(const nsAString& aShadowColor) |
2620 | 0 | { |
2621 | 0 | nscolor color; |
2622 | 0 | if (!ParseColor(aShadowColor, &color)) { |
2623 | 0 | return; |
2624 | 0 | } |
2625 | 0 | |
2626 | 0 | CurrentState().shadowColor = color; |
2627 | 0 | } |
2628 | | |
2629 | | // |
2630 | | // filters |
2631 | | // |
2632 | | |
2633 | | |
2634 | | static already_AddRefed<RawServoDeclarationBlock> |
2635 | | CreateDeclarationForServo(nsCSSPropertyID aProperty, |
2636 | | const nsAString& aPropertyValue, |
2637 | | nsIDocument* aDocument) |
2638 | 0 | { |
2639 | 0 | RefPtr<URLExtraData> data = |
2640 | 0 | new URLExtraData(aDocument->GetDocBaseURI(), |
2641 | 0 | aDocument->GetDocumentURI(), |
2642 | 0 | aDocument->NodePrincipal(), |
2643 | 0 | aDocument->GetReferrerPolicy()); |
2644 | 0 |
|
2645 | 0 | ServoCSSParser::ParsingEnvironment env(data, |
2646 | 0 | aDocument->GetCompatibilityMode(), |
2647 | 0 | aDocument->CSSLoader()); |
2648 | 0 | RefPtr<RawServoDeclarationBlock> servoDeclarations = |
2649 | 0 | ServoCSSParser::ParseProperty(aProperty, aPropertyValue, env); |
2650 | 0 |
|
2651 | 0 | if (!servoDeclarations) { |
2652 | 0 | // We got a syntax error. The spec says this value must be ignored. |
2653 | 0 | return nullptr; |
2654 | 0 | } |
2655 | 0 | |
2656 | 0 | // From canvas spec, force to set line-height property to 'normal' font |
2657 | 0 | // property. |
2658 | 0 | if (aProperty == eCSSProperty_font) { |
2659 | 0 | const nsCString normalString = NS_LITERAL_CSTRING("normal"); |
2660 | 0 | Servo_DeclarationBlock_SetPropertyById(servoDeclarations, |
2661 | 0 | eCSSProperty_line_height, |
2662 | 0 | &normalString, |
2663 | 0 | false, |
2664 | 0 | data, |
2665 | 0 | ParsingMode::Default, |
2666 | 0 | aDocument->GetCompatibilityMode(), |
2667 | 0 | aDocument->CSSLoader(), |
2668 | 0 | { }); |
2669 | 0 | } |
2670 | 0 |
|
2671 | 0 | return servoDeclarations.forget(); |
2672 | 0 | } |
2673 | | |
2674 | | static already_AddRefed<RawServoDeclarationBlock> |
2675 | | CreateFontDeclarationForServo(const nsAString& aFont, |
2676 | | nsIDocument* aDocument) |
2677 | 0 | { |
2678 | 0 | return CreateDeclarationForServo(eCSSProperty_font, aFont, aDocument); |
2679 | 0 | } |
2680 | | |
2681 | | static already_AddRefed<ComputedStyle> |
2682 | | GetFontStyleForServo(Element* aElement, const nsAString& aFont, |
2683 | | nsIPresShell* aPresShell, |
2684 | | nsAString& aOutUsedFont, |
2685 | | ErrorResult& aError) |
2686 | 0 | { |
2687 | 0 | RefPtr<RawServoDeclarationBlock> declarations = |
2688 | 0 | CreateFontDeclarationForServo(aFont, aPresShell->GetDocument()); |
2689 | 0 | if (!declarations) { |
2690 | 0 | // We got a syntax error. The spec says this value must be ignored. |
2691 | 0 | return nullptr; |
2692 | 0 | } |
2693 | 0 | |
2694 | 0 | // In addition to unparseable values, the spec says we need to reject |
2695 | 0 | // 'inherit' and 'initial'. The easiest way to check for this is to look |
2696 | 0 | // at font-size-adjust, which the font shorthand resets to 'none'. |
2697 | 0 | if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, |
2698 | 0 | eCSSProperty_font_size_adjust)) { |
2699 | 0 | return nullptr; |
2700 | 0 | } |
2701 | 0 | |
2702 | 0 | ServoStyleSet* styleSet = aPresShell->StyleSet(); |
2703 | 0 |
|
2704 | 0 | RefPtr<ComputedStyle> parentStyle; |
2705 | 0 | // have to get a parent ComputedStyle for inherit-like relative |
2706 | 0 | // values (2em, bolder, etc.) |
2707 | 0 | if (aElement && aElement->IsInComposedDoc()) { |
2708 | 0 | parentStyle = nsComputedDOMStyle::GetComputedStyle(aElement, nullptr); |
2709 | 0 | if (!parentStyle) { |
2710 | 0 | // The flush killed the shell, so we couldn't get any meaningful style |
2711 | 0 | // back. |
2712 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2713 | 0 | return nullptr; |
2714 | 0 | } |
2715 | 0 | } else { |
2716 | 0 | RefPtr<RawServoDeclarationBlock> declarations = |
2717 | 0 | CreateFontDeclarationForServo(NS_LITERAL_STRING("10px sans-serif"), |
2718 | 0 | aPresShell->GetDocument()); |
2719 | 0 | MOZ_ASSERT(declarations); |
2720 | 0 |
|
2721 | 0 | parentStyle = aPresShell->StyleSet()-> |
2722 | 0 | ResolveForDeclarations(nullptr, declarations); |
2723 | 0 | } |
2724 | 0 |
|
2725 | 0 | MOZ_RELEASE_ASSERT(parentStyle, "Should have a valid parent style"); |
2726 | 0 |
|
2727 | 0 | MOZ_ASSERT(!aPresShell->IsDestroying(), |
2728 | 0 | "We should have returned an error above if the presshell is " |
2729 | 0 | "being destroyed."); |
2730 | 0 |
|
2731 | 0 | RefPtr<ComputedStyle> sc = |
2732 | 0 | styleSet->ResolveForDeclarations(parentStyle, declarations); |
2733 | 0 |
|
2734 | 0 | // The font getter is required to be reserialized based on what we |
2735 | 0 | // parsed (including having line-height removed). (Older drafts of |
2736 | 0 | // the spec required font sizes be converted to pixels, but that no |
2737 | 0 | // longer seems to be required.) |
2738 | 0 | Servo_SerializeFontValueForCanvas(declarations, &aOutUsedFont); |
2739 | 0 | return sc.forget(); |
2740 | 0 | } |
2741 | | |
2742 | | |
2743 | | static already_AddRefed<RawServoDeclarationBlock> |
2744 | | CreateFilterDeclarationForServo(const nsAString& aFilter, |
2745 | | nsIDocument* aDocument) |
2746 | 0 | { |
2747 | 0 | return CreateDeclarationForServo(eCSSProperty_filter, aFilter, aDocument); |
2748 | 0 | } |
2749 | | |
2750 | | static already_AddRefed<ComputedStyle> |
2751 | | ResolveFilterStyleForServo(const nsAString& aFilterString, |
2752 | | const ComputedStyle* aParentStyle, |
2753 | | nsIPresShell* aPresShell, |
2754 | | ErrorResult& aError) |
2755 | 0 | { |
2756 | 0 | RefPtr<RawServoDeclarationBlock> declarations = |
2757 | 0 | CreateFilterDeclarationForServo(aFilterString, aPresShell->GetDocument()); |
2758 | 0 | if (!declarations) { |
2759 | 0 | // Refuse to accept the filter, but do not throw an error. |
2760 | 0 | return nullptr; |
2761 | 0 | } |
2762 | 0 | |
2763 | 0 | // In addition to unparseable values, the spec says we need to reject |
2764 | 0 | // 'inherit' and 'initial'. |
2765 | 0 | if (Servo_DeclarationBlock_HasCSSWideKeyword(declarations, |
2766 | 0 | eCSSProperty_filter)) { |
2767 | 0 | return nullptr; |
2768 | 0 | } |
2769 | 0 | |
2770 | 0 | ServoStyleSet* styleSet = aPresShell->StyleSet(); |
2771 | 0 | RefPtr<ComputedStyle> computedValues = |
2772 | 0 | styleSet->ResolveForDeclarations(aParentStyle, declarations); |
2773 | 0 |
|
2774 | 0 | return computedValues.forget(); |
2775 | 0 | } |
2776 | | |
2777 | | bool |
2778 | | CanvasRenderingContext2D::ParseFilter(const nsAString& aString, |
2779 | | nsTArray<nsStyleFilter>& aFilterChain, |
2780 | | ErrorResult& aError) |
2781 | 0 | { |
2782 | 0 | if (!mCanvasElement && !mDocShell) { |
2783 | 0 | NS_WARNING("Canvas element must be non-null or a docshell must be provided"); |
2784 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2785 | 0 | return false; |
2786 | 0 | } |
2787 | 0 |
|
2788 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
2789 | 0 | if (!presShell) { |
2790 | 0 | aError.Throw(NS_ERROR_FAILURE); |
2791 | 0 | return false; |
2792 | 0 | } |
2793 | 0 | |
2794 | 0 | nsString usedFont; // unused |
2795 | 0 |
|
2796 | 0 | RefPtr<ComputedStyle> parentStyle = |
2797 | 0 | GetFontStyleForServo(mCanvasElement, |
2798 | 0 | GetFont(), |
2799 | 0 | presShell, |
2800 | 0 | usedFont, |
2801 | 0 | aError); |
2802 | 0 | if (!parentStyle) { |
2803 | 0 | return false; |
2804 | 0 | } |
2805 | 0 | |
2806 | 0 | RefPtr<ComputedStyle> computedValues = |
2807 | 0 | ResolveFilterStyleForServo(aString, |
2808 | 0 | parentStyle, |
2809 | 0 | presShell, |
2810 | 0 | aError); |
2811 | 0 | if (!computedValues) { |
2812 | 0 | return false; |
2813 | 0 | } |
2814 | 0 | |
2815 | 0 | const nsStyleEffects* effects = computedValues->ComputedData()->GetStyleEffects(); |
2816 | 0 | // XXX: This mFilters is a one shot object, we probably could avoid copying. |
2817 | 0 | aFilterChain = effects->mFilters; |
2818 | 0 | return true; |
2819 | 0 | } |
2820 | | |
2821 | | void |
2822 | | CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError) |
2823 | 0 | { |
2824 | 0 | nsTArray<nsStyleFilter> filterChain; |
2825 | 0 | if (ParseFilter(aFilter, filterChain, aError)) { |
2826 | 0 | CurrentState().filterString = aFilter; |
2827 | 0 | filterChain.SwapElements(CurrentState().filterChain); |
2828 | 0 | if (mCanvasElement) { |
2829 | 0 | CurrentState().filterObserverList = |
2830 | 0 | new SVGFilterObserverListForCanvas(CurrentState().filterChain, |
2831 | 0 | mCanvasElement, this); |
2832 | 0 | UpdateFilter(); |
2833 | 0 | } |
2834 | 0 | } |
2835 | 0 | } |
2836 | | |
2837 | | class CanvasUserSpaceMetrics : public UserSpaceMetricsWithSize |
2838 | | { |
2839 | | public: |
2840 | | CanvasUserSpaceMetrics(const gfx::IntSize& aSize, const nsFont& aFont, |
2841 | | nsAtom* aFontLanguage, bool aExplicitLanguage, |
2842 | | nsPresContext* aPresContext) |
2843 | | : mSize(aSize) |
2844 | | , mFont(aFont) |
2845 | | , mFontLanguage(aFontLanguage) |
2846 | | , mExplicitLanguage(aExplicitLanguage) |
2847 | | , mPresContext(aPresContext) |
2848 | 0 | { |
2849 | 0 | } |
2850 | | |
2851 | | virtual float GetEmLength() const override |
2852 | 0 | { |
2853 | 0 | return NSAppUnitsToFloatPixels(mFont.size, |
2854 | 0 | AppUnitsPerCSSPixel()); |
2855 | 0 | } |
2856 | | |
2857 | | virtual float GetExLength() const override |
2858 | 0 | { |
2859 | 0 | nsDeviceContext* dc = mPresContext->DeviceContext(); |
2860 | 0 | nsFontMetrics::Params params; |
2861 | 0 | params.language = mFontLanguage; |
2862 | 0 | params.explicitLanguage = mExplicitLanguage; |
2863 | 0 | params.textPerf = mPresContext->GetTextPerfMetrics(); |
2864 | 0 | RefPtr<nsFontMetrics> fontMetrics = dc->GetMetricsFor(mFont, params); |
2865 | 0 | return NSAppUnitsToFloatPixels(fontMetrics->XHeight(), |
2866 | 0 | AppUnitsPerCSSPixel()); |
2867 | 0 | } |
2868 | | |
2869 | | virtual gfx::Size GetSize() const override |
2870 | 0 | { return Size(mSize); } |
2871 | | |
2872 | | private: |
2873 | | gfx::IntSize mSize; |
2874 | | const nsFont& mFont; |
2875 | | nsAtom* mFontLanguage; |
2876 | | bool mExplicitLanguage; |
2877 | | nsPresContext* mPresContext; |
2878 | | }; |
2879 | | |
2880 | | void |
2881 | | CanvasRenderingContext2D::UpdateFilter() |
2882 | 0 | { |
2883 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
2884 | 0 | if (!presShell || presShell->IsDestroying()) { |
2885 | 0 | // Ensure we set an empty filter and update the state to |
2886 | 0 | // reflect the current "taint" status of the canvas |
2887 | 0 | CurrentState().filter = FilterDescription(); |
2888 | 0 | CurrentState().filterSourceGraphicTainted = |
2889 | 0 | (mCanvasElement && mCanvasElement->IsWriteOnly()); |
2890 | 0 | return; |
2891 | 0 | } |
2892 | 0 |
|
2893 | 0 | // The filter might reference an SVG filter that is declared inside this |
2894 | 0 | // document. Flush frames so that we'll have an nsSVGFilterFrame to work |
2895 | 0 | // with. |
2896 | 0 | presShell->FlushPendingNotifications(FlushType::Frames); |
2897 | 0 | MOZ_RELEASE_ASSERT(!mStyleStack.IsEmpty()); |
2898 | 0 | if (MOZ_UNLIKELY(presShell->IsDestroying())) { |
2899 | 0 | return; |
2900 | 0 | } |
2901 | 0 | |
2902 | 0 | bool sourceGraphicIsTainted = |
2903 | 0 | (mCanvasElement && mCanvasElement->IsWriteOnly()); |
2904 | 0 |
|
2905 | 0 | CurrentState().filter = |
2906 | 0 | nsFilterInstance::GetFilterDescription(mCanvasElement, |
2907 | 0 | CurrentState().filterChain, |
2908 | 0 | sourceGraphicIsTainted, |
2909 | 0 | CanvasUserSpaceMetrics(GetSize(), |
2910 | 0 | CurrentState().fontFont, |
2911 | 0 | CurrentState().fontLanguage, |
2912 | 0 | CurrentState().fontExplicitLanguage, |
2913 | 0 | presShell->GetPresContext()), |
2914 | 0 | gfxRect(0, 0, mWidth, mHeight), |
2915 | 0 | CurrentState().filterAdditionalImages); |
2916 | 0 | CurrentState().filterSourceGraphicTainted = sourceGraphicIsTainted; |
2917 | 0 | } |
2918 | | |
2919 | | // |
2920 | | // rects |
2921 | | // |
2922 | | |
2923 | | static bool |
2924 | | ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZeroSizeValid) |
2925 | 0 | { |
2926 | 0 | if (!aIsZeroSizeValid && (aWidth == 0.0 || aHeight == 0.0)) { |
2927 | 0 | return false; |
2928 | 0 | } |
2929 | 0 | |
2930 | 0 | // bug 1018527 |
2931 | 0 | // The values of canvas API input are in double precision, but Moz2D APIs are |
2932 | 0 | // using float precision. Bypass canvas API calls when the input is out of |
2933 | 0 | // float precision to avoid precision problem |
2934 | 0 | if (!std::isfinite((float)aX) | !std::isfinite((float)aY) | |
2935 | 0 | !std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) { |
2936 | 0 | return false; |
2937 | 0 | } |
2938 | 0 | |
2939 | 0 | // bug 1074733 |
2940 | 0 | // The canvas spec does not forbid rects with negative w or h, so given |
2941 | 0 | // corners (x, y), (x+w, y), (x+w, y+h), and (x, y+h) we must generate |
2942 | 0 | // the appropriate rect by flipping negative dimensions. This prevents |
2943 | 0 | // draw targets from receiving "empty" rects later on. |
2944 | 0 | if (aWidth < 0) { |
2945 | 0 | aWidth = -aWidth; |
2946 | 0 | aX -= aWidth; |
2947 | 0 | } |
2948 | 0 | if (aHeight < 0) { |
2949 | 0 | aHeight = -aHeight; |
2950 | 0 | aY -= aHeight; |
2951 | 0 | } |
2952 | 0 | return true; |
2953 | 0 | } |
2954 | | |
2955 | | void |
2956 | | CanvasRenderingContext2D::ClearRect(double aX, double aY, double aW, |
2957 | | double aH) |
2958 | 0 | { |
2959 | 0 | // Do not allow zeros - it's a no-op at that point per spec. |
2960 | 0 | if (!ValidateRect(aX, aY, aW, aH, false)) { |
2961 | 0 | return; |
2962 | 0 | } |
2963 | 0 | |
2964 | 0 | gfx::Rect clearRect(aX, aY, aW, aH); |
2965 | 0 |
|
2966 | 0 | EnsureTarget(&clearRect); |
2967 | 0 | if (!IsTargetValid()) { |
2968 | 0 | return; |
2969 | 0 | } |
2970 | 0 | |
2971 | 0 | mTarget->ClearRect(clearRect); |
2972 | 0 |
|
2973 | 0 | RedrawUser(gfxRect(aX, aY, aW, aH)); |
2974 | 0 | } |
2975 | | |
2976 | | void |
2977 | | CanvasRenderingContext2D::FillRect(double aX, double aY, double aW, double aH) |
2978 | 0 | { |
2979 | 0 | if (!ValidateRect(aX, aY, aW, aH, true)) { |
2980 | 0 | return; |
2981 | 0 | } |
2982 | 0 | |
2983 | 0 | const ContextState* state = &CurrentState(); |
2984 | 0 | if (state->patternStyles[Style::FILL]) { |
2985 | 0 | CanvasPattern::RepeatMode repeat = |
2986 | 0 | state->patternStyles[Style::FILL]->mRepeat; |
2987 | 0 | // In the FillRect case repeat modes are easy to deal with. |
2988 | 0 | bool limitx = repeat == CanvasPattern::RepeatMode::NOREPEAT || |
2989 | 0 | repeat == CanvasPattern::RepeatMode::REPEATY; |
2990 | 0 | bool limity = repeat == CanvasPattern::RepeatMode::NOREPEAT || |
2991 | 0 | repeat == CanvasPattern::RepeatMode::REPEATX; |
2992 | 0 |
|
2993 | 0 | IntSize patternSize = |
2994 | 0 | state->patternStyles[Style::FILL]->mSurface->GetSize(); |
2995 | 0 |
|
2996 | 0 | // We always need to execute painting for non-over operators, even if |
2997 | 0 | // we end up with w/h = 0. |
2998 | 0 | if (limitx) { |
2999 | 0 | if (aX < 0) { |
3000 | 0 | aW += aX; |
3001 | 0 | if (aW < 0) { |
3002 | 0 | aW = 0; |
3003 | 0 | } |
3004 | 0 |
|
3005 | 0 | aX = 0; |
3006 | 0 | } |
3007 | 0 | if (aX + aW > patternSize.width) { |
3008 | 0 | aW = patternSize.width - aX; |
3009 | 0 | if (aW < 0) { |
3010 | 0 | aW = 0; |
3011 | 0 | } |
3012 | 0 | } |
3013 | 0 | } |
3014 | 0 | if (limity) { |
3015 | 0 | if (aY < 0) { |
3016 | 0 | aH += aY; |
3017 | 0 | if (aH < 0) { |
3018 | 0 | aH = 0; |
3019 | 0 | } |
3020 | 0 |
|
3021 | 0 | aY = 0; |
3022 | 0 | } |
3023 | 0 | if (aY + aH > patternSize.height) { |
3024 | 0 | aH = patternSize.height - aY; |
3025 | 0 | if (aH < 0) { |
3026 | 0 | aH = 0; |
3027 | 0 | } |
3028 | 0 | } |
3029 | 0 | } |
3030 | 0 | } |
3031 | 0 | state = nullptr; |
3032 | 0 |
|
3033 | 0 | CompositionOp op = UsedOperation(); |
3034 | 0 | bool discardContent = PatternIsOpaque(Style::FILL) |
3035 | 0 | && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); |
3036 | 0 | const gfx::Rect fillRect(aX, aY, aW, aH); |
3037 | 0 | EnsureTarget(discardContent ? &fillRect : nullptr); |
3038 | 0 | if (!IsTargetValid()) { |
3039 | 0 | return; |
3040 | 0 | } |
3041 | 0 | |
3042 | 0 | gfx::Rect bounds; |
3043 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3044 | 0 | if (!IsTargetValid()) { |
3045 | 0 | return; |
3046 | 0 | } |
3047 | 0 | if (needBounds) { |
3048 | 0 | bounds = mTarget->GetTransform().TransformBounds(fillRect); |
3049 | 0 | } |
3050 | 0 |
|
3051 | 0 | AntialiasMode antialiasMode = CurrentState().imageSmoothingEnabled ? |
3052 | 0 | AntialiasMode::DEFAULT : AntialiasMode::NONE; |
3053 | 0 |
|
3054 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3055 | 0 | if (!target) { |
3056 | 0 | return; |
3057 | 0 | } |
3058 | 0 | target->FillRect(gfx::Rect(aX, aY, aW, aH), |
3059 | 0 | CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
3060 | 0 | DrawOptions(CurrentState().globalAlpha, op, antialiasMode)); |
3061 | 0 |
|
3062 | 0 | RedrawUser(gfxRect(aX, aY, aW, aH)); |
3063 | 0 | } |
3064 | | |
3065 | | void |
3066 | | CanvasRenderingContext2D::StrokeRect(double aX, double aY, double aW, double aH) |
3067 | 0 | { |
3068 | 0 | if (!aW && !aH) { |
3069 | 0 | return; |
3070 | 0 | } |
3071 | 0 | |
3072 | 0 | if (!ValidateRect(aX, aY, aW, aH, true)) { |
3073 | 0 | return; |
3074 | 0 | } |
3075 | 0 | |
3076 | 0 | EnsureTarget(); |
3077 | 0 | if (!IsTargetValid()) { |
3078 | 0 | return; |
3079 | 0 | } |
3080 | 0 | |
3081 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3082 | 0 | if (!IsTargetValid()) { |
3083 | 0 | return; |
3084 | 0 | } |
3085 | 0 | |
3086 | 0 | gfx::Rect bounds; |
3087 | 0 | if (needBounds) { |
3088 | 0 | const ContextState& state = CurrentState(); |
3089 | 0 | bounds = gfx::Rect(aX - state.lineWidth / 2.0f, aY - state.lineWidth / 2.0f, |
3090 | 0 | aW + state.lineWidth, aH + state.lineWidth); |
3091 | 0 | bounds = mTarget->GetTransform().TransformBounds(bounds); |
3092 | 0 | } |
3093 | 0 |
|
3094 | 0 | auto op = UsedOperation(); |
3095 | 0 | if (!IsTargetValid()) { |
3096 | 0 | return; |
3097 | 0 | } |
3098 | 0 | |
3099 | 0 | if (!aH) { |
3100 | 0 | CapStyle cap = CapStyle::BUTT; |
3101 | 0 | if (CurrentState().lineJoin == JoinStyle::ROUND) { |
3102 | 0 | cap = CapStyle::ROUND; |
3103 | 0 | } |
3104 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3105 | 0 | if (!target) { |
3106 | 0 | return; |
3107 | 0 | } |
3108 | 0 | |
3109 | 0 | const ContextState& state = CurrentState(); |
3110 | 0 | target-> |
3111 | 0 | StrokeLine(Point(aX, aY), Point(aX + aW, aY), |
3112 | 0 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
3113 | 0 | StrokeOptions(state.lineWidth, state.lineJoin, |
3114 | 0 | cap, state.miterLimit, |
3115 | 0 | state.dash.Length(), |
3116 | 0 | state.dash.Elements(), |
3117 | 0 | state.dashOffset), |
3118 | 0 | DrawOptions(state.globalAlpha, op)); |
3119 | 0 | return; |
3120 | 0 | } |
3121 | 0 | |
3122 | 0 | if (!aW) { |
3123 | 0 | CapStyle cap = CapStyle::BUTT; |
3124 | 0 | if (CurrentState().lineJoin == JoinStyle::ROUND) { |
3125 | 0 | cap = CapStyle::ROUND; |
3126 | 0 | } |
3127 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3128 | 0 | if (!target) { |
3129 | 0 | return; |
3130 | 0 | } |
3131 | 0 | |
3132 | 0 | const ContextState& state = CurrentState(); |
3133 | 0 | target-> |
3134 | 0 | StrokeLine(Point(aX, aY), Point(aX, aY + aH), |
3135 | 0 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
3136 | 0 | StrokeOptions(state.lineWidth, state.lineJoin, |
3137 | 0 | cap, state.miterLimit, |
3138 | 0 | state.dash.Length(), |
3139 | 0 | state.dash.Elements(), |
3140 | 0 | state.dashOffset), |
3141 | 0 | DrawOptions(state.globalAlpha, op)); |
3142 | 0 | return; |
3143 | 0 | } |
3144 | 0 | |
3145 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3146 | 0 | if (!target) { |
3147 | 0 | return; |
3148 | 0 | } |
3149 | 0 | |
3150 | 0 | const ContextState& state = CurrentState(); |
3151 | 0 | target-> |
3152 | 0 | StrokeRect(gfx::Rect(aX, aY, aW, aH), |
3153 | 0 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
3154 | 0 | StrokeOptions(state.lineWidth, state.lineJoin, |
3155 | 0 | state.lineCap, state.miterLimit, |
3156 | 0 | state.dash.Length(), |
3157 | 0 | state.dash.Elements(), |
3158 | 0 | state.dashOffset), |
3159 | 0 | DrawOptions(state.globalAlpha, op)); |
3160 | 0 |
|
3161 | 0 | Redraw(); |
3162 | 0 | } |
3163 | | |
3164 | | // |
3165 | | // path bits |
3166 | | // |
3167 | | |
3168 | | void |
3169 | | CanvasRenderingContext2D::BeginPath() |
3170 | 0 | { |
3171 | 0 | mPath = nullptr; |
3172 | 0 | mPathBuilder = nullptr; |
3173 | 0 | mDSPathBuilder = nullptr; |
3174 | 0 | mPathTransformWillUpdate = false; |
3175 | 0 | } |
3176 | | |
3177 | | void |
3178 | | CanvasRenderingContext2D::Fill(const CanvasWindingRule& aWinding) |
3179 | 0 | { |
3180 | 0 | EnsureUserSpacePath(aWinding); |
3181 | 0 |
|
3182 | 0 | if (!mPath) { |
3183 | 0 | return; |
3184 | 0 | } |
3185 | 0 | |
3186 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3187 | 0 | if (!IsTargetValid()) { |
3188 | 0 | return; |
3189 | 0 | } |
3190 | 0 | gfx::Rect bounds; |
3191 | 0 | if (needBounds) { |
3192 | 0 | bounds = mPath->GetBounds(mTarget->GetTransform()); |
3193 | 0 | } |
3194 | 0 |
|
3195 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3196 | 0 | if (!target) { |
3197 | 0 | return; |
3198 | 0 | } |
3199 | 0 | |
3200 | 0 | auto op = UsedOperation(); |
3201 | 0 | if (!IsTargetValid() || !target) { |
3202 | 0 | return; |
3203 | 0 | } |
3204 | 0 | target->Fill(mPath, |
3205 | 0 | CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
3206 | 0 | DrawOptions(CurrentState().globalAlpha, op)); |
3207 | 0 | Redraw(); |
3208 | 0 | } |
3209 | | |
3210 | | void CanvasRenderingContext2D::Fill(const CanvasPath& aPath, const CanvasWindingRule& aWinding) |
3211 | 0 | { |
3212 | 0 | EnsureTarget(); |
3213 | 0 | if (!IsTargetValid()) { |
3214 | 0 | return; |
3215 | 0 | } |
3216 | 0 | |
3217 | 0 | RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget); |
3218 | 0 | if (!gfxpath) { |
3219 | 0 | return; |
3220 | 0 | } |
3221 | 0 | |
3222 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3223 | 0 | if (!IsTargetValid()) { |
3224 | 0 | return; |
3225 | 0 | } |
3226 | 0 | gfx::Rect bounds; |
3227 | 0 | if (needBounds) { |
3228 | 0 | bounds = gfxpath->GetBounds(mTarget->GetTransform()); |
3229 | 0 | } |
3230 | 0 |
|
3231 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3232 | 0 | if (!target) { |
3233 | 0 | return; |
3234 | 0 | } |
3235 | 0 | |
3236 | 0 | auto op = UsedOperation(); |
3237 | 0 | if (!IsTargetValid() || !target) { |
3238 | 0 | return; |
3239 | 0 | } |
3240 | 0 | target->Fill(gfxpath, |
3241 | 0 | CanvasGeneralPattern().ForStyle(this, Style::FILL, mTarget), |
3242 | 0 | DrawOptions(CurrentState().globalAlpha, op)); |
3243 | 0 | Redraw(); |
3244 | 0 | } |
3245 | | |
3246 | | void |
3247 | | CanvasRenderingContext2D::Stroke() |
3248 | 0 | { |
3249 | 0 | EnsureUserSpacePath(); |
3250 | 0 |
|
3251 | 0 | if (!mPath) { |
3252 | 0 | return; |
3253 | 0 | } |
3254 | 0 | |
3255 | 0 | const ContextState* state = &CurrentState(); |
3256 | 0 | StrokeOptions strokeOptions(state->lineWidth, state->lineJoin, |
3257 | 0 | state->lineCap, state->miterLimit, |
3258 | 0 | state->dash.Length(), state->dash.Elements(), |
3259 | 0 | state->dashOffset); |
3260 | 0 | state = nullptr; |
3261 | 0 |
|
3262 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3263 | 0 | if (!IsTargetValid()) { |
3264 | 0 | return; |
3265 | 0 | } |
3266 | 0 | gfx::Rect bounds; |
3267 | 0 | if (needBounds) { |
3268 | 0 | bounds = |
3269 | 0 | mPath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); |
3270 | 0 | } |
3271 | 0 |
|
3272 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3273 | 0 | if (!target) { |
3274 | 0 | return; |
3275 | 0 | } |
3276 | 0 | |
3277 | 0 | auto op = UsedOperation(); |
3278 | 0 | if (!IsTargetValid() || !target) { |
3279 | 0 | return; |
3280 | 0 | } |
3281 | 0 | target->Stroke(mPath, |
3282 | 0 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
3283 | 0 | strokeOptions, DrawOptions(CurrentState().globalAlpha, op)); |
3284 | 0 | Redraw(); |
3285 | 0 | } |
3286 | | |
3287 | | void |
3288 | | CanvasRenderingContext2D::Stroke(const CanvasPath& aPath) |
3289 | 0 | { |
3290 | 0 | EnsureTarget(); |
3291 | 0 | if (!IsTargetValid()) { |
3292 | 0 | return; |
3293 | 0 | } |
3294 | 0 | |
3295 | 0 | RefPtr<gfx::Path> gfxpath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget); |
3296 | 0 |
|
3297 | 0 | if (!gfxpath) { |
3298 | 0 | return; |
3299 | 0 | } |
3300 | 0 | |
3301 | 0 | const ContextState* state = &CurrentState(); |
3302 | 0 | StrokeOptions strokeOptions(state->lineWidth, state->lineJoin, |
3303 | 0 | state->lineCap, state->miterLimit, |
3304 | 0 | state->dash.Length(), state->dash.Elements(), |
3305 | 0 | state->dashOffset); |
3306 | 0 | state = nullptr; |
3307 | 0 |
|
3308 | 0 | const bool needBounds = NeedToCalculateBounds(); |
3309 | 0 | if (!IsTargetValid()) { |
3310 | 0 | return; |
3311 | 0 | } |
3312 | 0 | gfx::Rect bounds; |
3313 | 0 | if (needBounds) { |
3314 | 0 | bounds = |
3315 | 0 | gfxpath->GetStrokedBounds(strokeOptions, mTarget->GetTransform()); |
3316 | 0 | } |
3317 | 0 |
|
3318 | 0 | AdjustedTarget target(this, bounds.IsEmpty() ? nullptr : &bounds); |
3319 | 0 | if (!target) { |
3320 | 0 | return; |
3321 | 0 | } |
3322 | 0 | |
3323 | 0 | auto op = UsedOperation(); |
3324 | 0 | if (!IsTargetValid() || !target) { |
3325 | 0 | return; |
3326 | 0 | } |
3327 | 0 | target->Stroke(gfxpath, |
3328 | 0 | CanvasGeneralPattern().ForStyle(this, Style::STROKE, mTarget), |
3329 | 0 | strokeOptions, DrawOptions(CurrentState().globalAlpha, op)); |
3330 | 0 | Redraw(); |
3331 | 0 | } |
3332 | | |
3333 | | void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement, |
3334 | | ErrorResult& aRv) |
3335 | 0 | { |
3336 | 0 | EnsureUserSpacePath(); |
3337 | 0 | if (!mPath) { |
3338 | 0 | return; |
3339 | 0 | } |
3340 | 0 | |
3341 | 0 | if (DrawCustomFocusRing(aElement)) { |
3342 | 0 | AutoSaveRestore asr(this); |
3343 | 0 |
|
3344 | 0 | // set state to conforming focus state |
3345 | 0 | ContextState* state = &CurrentState(); |
3346 | 0 | state->globalAlpha = 1.0; |
3347 | 0 | state->shadowBlur = 0; |
3348 | 0 | state->shadowOffset.x = 0; |
3349 | 0 | state->shadowOffset.y = 0; |
3350 | 0 | state->op = mozilla::gfx::CompositionOp::OP_OVER; |
3351 | 0 |
|
3352 | 0 | state->lineCap = CapStyle::BUTT; |
3353 | 0 | state->lineJoin = mozilla::gfx::JoinStyle::MITER_OR_BEVEL; |
3354 | 0 | state->lineWidth = 1; |
3355 | 0 | state->dash.Clear(); |
3356 | 0 |
|
3357 | 0 | // color and style of the rings is the same as for image maps |
3358 | 0 | // set the background focus color |
3359 | 0 | state->SetColorStyle(Style::STROKE, NS_RGBA(255, 255, 255, 255)); |
3360 | 0 | state = nullptr; |
3361 | 0 |
|
3362 | 0 | // draw the focus ring |
3363 | 0 | Stroke(); |
3364 | 0 | if (!mPath) { |
3365 | 0 | return; |
3366 | 0 | } |
3367 | 0 | |
3368 | 0 | // set dashing for foreground |
3369 | 0 | nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash; |
3370 | 0 | for (uint32_t i = 0; i < 2; ++i) { |
3371 | 0 | if (!dash.AppendElement(1, fallible)) { |
3372 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
3373 | 0 | return; |
3374 | 0 | } |
3375 | 0 | } |
3376 | 0 |
|
3377 | 0 | // set the foreground focus color |
3378 | 0 | CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); |
3379 | 0 | // draw the focus ring |
3380 | 0 | Stroke(); |
3381 | 0 | if (!mPath) { |
3382 | 0 | return; |
3383 | 0 | } |
3384 | 0 | } |
3385 | 0 | } |
3386 | | |
3387 | | bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement) |
3388 | 0 | { |
3389 | 0 | EnsureUserSpacePath(); |
3390 | 0 |
|
3391 | 0 | HTMLCanvasElement* canvas = GetCanvas(); |
3392 | 0 |
|
3393 | 0 | if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) { |
3394 | 0 | return false; |
3395 | 0 | } |
3396 | 0 | |
3397 | 0 | nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
3398 | 0 | if (fm) { |
3399 | 0 | // check that the element is focused |
3400 | 0 | if (&aElement == fm->GetFocusedElement()) { |
3401 | 0 | if (nsPIDOMWindowOuter* window = aElement.OwnerDoc()->GetWindow()) { |
3402 | 0 | return window->ShouldShowFocusRing(); |
3403 | 0 | } |
3404 | 0 | } |
3405 | 0 | } |
3406 | 0 | |
3407 | 0 | return false; |
3408 | 0 | } |
3409 | | |
3410 | | void |
3411 | | CanvasRenderingContext2D::Clip(const CanvasWindingRule& aWinding) |
3412 | 0 | { |
3413 | 0 | EnsureUserSpacePath(aWinding); |
3414 | 0 |
|
3415 | 0 | if (!mPath) { |
3416 | 0 | return; |
3417 | 0 | } |
3418 | 0 | |
3419 | 0 | mTarget->PushClip(mPath); |
3420 | 0 | CurrentState().clipsAndTransforms.AppendElement(ClipState(mPath)); |
3421 | 0 | } |
3422 | | |
3423 | | void |
3424 | | CanvasRenderingContext2D::Clip(const CanvasPath& aPath, const CanvasWindingRule& aWinding) |
3425 | 0 | { |
3426 | 0 | EnsureTarget(); |
3427 | 0 | if (!IsTargetValid()) { |
3428 | 0 | return; |
3429 | 0 | } |
3430 | 0 | |
3431 | 0 | RefPtr<gfx::Path> gfxpath = aPath.GetPath(aWinding, mTarget); |
3432 | 0 |
|
3433 | 0 | if (!gfxpath) { |
3434 | 0 | return; |
3435 | 0 | } |
3436 | 0 | |
3437 | 0 | mTarget->PushClip(gfxpath); |
3438 | 0 | CurrentState().clipsAndTransforms.AppendElement(ClipState(gfxpath)); |
3439 | 0 | } |
3440 | | |
3441 | | void |
3442 | | CanvasRenderingContext2D::ArcTo(double aX1, double aY1, double aX2, |
3443 | | double aY2, double aRadius, |
3444 | | ErrorResult& aError) |
3445 | 0 | { |
3446 | 0 | if (aRadius < 0) { |
3447 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
3448 | 0 | return; |
3449 | 0 | } |
3450 | 0 | |
3451 | 0 | EnsureWritablePath(); |
3452 | 0 |
|
3453 | 0 | // Current point in user space! |
3454 | 0 | Point p0; |
3455 | 0 | if (mPathBuilder) { |
3456 | 0 | p0 = mPathBuilder->CurrentPoint(); |
3457 | 0 | } else { |
3458 | 0 | Matrix invTransform = mTarget->GetTransform(); |
3459 | 0 | if (!invTransform.Invert()) { |
3460 | 0 | return; |
3461 | 0 | } |
3462 | 0 | |
3463 | 0 | p0 = invTransform.TransformPoint(mDSPathBuilder->CurrentPoint()); |
3464 | 0 | } |
3465 | 0 |
|
3466 | 0 | Point p1(aX1, aY1); |
3467 | 0 | Point p2(aX2, aY2); |
3468 | 0 |
|
3469 | 0 | // Execute these calculations in double precision to avoid cumulative |
3470 | 0 | // rounding errors. |
3471 | 0 | double dir, a2, b2, c2, cosx, sinx, d, anx, any, |
3472 | 0 | bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; |
3473 | 0 | bool anticlockwise; |
3474 | 0 |
|
3475 | 0 | if (p0 == p1 || p1 == p2 || aRadius == 0) { |
3476 | 0 | LineTo(p1.x, p1.y); |
3477 | 0 | return; |
3478 | 0 | } |
3479 | 0 | |
3480 | 0 | // Check for colinearity |
3481 | 0 | dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); |
3482 | 0 | if (dir == 0) { |
3483 | 0 | LineTo(p1.x, p1.y); |
3484 | 0 | return; |
3485 | 0 | } |
3486 | 0 | |
3487 | 0 | |
3488 | 0 | // XXX - Math for this code was already available from the non-azure code |
3489 | 0 | // and would be well tested. Perhaps converting to bezier directly might |
3490 | 0 | // be more efficient longer run. |
3491 | 0 | a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1); |
3492 | 0 | b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2); |
3493 | 0 | c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2); |
3494 | 0 | cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); |
3495 | 0 |
|
3496 | 0 | sinx = sqrt(1 - cosx*cosx); |
3497 | 0 | d = aRadius / ((1 - cosx) / sinx); |
3498 | 0 |
|
3499 | 0 | anx = (aX1-p0.x) / sqrt(a2); |
3500 | 0 | any = (aY1-p0.y) / sqrt(a2); |
3501 | 0 | bnx = (aX1-aX2) / sqrt(b2); |
3502 | 0 | bny = (aY1-aY2) / sqrt(b2); |
3503 | 0 | x3 = aX1 - anx*d; |
3504 | 0 | y3 = aY1 - any*d; |
3505 | 0 | x4 = aX1 - bnx*d; |
3506 | 0 | y4 = aY1 - bny*d; |
3507 | 0 | anticlockwise = (dir < 0); |
3508 | 0 | cx = x3 + any*aRadius*(anticlockwise ? 1 : -1); |
3509 | 0 | cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1); |
3510 | 0 | angle0 = atan2((y3-cy), (x3-cx)); |
3511 | 0 | angle1 = atan2((y4-cy), (x4-cx)); |
3512 | 0 |
|
3513 | 0 |
|
3514 | 0 | LineTo(x3, y3); |
3515 | 0 |
|
3516 | 0 | Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError); |
3517 | 0 | } |
3518 | | |
3519 | | void |
3520 | | CanvasRenderingContext2D::Arc(double aX, double aY, double aR, |
3521 | | double aStartAngle, double aEndAngle, |
3522 | | bool aAnticlockwise, ErrorResult& aError) |
3523 | 0 | { |
3524 | 0 | if (aR < 0.0) { |
3525 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
3526 | 0 | return; |
3527 | 0 | } |
3528 | 0 | |
3529 | 0 | EnsureWritablePath(); |
3530 | 0 |
|
3531 | 0 | ArcToBezier(this, Point(aX, aY), Size(aR, aR), aStartAngle, aEndAngle, aAnticlockwise); |
3532 | 0 | } |
3533 | | |
3534 | | void |
3535 | | CanvasRenderingContext2D::Rect(double aX, double aY, double aW, double aH) |
3536 | 0 | { |
3537 | 0 | EnsureWritablePath(); |
3538 | 0 |
|
3539 | 0 | if (mPathBuilder) { |
3540 | 0 | mPathBuilder->MoveTo(Point(aX, aY)); |
3541 | 0 | mPathBuilder->LineTo(Point(aX + aW, aY)); |
3542 | 0 | mPathBuilder->LineTo(Point(aX + aW, aY + aH)); |
3543 | 0 | mPathBuilder->LineTo(Point(aX, aY + aH)); |
3544 | 0 | mPathBuilder->Close(); |
3545 | 0 | } else { |
3546 | 0 | mDSPathBuilder->MoveTo(mTarget->GetTransform().TransformPoint(Point(aX, aY))); |
3547 | 0 | mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY))); |
3548 | 0 | mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX + aW, aY + aH))); |
3549 | 0 | mDSPathBuilder->LineTo(mTarget->GetTransform().TransformPoint(Point(aX, aY + aH))); |
3550 | 0 | mDSPathBuilder->Close(); |
3551 | 0 | } |
3552 | 0 | } |
3553 | | |
3554 | | void |
3555 | | CanvasRenderingContext2D::Ellipse(double aX, double aY, double aRadiusX, double aRadiusY, |
3556 | | double aRotation, double aStartAngle, double aEndAngle, |
3557 | | bool aAnticlockwise, ErrorResult& aError) |
3558 | 0 | { |
3559 | 0 | if (aRadiusX < 0.0 || aRadiusY < 0.0) { |
3560 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
3561 | 0 | return; |
3562 | 0 | } |
3563 | 0 | |
3564 | 0 | EnsureWritablePath(); |
3565 | 0 |
|
3566 | 0 | ArcToBezier(this, Point(aX, aY), Size(aRadiusX, aRadiusY), aStartAngle, aEndAngle, |
3567 | 0 | aAnticlockwise, aRotation); |
3568 | 0 | } |
3569 | | |
3570 | | void |
3571 | | CanvasRenderingContext2D::EnsureWritablePath() |
3572 | 0 | { |
3573 | 0 | EnsureTarget(); |
3574 | 0 | // NOTE: IsTargetValid() may be false here (mTarget == sErrorTarget) but we |
3575 | 0 | // go ahead and create a path anyway since callers depend on that. |
3576 | 0 |
|
3577 | 0 | if (mDSPathBuilder) { |
3578 | 0 | return; |
3579 | 0 | } |
3580 | 0 | |
3581 | 0 | FillRule fillRule = CurrentState().fillRule; |
3582 | 0 |
|
3583 | 0 | if (mPathBuilder) { |
3584 | 0 | if (mPathTransformWillUpdate) { |
3585 | 0 | mPath = mPathBuilder->Finish(); |
3586 | 0 | mDSPathBuilder = |
3587 | 0 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
3588 | 0 | mPath = nullptr; |
3589 | 0 | mPathBuilder = nullptr; |
3590 | 0 | mPathTransformWillUpdate = false; |
3591 | 0 | } |
3592 | 0 | return; |
3593 | 0 | } |
3594 | 0 |
|
3595 | 0 | if (!mPath) { |
3596 | 0 | NS_ASSERTION(!mPathTransformWillUpdate, "mPathTransformWillUpdate should be false, if all paths are null"); |
3597 | 0 | mPathBuilder = mTarget->CreatePathBuilder(fillRule); |
3598 | 0 | } else if (!mPathTransformWillUpdate) { |
3599 | 0 | mPathBuilder = mPath->CopyToBuilder(fillRule); |
3600 | 0 | } else { |
3601 | 0 | mDSPathBuilder = |
3602 | 0 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
3603 | 0 | mPathTransformWillUpdate = false; |
3604 | 0 | mPath = nullptr; |
3605 | 0 | } |
3606 | 0 | } |
3607 | | |
3608 | | void |
3609 | | CanvasRenderingContext2D::EnsureUserSpacePath(const CanvasWindingRule& aWinding) |
3610 | 0 | { |
3611 | 0 | FillRule fillRule = CurrentState().fillRule; |
3612 | 0 | if (aWinding == CanvasWindingRule::Evenodd) |
3613 | 0 | fillRule = FillRule::FILL_EVEN_ODD; |
3614 | 0 |
|
3615 | 0 | EnsureTarget(); |
3616 | 0 | if (!IsTargetValid()) { |
3617 | 0 | return; |
3618 | 0 | } |
3619 | 0 | |
3620 | 0 | if (!mPath && !mPathBuilder && !mDSPathBuilder) { |
3621 | 0 | mPathBuilder = mTarget->CreatePathBuilder(fillRule); |
3622 | 0 | } |
3623 | 0 |
|
3624 | 0 | if (mPathBuilder) { |
3625 | 0 | mPath = mPathBuilder->Finish(); |
3626 | 0 | mPathBuilder = nullptr; |
3627 | 0 | } |
3628 | 0 |
|
3629 | 0 | if (mPath && |
3630 | 0 | mPathTransformWillUpdate) { |
3631 | 0 | mDSPathBuilder = |
3632 | 0 | mPath->TransformedCopyToBuilder(mPathToDS, fillRule); |
3633 | 0 | mPath = nullptr; |
3634 | 0 | mPathTransformWillUpdate = false; |
3635 | 0 | } |
3636 | 0 |
|
3637 | 0 | if (mDSPathBuilder) { |
3638 | 0 | RefPtr<Path> dsPath; |
3639 | 0 | dsPath = mDSPathBuilder->Finish(); |
3640 | 0 | mDSPathBuilder = nullptr; |
3641 | 0 |
|
3642 | 0 | Matrix inverse = mTarget->GetTransform(); |
3643 | 0 | if (!inverse.Invert()) { |
3644 | 0 | NS_WARNING("Could not invert transform"); |
3645 | 0 | return; |
3646 | 0 | } |
3647 | 0 |
|
3648 | 0 | mPathBuilder = |
3649 | 0 | dsPath->TransformedCopyToBuilder(inverse, fillRule); |
3650 | 0 | mPath = mPathBuilder->Finish(); |
3651 | 0 | mPathBuilder = nullptr; |
3652 | 0 | } |
3653 | 0 |
|
3654 | 0 | if (mPath && mPath->GetFillRule() != fillRule) { |
3655 | 0 | mPathBuilder = mPath->CopyToBuilder(fillRule); |
3656 | 0 | mPath = mPathBuilder->Finish(); |
3657 | 0 | mPathBuilder = nullptr; |
3658 | 0 | } |
3659 | 0 |
|
3660 | 0 | NS_ASSERTION(mPath, "mPath should exist"); |
3661 | 0 | } |
3662 | | |
3663 | | void |
3664 | | CanvasRenderingContext2D::TransformWillUpdate() |
3665 | 0 | { |
3666 | 0 | EnsureTarget(); |
3667 | 0 | if (!IsTargetValid()) { |
3668 | 0 | return; |
3669 | 0 | } |
3670 | 0 | |
3671 | 0 | // Store the matrix that would transform the current path to device |
3672 | 0 | // space. |
3673 | 0 | if (mPath || mPathBuilder) { |
3674 | 0 | if (!mPathTransformWillUpdate) { |
3675 | 0 | // If the transform has already been updated, but a device space builder |
3676 | 0 | // has not been created yet mPathToDS contains the right transform to |
3677 | 0 | // transform the current mPath into device space. |
3678 | 0 | // We should leave it alone. |
3679 | 0 | mPathToDS = mTarget->GetTransform(); |
3680 | 0 | } |
3681 | 0 | mPathTransformWillUpdate = true; |
3682 | 0 | } |
3683 | 0 | } |
3684 | | |
3685 | | // |
3686 | | // text |
3687 | | // |
3688 | | |
3689 | | void |
3690 | | CanvasRenderingContext2D::SetFont(const nsAString& aFont, |
3691 | | ErrorResult& aError) |
3692 | 0 | { |
3693 | 0 | SetFontInternal(aFont, aError); |
3694 | 0 | } |
3695 | | |
3696 | | bool |
3697 | | CanvasRenderingContext2D::SetFontInternal(const nsAString& aFont, |
3698 | | ErrorResult& aError) |
3699 | 0 | { |
3700 | 0 | /* |
3701 | 0 | * If font is defined with relative units (e.g. ems) and the parent |
3702 | 0 | * ComputedStyle changes in between calls, setting the font to the |
3703 | 0 | * same value as previous could result in a different computed value, |
3704 | 0 | * so we cannot have the optimization where we check if the new font |
3705 | 0 | * string is equal to the old one. |
3706 | 0 | */ |
3707 | 0 |
|
3708 | 0 | if (!mCanvasElement && !mDocShell) { |
3709 | 0 | NS_WARNING("Canvas element must be non-null or a docshell must be provided"); |
3710 | 0 | aError.Throw(NS_ERROR_FAILURE); |
3711 | 0 | return false; |
3712 | 0 | } |
3713 | 0 |
|
3714 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
3715 | 0 | if (!presShell) { |
3716 | 0 | aError.Throw(NS_ERROR_FAILURE); |
3717 | 0 | return false; |
3718 | 0 | } |
3719 | 0 | |
3720 | 0 | nsString usedFont; |
3721 | 0 | RefPtr<ComputedStyle> sc = |
3722 | 0 | GetFontStyleForServo(mCanvasElement, aFont, presShell, usedFont, aError); |
3723 | 0 | if (!sc) { |
3724 | 0 | return false; |
3725 | 0 | } |
3726 | 0 | |
3727 | 0 | const nsStyleFont* fontStyle = sc->StyleFont(); |
3728 | 0 | nsPresContext* c = presShell->GetPresContext(); |
3729 | 0 |
|
3730 | 0 | // Purposely ignore the font size that respects the user's minimum |
3731 | 0 | // font preference (fontStyle->mFont.size) in favor of the computed |
3732 | 0 | // size (fontStyle->mSize). See |
3733 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=698652. |
3734 | 0 | // FIXME: Nobody initializes mAllowZoom for servo? |
3735 | 0 | //MOZ_ASSERT(!fontStyle->mAllowZoom, |
3736 | 0 | // "expected text zoom to be disabled on this nsStyleFont"); |
3737 | 0 | nsFont resizedFont(fontStyle->mFont); |
3738 | 0 | // Create a font group working in units of CSS pixels instead of the usual |
3739 | 0 | // device pixels, to avoid being affected by page zoom. nsFontMetrics will |
3740 | 0 | // convert nsFont size in app units to device pixels for the font group, so |
3741 | 0 | // here we first apply to the size the equivalent of a conversion from device |
3742 | 0 | // pixels to CSS pixels, to adjust for the difference in expectations from |
3743 | 0 | // other nsFontMetrics clients. |
3744 | 0 | resizedFont.size = |
3745 | 0 | (fontStyle->mSize * c->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel(); |
3746 | 0 |
|
3747 | 0 | nsFontMetrics::Params params; |
3748 | 0 | params.language = fontStyle->mLanguage; |
3749 | 0 | params.explicitLanguage = fontStyle->mExplicitLanguage; |
3750 | 0 | params.userFontSet = c->GetUserFontSet(); |
3751 | 0 | params.textPerf = c->GetTextPerfMetrics(); |
3752 | 0 | RefPtr<nsFontMetrics> metrics = |
3753 | 0 | c->DeviceContext()->GetMetricsFor(resizedFont, params); |
3754 | 0 |
|
3755 | 0 | gfxFontGroup* newFontGroup = metrics->GetThebesFontGroup(); |
3756 | 0 | CurrentState().fontGroup = newFontGroup; |
3757 | 0 | NS_ASSERTION(CurrentState().fontGroup, "Could not get font group"); |
3758 | 0 | CurrentState().font = usedFont; |
3759 | 0 | CurrentState().fontFont = fontStyle->mFont; |
3760 | 0 | CurrentState().fontFont.size = fontStyle->mSize; |
3761 | 0 | CurrentState().fontLanguage = fontStyle->mLanguage; |
3762 | 0 | CurrentState().fontExplicitLanguage = fontStyle->mExplicitLanguage; |
3763 | 0 |
|
3764 | 0 | return true; |
3765 | 0 | } |
3766 | | |
3767 | | void |
3768 | | CanvasRenderingContext2D::SetTextAlign(const nsAString& aTextAlign) |
3769 | 0 | { |
3770 | 0 | if (aTextAlign.EqualsLiteral("start")) |
3771 | 0 | CurrentState().textAlign = TextAlign::START; |
3772 | 0 | else if (aTextAlign.EqualsLiteral("end")) |
3773 | 0 | CurrentState().textAlign = TextAlign::END; |
3774 | 0 | else if (aTextAlign.EqualsLiteral("left")) |
3775 | 0 | CurrentState().textAlign = TextAlign::LEFT; |
3776 | 0 | else if (aTextAlign.EqualsLiteral("right")) |
3777 | 0 | CurrentState().textAlign = TextAlign::RIGHT; |
3778 | 0 | else if (aTextAlign.EqualsLiteral("center")) |
3779 | 0 | CurrentState().textAlign = TextAlign::CENTER; |
3780 | 0 | } |
3781 | | |
3782 | | void |
3783 | | CanvasRenderingContext2D::GetTextAlign(nsAString& aTextAlign) |
3784 | | { |
3785 | | switch (CurrentState().textAlign) |
3786 | | { |
3787 | | case TextAlign::START: |
3788 | | aTextAlign.AssignLiteral("start"); |
3789 | | break; |
3790 | | case TextAlign::END: |
3791 | | aTextAlign.AssignLiteral("end"); |
3792 | | break; |
3793 | | case TextAlign::LEFT: |
3794 | | aTextAlign.AssignLiteral("left"); |
3795 | | break; |
3796 | | case TextAlign::RIGHT: |
3797 | | aTextAlign.AssignLiteral("right"); |
3798 | | break; |
3799 | | case TextAlign::CENTER: |
3800 | | aTextAlign.AssignLiteral("center"); |
3801 | | break; |
3802 | | } |
3803 | | } |
3804 | | |
3805 | | void |
3806 | | CanvasRenderingContext2D::SetTextBaseline(const nsAString& aTextBaseline) |
3807 | 0 | { |
3808 | 0 | if (aTextBaseline.EqualsLiteral("top")) |
3809 | 0 | CurrentState().textBaseline = TextBaseline::TOP; |
3810 | 0 | else if (aTextBaseline.EqualsLiteral("hanging")) |
3811 | 0 | CurrentState().textBaseline = TextBaseline::HANGING; |
3812 | 0 | else if (aTextBaseline.EqualsLiteral("middle")) |
3813 | 0 | CurrentState().textBaseline = TextBaseline::MIDDLE; |
3814 | 0 | else if (aTextBaseline.EqualsLiteral("alphabetic")) |
3815 | 0 | CurrentState().textBaseline = TextBaseline::ALPHABETIC; |
3816 | 0 | else if (aTextBaseline.EqualsLiteral("ideographic")) |
3817 | 0 | CurrentState().textBaseline = TextBaseline::IDEOGRAPHIC; |
3818 | 0 | else if (aTextBaseline.EqualsLiteral("bottom")) |
3819 | 0 | CurrentState().textBaseline = TextBaseline::BOTTOM; |
3820 | 0 | } |
3821 | | |
3822 | | void |
3823 | | CanvasRenderingContext2D::GetTextBaseline(nsAString& aTextBaseline) |
3824 | | { |
3825 | | switch (CurrentState().textBaseline) |
3826 | | { |
3827 | | case TextBaseline::TOP: |
3828 | | aTextBaseline.AssignLiteral("top"); |
3829 | | break; |
3830 | | case TextBaseline::HANGING: |
3831 | | aTextBaseline.AssignLiteral("hanging"); |
3832 | | break; |
3833 | | case TextBaseline::MIDDLE: |
3834 | | aTextBaseline.AssignLiteral("middle"); |
3835 | | break; |
3836 | | case TextBaseline::ALPHABETIC: |
3837 | | aTextBaseline.AssignLiteral("alphabetic"); |
3838 | | break; |
3839 | | case TextBaseline::IDEOGRAPHIC: |
3840 | | aTextBaseline.AssignLiteral("ideographic"); |
3841 | | break; |
3842 | | case TextBaseline::BOTTOM: |
3843 | | aTextBaseline.AssignLiteral("bottom"); |
3844 | | break; |
3845 | | } |
3846 | | } |
3847 | | |
3848 | | /* |
3849 | | * Helper function that replaces the whitespace characters in a string |
3850 | | * with U+0020 SPACE. The whitespace characters are defined as U+0020 SPACE, |
3851 | | * U+0009 CHARACTER TABULATION (tab), U+000A LINE FEED (LF), U+000B LINE |
3852 | | * TABULATION, U+000C FORM FEED (FF), and U+000D CARRIAGE RETURN (CR). |
3853 | | * @param str The string whose whitespace characters to replace. |
3854 | | */ |
3855 | | static inline void |
3856 | | TextReplaceWhitespaceCharacters(nsAutoString& aStr) |
3857 | 0 | { |
3858 | 0 | aStr.ReplaceChar("\x09\x0A\x0B\x0C\x0D", char16_t(' ')); |
3859 | 0 | } |
3860 | | |
3861 | | void |
3862 | | CanvasRenderingContext2D::FillText(const nsAString& aText, double aX, |
3863 | | double aY, |
3864 | | const Optional<double>& aMaxWidth, |
3865 | | ErrorResult& aError) |
3866 | 0 | { |
3867 | 0 | aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::FILL, nullptr); |
3868 | 0 | } |
3869 | | |
3870 | | void |
3871 | | CanvasRenderingContext2D::StrokeText(const nsAString& aText, double aX, |
3872 | | double aY, |
3873 | | const Optional<double>& aMaxWidth, |
3874 | | ErrorResult& aError) |
3875 | 0 | { |
3876 | 0 | aError = DrawOrMeasureText(aText, aX, aY, aMaxWidth, TextDrawOperation::STROKE, nullptr); |
3877 | 0 | } |
3878 | | |
3879 | | TextMetrics* |
3880 | | CanvasRenderingContext2D::MeasureText(const nsAString& aRawText, |
3881 | | ErrorResult& aError) |
3882 | 0 | { |
3883 | 0 | float width; |
3884 | 0 | Optional<double> maxWidth; |
3885 | 0 | aError = DrawOrMeasureText(aRawText, 0, 0, maxWidth, TextDrawOperation::MEASURE, &width); |
3886 | 0 | if (aError.Failed()) { |
3887 | 0 | return nullptr; |
3888 | 0 | } |
3889 | 0 | |
3890 | 0 | return new TextMetrics(width); |
3891 | 0 | } |
3892 | | |
3893 | | void |
3894 | | CanvasRenderingContext2D::AddHitRegion(const HitRegionOptions& aOptions, ErrorResult& aError) |
3895 | 0 | { |
3896 | 0 | RefPtr<gfx::Path> path; |
3897 | 0 | if (aOptions.mPath) { |
3898 | 0 | EnsureTarget(); |
3899 | 0 | if (!IsTargetValid()) { |
3900 | 0 | return; |
3901 | 0 | } |
3902 | 0 | path = aOptions.mPath->GetPath(CanvasWindingRule::Nonzero, mTarget); |
3903 | 0 | } |
3904 | 0 |
|
3905 | 0 | if (!path) { |
3906 | 0 | // check if the path is valid |
3907 | 0 | EnsureUserSpacePath(CanvasWindingRule::Nonzero); |
3908 | 0 | path = mPath; |
3909 | 0 | } |
3910 | 0 |
|
3911 | 0 | if (!path) { |
3912 | 0 | aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
3913 | 0 | return; |
3914 | 0 | } |
3915 | 0 | |
3916 | 0 | // get the bounds of the current path. They are relative to the canvas |
3917 | 0 | gfx::Rect bounds(path->GetBounds(mTarget->GetTransform())); |
3918 | 0 | if ((bounds.width == 0) || (bounds.height == 0) || !bounds.IsFinite()) { |
3919 | 0 | // The specified region has no pixels. |
3920 | 0 | aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
3921 | 0 | return; |
3922 | 0 | } |
3923 | 0 | |
3924 | 0 | // remove old hit region first |
3925 | 0 | RemoveHitRegion(aOptions.mId); |
3926 | 0 |
|
3927 | 0 | if (aOptions.mControl) { |
3928 | 0 | // also remove regions with this control |
3929 | 0 | for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) { |
3930 | 0 | RegionInfo& info = mHitRegionsOptions[x]; |
3931 | 0 | if (info.mElement == aOptions.mControl) { |
3932 | 0 | mHitRegionsOptions.RemoveElementAt(x); |
3933 | 0 | break; |
3934 | 0 | } |
3935 | 0 | } |
3936 | 0 | #ifdef ACCESSIBILITY |
3937 | 0 | aOptions.mControl->SetProperty(nsGkAtoms::hitregion, new bool(true), |
3938 | 0 | nsINode::DeleteProperty<bool>); |
3939 | 0 | #endif |
3940 | 0 | } |
3941 | 0 |
|
3942 | 0 | // finally, add the region to the list |
3943 | 0 | RegionInfo info; |
3944 | 0 | info.mId = aOptions.mId; |
3945 | 0 | info.mElement = aOptions.mControl; |
3946 | 0 | RefPtr<PathBuilder> pathBuilder = path->TransformedCopyToBuilder(mTarget->GetTransform()); |
3947 | 0 | info.mPath = pathBuilder->Finish(); |
3948 | 0 |
|
3949 | 0 | mHitRegionsOptions.InsertElementAt(0, info); |
3950 | 0 | } |
3951 | | |
3952 | | void |
3953 | | CanvasRenderingContext2D::RemoveHitRegion(const nsAString& aId) |
3954 | 0 | { |
3955 | 0 | if (aId.Length() == 0) { |
3956 | 0 | return; |
3957 | 0 | } |
3958 | 0 | |
3959 | 0 | for (size_t x = 0; x < mHitRegionsOptions.Length(); x++) { |
3960 | 0 | RegionInfo& info = mHitRegionsOptions[x]; |
3961 | 0 | if (info.mId == aId) { |
3962 | 0 | mHitRegionsOptions.RemoveElementAt(x); |
3963 | 0 |
|
3964 | 0 | return; |
3965 | 0 | } |
3966 | 0 | } |
3967 | 0 | } |
3968 | | |
3969 | | void |
3970 | | CanvasRenderingContext2D::ClearHitRegions() |
3971 | 0 | { |
3972 | 0 | mHitRegionsOptions.Clear(); |
3973 | 0 | } |
3974 | | |
3975 | | bool |
3976 | | CanvasRenderingContext2D::GetHitRegionRect(Element* aElement, nsRect& aRect) |
3977 | 0 | { |
3978 | 0 | for (unsigned int x = 0; x < mHitRegionsOptions.Length(); x++) { |
3979 | 0 | RegionInfo& info = mHitRegionsOptions[x]; |
3980 | 0 | if (info.mElement == aElement) { |
3981 | 0 | gfx::Rect bounds(info.mPath->GetBounds()); |
3982 | 0 | gfxRect rect(bounds.x, bounds.y, bounds.width, bounds.height); |
3983 | 0 | aRect = nsLayoutUtils::RoundGfxRectToAppRect(rect, AppUnitsPerCSSPixel()); |
3984 | 0 |
|
3985 | 0 | return true; |
3986 | 0 | } |
3987 | 0 | } |
3988 | 0 |
|
3989 | 0 | return false; |
3990 | 0 | } |
3991 | | |
3992 | | /** |
3993 | | * Used for nsBidiPresUtils::ProcessText |
3994 | | */ |
3995 | | struct MOZ_STACK_CLASS CanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor |
3996 | | { |
3997 | | typedef CanvasRenderingContext2D::Style Style; |
3998 | | |
3999 | | CanvasBidiProcessor() |
4000 | | : nsBidiPresUtils::BidiProcessor() |
4001 | | , mCtx(nullptr) |
4002 | | , mFontgrp(nullptr) |
4003 | | , mAppUnitsPerDevPixel(0) |
4004 | | , mOp(CanvasRenderingContext2D::TextDrawOperation::FILL) |
4005 | | , mTextRunFlags() |
4006 | | , mDoMeasureBoundingBox(false) |
4007 | 0 | { |
4008 | 0 | if (Preferences::GetBool(GFX_MISSING_FONTS_NOTIFY_PREF)) { |
4009 | 0 | mMissingFonts = new gfxMissingFontRecorder(); |
4010 | 0 | } |
4011 | 0 | } |
4012 | | |
4013 | | ~CanvasBidiProcessor() |
4014 | 0 | { |
4015 | 0 | // notify front-end code if we encountered missing glyphs in any script |
4016 | 0 | if (mMissingFonts) { |
4017 | 0 | mMissingFonts->Flush(); |
4018 | 0 | } |
4019 | 0 | } |
4020 | | |
4021 | | typedef CanvasRenderingContext2D::ContextState ContextState; |
4022 | | |
4023 | | virtual void SetText(const char16_t* aText, int32_t aLength, nsBidiDirection aDirection) override |
4024 | 0 | { |
4025 | 0 | mFontgrp->UpdateUserFonts(); // ensure user font generation is current |
4026 | 0 | // adjust flags for current direction run |
4027 | 0 | gfx::ShapedTextFlags flags = mTextRunFlags; |
4028 | 0 | if (aDirection == NSBIDI_RTL) { |
4029 | 0 | flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; |
4030 | 0 | } else { |
4031 | 0 | flags &= ~gfx::ShapedTextFlags::TEXT_IS_RTL; |
4032 | 0 | } |
4033 | 0 | mTextRun = mFontgrp->MakeTextRun(aText, |
4034 | 0 | aLength, |
4035 | 0 | mDrawTarget, |
4036 | 0 | mAppUnitsPerDevPixel, |
4037 | 0 | flags, |
4038 | 0 | nsTextFrameUtils::Flags(), |
4039 | 0 | mMissingFonts); |
4040 | 0 | } |
4041 | | |
4042 | | virtual nscoord GetWidth() override |
4043 | 0 | { |
4044 | 0 | gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText( |
4045 | 0 | mDoMeasureBoundingBox ? gfxFont::TIGHT_INK_EXTENTS |
4046 | 0 | : gfxFont::LOOSE_INK_EXTENTS, mDrawTarget); |
4047 | 0 |
|
4048 | 0 | // this only measures the height; the total width is gotten from the |
4049 | 0 | // the return value of ProcessText. |
4050 | 0 | if (mDoMeasureBoundingBox) { |
4051 | 0 | textRunMetrics.mBoundingBox.Scale(1.0 / mAppUnitsPerDevPixel); |
4052 | 0 | mBoundingBox = mBoundingBox.Union(textRunMetrics.mBoundingBox); |
4053 | 0 | } |
4054 | 0 |
|
4055 | 0 | return NSToCoordRound(textRunMetrics.mAdvanceWidth); |
4056 | 0 | } |
4057 | | |
4058 | | already_AddRefed<gfxPattern> GetGradientFor(Style aStyle) |
4059 | 0 | { |
4060 | 0 | RefPtr<gfxPattern> pattern; |
4061 | 0 | CanvasGradient* gradient = mCtx->CurrentState().gradientStyles[aStyle]; |
4062 | 0 | CanvasGradient::Type type = gradient->GetType(); |
4063 | 0 |
|
4064 | 0 | switch (type) { |
4065 | 0 | case CanvasGradient::Type::RADIAL: { |
4066 | 0 | auto radial = static_cast<CanvasRadialGradient*>(gradient); |
4067 | 0 | pattern = new gfxPattern(radial->mCenter1.x, radial->mCenter1.y, |
4068 | 0 | radial->mRadius1, radial->mCenter2.x, |
4069 | 0 | radial->mCenter2.y, radial->mRadius2); |
4070 | 0 | break; |
4071 | 0 | } |
4072 | 0 | case CanvasGradient::Type::LINEAR: { |
4073 | 0 | auto linear = static_cast<CanvasLinearGradient*>(gradient); |
4074 | 0 | pattern = new gfxPattern(linear->mBegin.x, linear->mBegin.y, |
4075 | 0 | linear->mEnd.x, linear->mEnd.y); |
4076 | 0 | break; |
4077 | 0 | } |
4078 | 0 | default: |
4079 | 0 | MOZ_ASSERT(false, "Should be linear or radial gradient."); |
4080 | 0 | return nullptr; |
4081 | 0 | } |
4082 | 0 |
|
4083 | 0 | for (auto stop : gradient->mRawStops) { |
4084 | 0 | pattern->AddColorStop(stop.offset, stop.color); |
4085 | 0 | } |
4086 | 0 |
|
4087 | 0 | return pattern.forget(); |
4088 | 0 | } |
4089 | | |
4090 | | gfx::ExtendMode CvtCanvasRepeatToGfxRepeat( |
4091 | | CanvasPattern::RepeatMode aRepeatMode) |
4092 | | { |
4093 | | switch (aRepeatMode) { |
4094 | | case CanvasPattern::RepeatMode::REPEAT: |
4095 | | return gfx::ExtendMode::REPEAT; |
4096 | | case CanvasPattern::RepeatMode::REPEATX: |
4097 | | return gfx::ExtendMode::REPEAT_X; |
4098 | | case CanvasPattern::RepeatMode::REPEATY: |
4099 | | return gfx::ExtendMode::REPEAT_Y; |
4100 | | case CanvasPattern::RepeatMode::NOREPEAT: |
4101 | | return gfx::ExtendMode::CLAMP; |
4102 | | default: |
4103 | | return gfx::ExtendMode::CLAMP; |
4104 | | } |
4105 | | } |
4106 | | |
4107 | | already_AddRefed<gfxPattern> GetPatternFor(Style aStyle) |
4108 | 0 | { |
4109 | 0 | const CanvasPattern* pat = mCtx->CurrentState().patternStyles[aStyle]; |
4110 | 0 | RefPtr<gfxPattern> pattern = new gfxPattern(pat->mSurface, pat->mTransform); |
4111 | 0 | pattern->SetExtend(CvtCanvasRepeatToGfxRepeat(pat->mRepeat)); |
4112 | 0 | return pattern.forget(); |
4113 | 0 | } |
4114 | | |
4115 | | virtual void DrawText(nscoord aXOffset, nscoord aWidth) override |
4116 | 0 | { |
4117 | 0 | gfx::Point point = mPt; |
4118 | 0 | bool rtl = mTextRun->IsRightToLeft(); |
4119 | 0 | bool verticalRun = mTextRun->IsVertical(); |
4120 | 0 | RefPtr<gfxPattern> pattern; |
4121 | 0 |
|
4122 | 0 | float& inlineCoord = verticalRun ? point.y : point.x; |
4123 | 0 | inlineCoord += aXOffset; |
4124 | 0 |
|
4125 | 0 | // offset is given in terms of left side of string |
4126 | 0 | if (rtl) { |
4127 | 0 | // Bug 581092 - don't use rounded pixel width to advance to |
4128 | 0 | // right-hand end of run, because this will cause different |
4129 | 0 | // glyph positioning for LTR vs RTL drawing of the same |
4130 | 0 | // glyph string on OS X and DWrite where textrun widths may |
4131 | 0 | // involve fractional pixels. |
4132 | 0 | gfxTextRun::Metrics textRunMetrics = |
4133 | 0 | mTextRun->MeasureText(mDoMeasureBoundingBox ? |
4134 | 0 | gfxFont::TIGHT_INK_EXTENTS : |
4135 | 0 | gfxFont::LOOSE_INK_EXTENTS, |
4136 | 0 | mDrawTarget); |
4137 | 0 | inlineCoord += textRunMetrics.mAdvanceWidth; |
4138 | 0 | // old code was: |
4139 | 0 | // point.x += width * mAppUnitsPerDevPixel; |
4140 | 0 | // TODO: restore this if/when we move to fractional coords |
4141 | 0 | // throughout the text layout process |
4142 | 0 | } |
4143 | 0 |
|
4144 | 0 | mCtx->EnsureTarget(); |
4145 | 0 | if (!mCtx->IsTargetValid()) { |
4146 | 0 | return; |
4147 | 0 | } |
4148 | 0 | |
4149 | 0 | // Defer the tasks to gfxTextRun which will handle color/svg-in-ot fonts |
4150 | 0 | // appropriately. |
4151 | 0 | StrokeOptions strokeOpts; |
4152 | 0 | DrawOptions drawOpts; |
4153 | 0 | Style style = (mOp == CanvasRenderingContext2D::TextDrawOperation::FILL) |
4154 | 0 | ? Style::FILL |
4155 | 0 | : Style::STROKE; |
4156 | 0 |
|
4157 | 0 | AdjustedTarget target(mCtx); |
4158 | 0 | if (!target) { |
4159 | 0 | return; |
4160 | 0 | } |
4161 | 0 | |
4162 | 0 | RefPtr<gfxContext> thebes = |
4163 | 0 | gfxContext::CreatePreservingTransformOrNull(target); |
4164 | 0 | if (!thebes) { |
4165 | 0 | // If CreatePreservingTransformOrNull returns null, it will also have |
4166 | 0 | // issued a gfxCriticalNote already, so here we'll just bail out. |
4167 | 0 | return; |
4168 | 0 | } |
4169 | 0 | gfxTextRun::DrawParams params(thebes); |
4170 | 0 |
|
4171 | 0 | const ContextState* state = &mCtx->CurrentState(); |
4172 | 0 | if (state->StyleIsColor(style)) { // Color |
4173 | 0 | nscolor fontColor = state->colorStyles[style]; |
4174 | 0 | if (style == Style::FILL) { |
4175 | 0 | params.context->SetColor(Color::FromABGR(fontColor)); |
4176 | 0 | } else { |
4177 | 0 | params.textStrokeColor = fontColor; |
4178 | 0 | } |
4179 | 0 | } else { |
4180 | 0 | if (state->gradientStyles[style]) { // Gradient |
4181 | 0 | pattern = GetGradientFor(style); |
4182 | 0 | } else if (state->patternStyles[style]) { // Pattern |
4183 | 0 | pattern = GetPatternFor(style); |
4184 | 0 | } else { |
4185 | 0 | MOZ_ASSERT(false, "Should never reach here."); |
4186 | 0 | return; |
4187 | 0 | } |
4188 | 0 | MOZ_ASSERT(pattern, "No valid pattern."); |
4189 | 0 |
|
4190 | 0 | if (style == Style::FILL) { |
4191 | 0 | params.context->SetPattern(pattern); |
4192 | 0 | } else { |
4193 | 0 | params.textStrokePattern = pattern; |
4194 | 0 | } |
4195 | 0 | } |
4196 | 0 |
|
4197 | 0 | drawOpts.mAlpha = state->globalAlpha; |
4198 | 0 | drawOpts.mCompositionOp = mCtx->UsedOperation(); |
4199 | 0 | if (!mCtx->IsTargetValid()) { |
4200 | 0 | return; |
4201 | 0 | } |
4202 | 0 | state = &mCtx->CurrentState(); |
4203 | 0 | params.drawOpts = &drawOpts; |
4204 | 0 |
|
4205 | 0 | if (style == Style::STROKE) { |
4206 | 0 | strokeOpts.mLineWidth = state->lineWidth; |
4207 | 0 | strokeOpts.mLineJoin = state->lineJoin; |
4208 | 0 | strokeOpts.mLineCap = state->lineCap; |
4209 | 0 | strokeOpts.mMiterLimit = state->miterLimit; |
4210 | 0 | strokeOpts.mDashLength = state->dash.Length(); |
4211 | 0 | strokeOpts.mDashPattern = |
4212 | 0 | (strokeOpts.mDashLength > 0) ? state->dash.Elements() : 0; |
4213 | 0 | strokeOpts.mDashOffset = state->dashOffset; |
4214 | 0 |
|
4215 | 0 | params.drawMode = DrawMode::GLYPH_STROKE; |
4216 | 0 | params.strokeOpts = &strokeOpts; |
4217 | 0 | } |
4218 | 0 |
|
4219 | 0 | mTextRun->Draw(gfxTextRun::Range(mTextRun.get()), point, params); |
4220 | 0 | } |
4221 | | |
4222 | | // current text run |
4223 | | RefPtr<gfxTextRun> mTextRun; |
4224 | | |
4225 | | // pointer to a screen reference context used to measure text and such |
4226 | | RefPtr<DrawTarget> mDrawTarget; |
4227 | | |
4228 | | // Pointer to the draw target we should fill our text to |
4229 | | CanvasRenderingContext2D* mCtx; |
4230 | | |
4231 | | // position of the left side of the string, alphabetic baseline |
4232 | | gfx::Point mPt; |
4233 | | |
4234 | | // current font |
4235 | | gfxFontGroup* mFontgrp; |
4236 | | |
4237 | | // to record any unsupported characters found in the text, |
4238 | | // and notify front-end if it is interested |
4239 | | nsAutoPtr<gfxMissingFontRecorder> mMissingFonts; |
4240 | | |
4241 | | // dev pixel conversion factor |
4242 | | int32_t mAppUnitsPerDevPixel; |
4243 | | |
4244 | | // operation (fill or stroke) |
4245 | | CanvasRenderingContext2D::TextDrawOperation mOp; |
4246 | | |
4247 | | // union of bounding boxes of all runs, needed for shadows |
4248 | | gfxRect mBoundingBox; |
4249 | | |
4250 | | // flags to use when creating textrun, based on CSS style |
4251 | | gfx::ShapedTextFlags mTextRunFlags; |
4252 | | |
4253 | | // true iff the bounding box should be measured |
4254 | | bool mDoMeasureBoundingBox; |
4255 | | }; |
4256 | | |
4257 | | nsresult |
4258 | | CanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText, |
4259 | | float aX, |
4260 | | float aY, |
4261 | | const Optional<double>& aMaxWidth, |
4262 | | TextDrawOperation aOp, |
4263 | | float* aWidth) |
4264 | 0 | { |
4265 | 0 | nsresult rv; |
4266 | 0 |
|
4267 | 0 | if (!mCanvasElement && !mDocShell) { |
4268 | 0 | NS_WARNING("Canvas element must be non-null or a docshell must be provided"); |
4269 | 0 | return NS_ERROR_FAILURE; |
4270 | 0 | } |
4271 | 0 |
|
4272 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
4273 | 0 | if (!presShell) |
4274 | 0 | return NS_ERROR_FAILURE; |
4275 | 0 | |
4276 | 0 | nsIDocument* document = presShell->GetDocument(); |
4277 | 0 |
|
4278 | 0 | // replace all the whitespace characters with U+0020 SPACE |
4279 | 0 | nsAutoString textToDraw(aRawText); |
4280 | 0 | TextReplaceWhitespaceCharacters(textToDraw); |
4281 | 0 |
|
4282 | 0 | // According to spec, the API should return an empty array if maxWidth was provided |
4283 | 0 | // but is less than or equal to zero or equal to NaN. |
4284 | 0 | if (aMaxWidth.WasPassed() && (aMaxWidth.Value() <= 0 || IsNaN(aMaxWidth.Value()))) { |
4285 | 0 | textToDraw.Truncate(); |
4286 | 0 | } |
4287 | 0 |
|
4288 | 0 | // for now, default to ltr if not in doc |
4289 | 0 | bool isRTL = false; |
4290 | 0 |
|
4291 | 0 | RefPtr<ComputedStyle> canvasStyle; |
4292 | 0 | if (mCanvasElement && mCanvasElement->IsInComposedDoc()) { |
4293 | 0 | // try to find the closest context |
4294 | 0 | canvasStyle = |
4295 | 0 | nsComputedDOMStyle::GetComputedStyle(mCanvasElement, nullptr); |
4296 | 0 | if (!canvasStyle) { |
4297 | 0 | return NS_ERROR_FAILURE; |
4298 | 0 | } |
4299 | 0 | |
4300 | 0 | isRTL = canvasStyle->StyleVisibility()->mDirection == |
4301 | 0 | NS_STYLE_DIRECTION_RTL; |
4302 | 0 | } else { |
4303 | 0 | isRTL = GET_BIDI_OPTION_DIRECTION(document->GetBidiOptions()) == IBMBIDI_TEXTDIRECTION_RTL; |
4304 | 0 | } |
4305 | 0 |
|
4306 | 0 | // This is only needed to know if we can know the drawing bounding box easily. |
4307 | 0 | const bool doCalculateBounds = NeedToCalculateBounds(); |
4308 | 0 | if (presShell->IsDestroying()) { |
4309 | 0 | return NS_ERROR_FAILURE; |
4310 | 0 | } |
4311 | 0 | |
4312 | 0 | gfxFontGroup* currentFontStyle = GetCurrentFontStyle(); |
4313 | 0 | if (!currentFontStyle) { |
4314 | 0 | return NS_ERROR_FAILURE; |
4315 | 0 | } |
4316 | 0 | |
4317 | 0 | MOZ_ASSERT(!presShell->IsDestroying(), |
4318 | 0 | "GetCurrentFontStyle() should have returned null if the presshell is being destroyed"); |
4319 | 0 |
|
4320 | 0 | nsPresContext* presContext = presShell->GetPresContext(); |
4321 | 0 |
|
4322 | 0 | // ensure user font set is up to date |
4323 | 0 | currentFontStyle->SetUserFontSet(presContext->GetUserFontSet()); |
4324 | 0 |
|
4325 | 0 | if (currentFontStyle->GetStyle()->size == 0.0F) { |
4326 | 0 | if (aWidth) { |
4327 | 0 | *aWidth = 0; |
4328 | 0 | } |
4329 | 0 | return NS_OK; |
4330 | 0 | } |
4331 | 0 |
|
4332 | 0 | if (!IsFinite(aX) || !IsFinite(aY)) { |
4333 | 0 | return NS_OK; |
4334 | 0 | } |
4335 | 0 | |
4336 | 0 | CanvasBidiProcessor processor; |
4337 | 0 |
|
4338 | 0 | // If we don't have a ComputedStyle, we can't set up vertical-text flags |
4339 | 0 | // (for now, at least; perhaps we need new Canvas API to control this). |
4340 | 0 | processor.mTextRunFlags = canvasStyle |
4341 | 0 | ? nsLayoutUtils::GetTextRunFlagsForStyle(canvasStyle, |
4342 | 0 | presContext, |
4343 | 0 | canvasStyle->StyleFont(), |
4344 | 0 | canvasStyle->StyleText(), |
4345 | 0 | 0) |
4346 | 0 | : gfx::ShapedTextFlags(); |
4347 | 0 |
|
4348 | 0 | GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, nullptr); |
4349 | 0 | processor.mPt = gfx::Point(aX, aY); |
4350 | 0 | processor.mDrawTarget = |
4351 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); |
4352 | 0 |
|
4353 | 0 | // If we don't have a target then we don't have a transform. A target won't |
4354 | 0 | // be needed in the case where we're measuring the text size. This allows |
4355 | 0 | // to avoid creating a target if it's only being used to measure text sizes. |
4356 | 0 | if (mTarget) { |
4357 | 0 | processor.mDrawTarget->SetTransform(mTarget->GetTransform()); |
4358 | 0 | } |
4359 | 0 | processor.mCtx = this; |
4360 | 0 | processor.mOp = aOp; |
4361 | 0 | processor.mBoundingBox = gfxRect(0, 0, 0, 0); |
4362 | 0 | processor.mDoMeasureBoundingBox = doCalculateBounds || !mIsEntireFrameInvalid; |
4363 | 0 | processor.mFontgrp = currentFontStyle; |
4364 | 0 |
|
4365 | 0 | nscoord totalWidthCoord; |
4366 | 0 |
|
4367 | 0 | // calls bidi algo twice since it needs the full text width and the |
4368 | 0 | // bounding boxes before rendering anything |
4369 | 0 | rv = nsBidiPresUtils::ProcessText(textToDraw.get(), |
4370 | 0 | textToDraw.Length(), |
4371 | 0 | isRTL ? NSBIDI_RTL : NSBIDI_LTR, |
4372 | 0 | presShell->GetPresContext(), |
4373 | 0 | processor, |
4374 | 0 | nsBidiPresUtils::MODE_MEASURE, |
4375 | 0 | nullptr, |
4376 | 0 | 0, |
4377 | 0 | &totalWidthCoord, |
4378 | 0 | &mBidiEngine); |
4379 | 0 | if (NS_FAILED(rv)) { |
4380 | 0 | return rv; |
4381 | 0 | } |
4382 | 0 | |
4383 | 0 | float totalWidth = float(totalWidthCoord) / processor.mAppUnitsPerDevPixel; |
4384 | 0 | if (aWidth) { |
4385 | 0 | *aWidth = totalWidth; |
4386 | 0 | } |
4387 | 0 |
|
4388 | 0 | // if only measuring, don't need to do any more work |
4389 | 0 | if (aOp==TextDrawOperation::MEASURE) { |
4390 | 0 | return NS_OK; |
4391 | 0 | } |
4392 | 0 | |
4393 | 0 | // offset pt.x based on text align |
4394 | 0 | gfxFloat anchorX; |
4395 | 0 |
|
4396 | 0 | const ContextState& state = CurrentState(); |
4397 | 0 | if (state.textAlign == TextAlign::CENTER) { |
4398 | 0 | anchorX = .5; |
4399 | 0 | } else if (state.textAlign == TextAlign::LEFT || |
4400 | 0 | (!isRTL && state.textAlign == TextAlign::START) || |
4401 | 0 | (isRTL && state.textAlign == TextAlign::END)) { |
4402 | 0 | anchorX = 0; |
4403 | 0 | } else { |
4404 | 0 | anchorX = 1; |
4405 | 0 | } |
4406 | 0 |
|
4407 | 0 | processor.mPt.x -= anchorX * totalWidth; |
4408 | 0 |
|
4409 | 0 | // offset pt.y (or pt.x, for vertical text) based on text baseline |
4410 | 0 | processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current |
4411 | 0 | const gfxFont::Metrics& fontMetrics = |
4412 | 0 | processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal); |
4413 | 0 |
|
4414 | 0 | gfxFloat baselineAnchor; |
4415 | 0 |
|
4416 | 0 | switch (state.textBaseline) |
4417 | 0 | { |
4418 | 0 | case TextBaseline::HANGING: |
4419 | 0 | // fall through; best we can do with the information available |
4420 | 0 | case TextBaseline::TOP: |
4421 | 0 | baselineAnchor = fontMetrics.emAscent; |
4422 | 0 | break; |
4423 | 0 | case TextBaseline::MIDDLE: |
4424 | 0 | baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; |
4425 | 0 | break; |
4426 | 0 | case TextBaseline::IDEOGRAPHIC: |
4427 | 0 | // fall through; best we can do with the information available |
4428 | 0 | case TextBaseline::ALPHABETIC: |
4429 | 0 | baselineAnchor = 0; |
4430 | 0 | break; |
4431 | 0 | case TextBaseline::BOTTOM: |
4432 | 0 | baselineAnchor = -fontMetrics.emDescent; |
4433 | 0 | break; |
4434 | 0 | default: |
4435 | 0 | MOZ_CRASH("GFX: unexpected TextBaseline"); |
4436 | 0 | } |
4437 | 0 |
|
4438 | 0 | // We can't query the textRun directly, as it may not have been created yet; |
4439 | 0 | // so instead we check the flags that will be used to initialize it. |
4440 | 0 | gfx::ShapedTextFlags runOrientation = |
4441 | 0 | (processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK); |
4442 | 0 | if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) { |
4443 | 0 | if (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED || |
4444 | 0 | runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) { |
4445 | 0 | // Adjust to account for mTextRun being shaped using center baseline |
4446 | 0 | // rather than alphabetic. |
4447 | 0 | baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f; |
4448 | 0 | } |
4449 | 0 | processor.mPt.x -= baselineAnchor; |
4450 | 0 | } else { |
4451 | 0 | processor.mPt.y += baselineAnchor; |
4452 | 0 | } |
4453 | 0 |
|
4454 | 0 | // correct bounding box to get it to be the correct size/position |
4455 | 0 | processor.mBoundingBox.width = totalWidth; |
4456 | 0 | processor.mBoundingBox.MoveBy(gfxPoint(processor.mPt.x, processor.mPt.y)); |
4457 | 0 |
|
4458 | 0 | processor.mPt.x *= processor.mAppUnitsPerDevPixel; |
4459 | 0 | processor.mPt.y *= processor.mAppUnitsPerDevPixel; |
4460 | 0 |
|
4461 | 0 | EnsureTarget(); |
4462 | 0 | if (!IsTargetValid()) { |
4463 | 0 | return NS_ERROR_FAILURE; |
4464 | 0 | } |
4465 | 0 | |
4466 | 0 | Matrix oldTransform = mTarget->GetTransform(); |
4467 | 0 | // if text is over aMaxWidth, then scale the text horizontally such that its |
4468 | 0 | // width is precisely aMaxWidth |
4469 | 0 | if (aMaxWidth.WasPassed() && aMaxWidth.Value() > 0 && |
4470 | 0 | totalWidth > aMaxWidth.Value()) { |
4471 | 0 | Matrix newTransform = oldTransform; |
4472 | 0 |
|
4473 | 0 | // Translate so that the anchor point is at 0,0, then scale and then |
4474 | 0 | // translate back. |
4475 | 0 | newTransform.PreTranslate(aX, 0); |
4476 | 0 | newTransform.PreScale(aMaxWidth.Value() / totalWidth, 1); |
4477 | 0 | newTransform.PreTranslate(-aX, 0); |
4478 | 0 | /* we do this to avoid an ICE in the android compiler */ |
4479 | 0 | Matrix androidCompilerBug = newTransform; |
4480 | 0 | mTarget->SetTransform(androidCompilerBug); |
4481 | 0 | } |
4482 | 0 |
|
4483 | 0 | // save the previous bounding box |
4484 | 0 | gfxRect boundingBox = processor.mBoundingBox; |
4485 | 0 |
|
4486 | 0 | // don't ever need to measure the bounding box twice |
4487 | 0 | processor.mDoMeasureBoundingBox = false; |
4488 | 0 |
|
4489 | 0 | rv = nsBidiPresUtils::ProcessText(textToDraw.get(), |
4490 | 0 | textToDraw.Length(), |
4491 | 0 | isRTL ? NSBIDI_RTL : NSBIDI_LTR, |
4492 | 0 | presShell->GetPresContext(), |
4493 | 0 | processor, |
4494 | 0 | nsBidiPresUtils::MODE_DRAW, |
4495 | 0 | nullptr, |
4496 | 0 | 0, |
4497 | 0 | nullptr, |
4498 | 0 | &mBidiEngine); |
4499 | 0 |
|
4500 | 0 |
|
4501 | 0 | mTarget->SetTransform(oldTransform); |
4502 | 0 |
|
4503 | 0 | if (aOp == CanvasRenderingContext2D::TextDrawOperation::FILL && |
4504 | 0 | !doCalculateBounds) { |
4505 | 0 | RedrawUser(boundingBox); |
4506 | 0 | return NS_OK; |
4507 | 0 | } |
4508 | 0 | |
4509 | 0 | Redraw(); |
4510 | 0 | return NS_OK; |
4511 | 0 | } |
4512 | | |
4513 | | gfxFontGroup* |
4514 | | CanvasRenderingContext2D::GetCurrentFontStyle() |
4515 | 0 | { |
4516 | 0 | // use lazy initilization for the font group since it's rather expensive |
4517 | 0 | if (!CurrentState().fontGroup) { |
4518 | 0 | ErrorResult err; |
4519 | 0 | NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif"); |
4520 | 0 | static float kDefaultFontSize = 10.0; |
4521 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
4522 | 0 | bool fontUpdated = SetFontInternal(kDefaultFontStyle, err); |
4523 | 0 | if (err.Failed() || !fontUpdated) { |
4524 | 0 | err.SuppressException(); |
4525 | 0 | gfxFontStyle style; |
4526 | 0 | style.size = kDefaultFontSize; |
4527 | 0 | gfxTextPerfMetrics* tp = nullptr; |
4528 | 0 | if (presShell && !presShell->IsDestroying()) { |
4529 | 0 | tp = presShell->GetPresContext()->GetTextPerfMetrics(); |
4530 | 0 | } |
4531 | 0 | int32_t perDevPixel, perCSSPixel; |
4532 | 0 | GetAppUnitsValues(&perDevPixel, &perCSSPixel); |
4533 | 0 | gfxFloat devToCssSize = gfxFloat(perDevPixel) / gfxFloat(perCSSPixel); |
4534 | 0 | CurrentState().fontGroup = |
4535 | 0 | gfxPlatform::GetPlatform()->CreateFontGroup(FontFamilyList(eFamily_sans_serif), |
4536 | 0 | &style, tp, |
4537 | 0 | nullptr, devToCssSize); |
4538 | 0 | if (CurrentState().fontGroup) { |
4539 | 0 | CurrentState().font = kDefaultFontStyle; |
4540 | 0 | } else { |
4541 | 0 | NS_ERROR("Default canvas font is invalid"); |
4542 | 0 | } |
4543 | 0 | } |
4544 | 0 | } |
4545 | 0 |
|
4546 | 0 | return CurrentState().fontGroup; |
4547 | 0 | } |
4548 | | |
4549 | | // |
4550 | | // line caps/joins |
4551 | | // |
4552 | | |
4553 | | void |
4554 | | CanvasRenderingContext2D::SetLineCap(const nsAString& aLinecapStyle) |
4555 | 0 | { |
4556 | 0 | CapStyle cap; |
4557 | 0 |
|
4558 | 0 | if (aLinecapStyle.EqualsLiteral("butt")) { |
4559 | 0 | cap = CapStyle::BUTT; |
4560 | 0 | } else if (aLinecapStyle.EqualsLiteral("round")) { |
4561 | 0 | cap = CapStyle::ROUND; |
4562 | 0 | } else if (aLinecapStyle.EqualsLiteral("square")) { |
4563 | 0 | cap = CapStyle::SQUARE; |
4564 | 0 | } else { |
4565 | 0 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
4566 | 0 | return; |
4567 | 0 | } |
4568 | 0 | |
4569 | 0 | CurrentState().lineCap = cap; |
4570 | 0 | } |
4571 | | |
4572 | | void |
4573 | | CanvasRenderingContext2D::GetLineCap(nsAString& aLinecapStyle) |
4574 | | { |
4575 | | switch (CurrentState().lineCap) { |
4576 | | case CapStyle::BUTT: |
4577 | | aLinecapStyle.AssignLiteral("butt"); |
4578 | | break; |
4579 | | case CapStyle::ROUND: |
4580 | | aLinecapStyle.AssignLiteral("round"); |
4581 | | break; |
4582 | | case CapStyle::SQUARE: |
4583 | | aLinecapStyle.AssignLiteral("square"); |
4584 | | break; |
4585 | | } |
4586 | | } |
4587 | | |
4588 | | void |
4589 | | CanvasRenderingContext2D::SetLineJoin(const nsAString& aLinejoinStyle) |
4590 | 0 | { |
4591 | 0 | JoinStyle j; |
4592 | 0 |
|
4593 | 0 | if (aLinejoinStyle.EqualsLiteral("round")) { |
4594 | 0 | j = JoinStyle::ROUND; |
4595 | 0 | } else if (aLinejoinStyle.EqualsLiteral("bevel")) { |
4596 | 0 | j = JoinStyle::BEVEL; |
4597 | 0 | } else if (aLinejoinStyle.EqualsLiteral("miter")) { |
4598 | 0 | j = JoinStyle::MITER_OR_BEVEL; |
4599 | 0 | } else { |
4600 | 0 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
4601 | 0 | return; |
4602 | 0 | } |
4603 | 0 | |
4604 | 0 | CurrentState().lineJoin = j; |
4605 | 0 | } |
4606 | | |
4607 | | void |
4608 | | CanvasRenderingContext2D::GetLineJoin(nsAString& aLinejoinStyle, ErrorResult& aError) |
4609 | | { |
4610 | | switch (CurrentState().lineJoin) { |
4611 | | case JoinStyle::ROUND: |
4612 | | aLinejoinStyle.AssignLiteral("round"); |
4613 | | break; |
4614 | | case JoinStyle::BEVEL: |
4615 | | aLinejoinStyle.AssignLiteral("bevel"); |
4616 | | break; |
4617 | | case JoinStyle::MITER_OR_BEVEL: |
4618 | | aLinejoinStyle.AssignLiteral("miter"); |
4619 | | break; |
4620 | | default: |
4621 | | aError.Throw(NS_ERROR_FAILURE); |
4622 | | } |
4623 | | } |
4624 | | |
4625 | | void |
4626 | | CanvasRenderingContext2D::SetLineDash(const Sequence<double>& aSegments, |
4627 | | ErrorResult& aRv) |
4628 | 0 | { |
4629 | 0 | nsTArray<mozilla::gfx::Float> dash; |
4630 | 0 |
|
4631 | 0 | for (uint32_t x = 0; x < aSegments.Length(); x++) { |
4632 | 0 | if (aSegments[x] < 0.0) { |
4633 | 0 | // Pattern elements must be finite "numbers" >= 0, with "finite" |
4634 | 0 | // taken care of by WebIDL |
4635 | 0 | return; |
4636 | 0 | } |
4637 | 0 | |
4638 | 0 | if (!dash.AppendElement(aSegments[x], fallible)) { |
4639 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
4640 | 0 | return; |
4641 | 0 | } |
4642 | 0 | } |
4643 | 0 | if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again |
4644 | 0 | for (uint32_t x = 0; x < aSegments.Length(); x++) { |
4645 | 0 | if (!dash.AppendElement(aSegments[x], fallible)) { |
4646 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
4647 | 0 | return; |
4648 | 0 | } |
4649 | 0 | } |
4650 | 0 | } |
4651 | 0 |
|
4652 | 0 | CurrentState().dash = std::move(dash); |
4653 | 0 | } |
4654 | | |
4655 | | void |
4656 | 0 | CanvasRenderingContext2D::GetLineDash(nsTArray<double>& aSegments) const { |
4657 | 0 | const nsTArray<mozilla::gfx::Float>& dash = CurrentState().dash; |
4658 | 0 | aSegments.Clear(); |
4659 | 0 |
|
4660 | 0 | for (uint32_t x = 0; x < dash.Length(); x++) { |
4661 | 0 | aSegments.AppendElement(dash[x]); |
4662 | 0 | } |
4663 | 0 | } |
4664 | | |
4665 | | void |
4666 | 0 | CanvasRenderingContext2D::SetLineDashOffset(double aOffset) { |
4667 | 0 | CurrentState().dashOffset = aOffset; |
4668 | 0 | } |
4669 | | |
4670 | | double |
4671 | 0 | CanvasRenderingContext2D::LineDashOffset() const { |
4672 | 0 | return CurrentState().dashOffset; |
4673 | 0 | } |
4674 | | |
4675 | | bool |
4676 | | CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, double aX, double aY, |
4677 | | const CanvasWindingRule& aWinding, |
4678 | | nsIPrincipal& aSubjectPrincipal) |
4679 | 0 | { |
4680 | 0 | if (!FloatValidate(aX, aY)) { |
4681 | 0 | return false; |
4682 | 0 | } |
4683 | 0 | |
4684 | 0 | // Check for site-specific permission and return false if no permission. |
4685 | 0 | if (mCanvasElement) { |
4686 | 0 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
4687 | 0 | if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) { |
4688 | 0 | return false; |
4689 | 0 | } |
4690 | 0 | } |
4691 | 0 | |
4692 | 0 | EnsureUserSpacePath(aWinding); |
4693 | 0 | if (!mPath) { |
4694 | 0 | return false; |
4695 | 0 | } |
4696 | 0 | |
4697 | 0 | if (mPathTransformWillUpdate) { |
4698 | 0 | return mPath->ContainsPoint(Point(aX, aY), mPathToDS); |
4699 | 0 | } |
4700 | 0 | |
4701 | 0 | return mPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform()); |
4702 | 0 | } |
4703 | | |
4704 | | bool |
4705 | | CanvasRenderingContext2D::IsPointInPath(JSContext* aCx, const CanvasPath& aPath, |
4706 | | double aX, double aY, |
4707 | | const CanvasWindingRule& aWinding, |
4708 | | nsIPrincipal&) |
4709 | 0 | { |
4710 | 0 | if (!FloatValidate(aX, aY)) { |
4711 | 0 | return false; |
4712 | 0 | } |
4713 | 0 | |
4714 | 0 | EnsureTarget(); |
4715 | 0 | if (!IsTargetValid()) { |
4716 | 0 | return false; |
4717 | 0 | } |
4718 | 0 | |
4719 | 0 | RefPtr<gfx::Path> tempPath = aPath.GetPath(aWinding, mTarget); |
4720 | 0 |
|
4721 | 0 | return tempPath->ContainsPoint(Point(aX, aY), mTarget->GetTransform()); |
4722 | 0 | } |
4723 | | |
4724 | | bool |
4725 | | CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, double aX, double aY, |
4726 | | nsIPrincipal& aSubjectPrincipal) |
4727 | 0 | { |
4728 | 0 | if (!FloatValidate(aX, aY)) { |
4729 | 0 | return false; |
4730 | 0 | } |
4731 | 0 | |
4732 | 0 | // Check for site-specific permission and return false if no permission. |
4733 | 0 | if (mCanvasElement) { |
4734 | 0 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
4735 | 0 | if (!CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal)) { |
4736 | 0 | return false; |
4737 | 0 | } |
4738 | 0 | } |
4739 | 0 | |
4740 | 0 | EnsureUserSpacePath(); |
4741 | 0 | if (!mPath) { |
4742 | 0 | return false; |
4743 | 0 | } |
4744 | 0 | |
4745 | 0 | const ContextState &state = CurrentState(); |
4746 | 0 |
|
4747 | 0 | StrokeOptions strokeOptions(state.lineWidth, |
4748 | 0 | state.lineJoin, |
4749 | 0 | state.lineCap, |
4750 | 0 | state.miterLimit, |
4751 | 0 | state.dash.Length(), |
4752 | 0 | state.dash.Elements(), |
4753 | 0 | state.dashOffset); |
4754 | 0 |
|
4755 | 0 | if (mPathTransformWillUpdate) { |
4756 | 0 | return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mPathToDS); |
4757 | 0 | } |
4758 | 0 | return mPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform()); |
4759 | 0 | } |
4760 | | |
4761 | | bool |
4762 | | CanvasRenderingContext2D::IsPointInStroke(JSContext* aCx, const CanvasPath& aPath, |
4763 | | double aX, double aY, nsIPrincipal&) |
4764 | 0 | { |
4765 | 0 | if (!FloatValidate(aX, aY)) { |
4766 | 0 | return false; |
4767 | 0 | } |
4768 | 0 | |
4769 | 0 | EnsureTarget(); |
4770 | 0 | if (!IsTargetValid()) { |
4771 | 0 | return false; |
4772 | 0 | } |
4773 | 0 | |
4774 | 0 | RefPtr<gfx::Path> tempPath = aPath.GetPath(CanvasWindingRule::Nonzero, mTarget); |
4775 | 0 |
|
4776 | 0 | const ContextState &state = CurrentState(); |
4777 | 0 |
|
4778 | 0 | StrokeOptions strokeOptions(state.lineWidth, |
4779 | 0 | state.lineJoin, |
4780 | 0 | state.lineCap, |
4781 | 0 | state.miterLimit, |
4782 | 0 | state.dash.Length(), |
4783 | 0 | state.dash.Elements(), |
4784 | 0 | state.dashOffset); |
4785 | 0 |
|
4786 | 0 | return tempPath->StrokeContainsPoint(strokeOptions, Point(aX, aY), mTarget->GetTransform()); |
4787 | 0 | } |
4788 | | |
4789 | | // Returns a surface that contains only the part needed to draw aSourceRect. |
4790 | | // On entry, aSourceRect is relative to aSurface, and on return aSourceRect is |
4791 | | // relative to the returned surface. |
4792 | | static already_AddRefed<SourceSurface> |
4793 | | ExtractSubrect(SourceSurface* aSurface, gfx::Rect* aSourceRect, DrawTarget* aTargetDT) |
4794 | 0 | { |
4795 | 0 | gfx::Rect roundedOutSourceRect = *aSourceRect; |
4796 | 0 | roundedOutSourceRect.RoundOut(); |
4797 | 0 | gfx::IntRect roundedOutSourceRectInt; |
4798 | 0 | if (!roundedOutSourceRect.ToIntRect(&roundedOutSourceRectInt)) { |
4799 | 0 | RefPtr<SourceSurface> surface(aSurface); |
4800 | 0 | return surface.forget(); |
4801 | 0 | } |
4802 | 0 | |
4803 | 0 | RefPtr<DrawTarget> subrectDT = |
4804 | 0 | aTargetDT->CreateSimilarDrawTarget(roundedOutSourceRectInt.Size(), SurfaceFormat::B8G8R8A8); |
4805 | 0 |
|
4806 | 0 | if (!subrectDT) { |
4807 | 0 | RefPtr<SourceSurface> surface(aSurface); |
4808 | 0 | return surface.forget(); |
4809 | 0 | } |
4810 | 0 | |
4811 | 0 | *aSourceRect -= roundedOutSourceRect.TopLeft(); |
4812 | 0 |
|
4813 | 0 | subrectDT->CopySurface(aSurface, roundedOutSourceRectInt, IntPoint()); |
4814 | 0 | return subrectDT->Snapshot(); |
4815 | 0 | } |
4816 | | |
4817 | | // |
4818 | | // image |
4819 | | // |
4820 | | |
4821 | | static void |
4822 | | ClipImageDimension(double& aSourceCoord, double& aSourceSize, int32_t aImageSize, |
4823 | | double& aDestCoord, double& aDestSize) |
4824 | 0 | { |
4825 | 0 | double scale = aDestSize / aSourceSize; |
4826 | 0 | if (aSourceCoord < 0.0) { |
4827 | 0 | double destEnd = aDestCoord + aDestSize; |
4828 | 0 | aDestCoord -= aSourceCoord * scale; |
4829 | 0 | aDestSize = destEnd - aDestCoord; |
4830 | 0 | aSourceSize += aSourceCoord; |
4831 | 0 | aSourceCoord = 0.0; |
4832 | 0 | } |
4833 | 0 | double delta = aImageSize - (aSourceCoord + aSourceSize); |
4834 | 0 | if (delta < 0.0) { |
4835 | 0 | aDestSize += delta * scale; |
4836 | 0 | aSourceSize = aImageSize - aSourceCoord; |
4837 | 0 | } |
4838 | 0 | } |
4839 | | |
4840 | | // Acts like nsLayoutUtils::SurfaceFromElement, but it'll attempt |
4841 | | // to pull a SourceSurface from our cache. This allows us to avoid |
4842 | | // reoptimizing surfaces if content and canvas backends are different. |
4843 | | nsLayoutUtils::SurfaceFromElementResult |
4844 | | CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement) |
4845 | 0 | { |
4846 | 0 | nsLayoutUtils::SurfaceFromElementResult res; |
4847 | 0 | nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement); |
4848 | 0 | if (!imageLoader) { |
4849 | 0 | return res; |
4850 | 0 | } |
4851 | 0 | |
4852 | 0 | nsCOMPtr<imgIRequest> imgRequest; |
4853 | 0 | imageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, |
4854 | 0 | getter_AddRefs(imgRequest)); |
4855 | 0 | if (!imgRequest) { |
4856 | 0 | return res; |
4857 | 0 | } |
4858 | 0 | |
4859 | 0 | uint32_t status = 0; |
4860 | 0 | if (NS_FAILED(imgRequest->GetImageStatus(&status)) || |
4861 | 0 | !(status & imgIRequest::STATUS_LOAD_COMPLETE)) { |
4862 | 0 | return res; |
4863 | 0 | } |
4864 | 0 | |
4865 | 0 | nsCOMPtr<nsIPrincipal> principal; |
4866 | 0 | if (NS_FAILED(imgRequest->GetImagePrincipal(getter_AddRefs(principal))) || |
4867 | 0 | !principal) { |
4868 | 0 | return res; |
4869 | 0 | } |
4870 | 0 | |
4871 | 0 | res.mSourceSurface = |
4872 | 0 | CanvasImageCache::LookupAllCanvas(aElement, mIsSkiaGL); |
4873 | 0 | if (!res.mSourceSurface) { |
4874 | 0 | return res; |
4875 | 0 | } |
4876 | 0 | |
4877 | 0 | int32_t corsmode = imgIRequest::CORS_NONE; |
4878 | 0 | if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) { |
4879 | 0 | res.mCORSUsed = corsmode != imgIRequest::CORS_NONE; |
4880 | 0 | } |
4881 | 0 |
|
4882 | 0 | res.mSize = res.mSourceSurface->GetSize(); |
4883 | 0 | res.mPrincipal = principal.forget(); |
4884 | 0 | res.mIsWriteOnly = false; |
4885 | 0 | res.mImageRequest = imgRequest.forget(); |
4886 | 0 |
|
4887 | 0 | return res; |
4888 | 0 | } |
4889 | | |
4890 | | // drawImage(in HTMLImageElement image, in float dx, in float dy); |
4891 | | // -- render image from 0,0 at dx,dy top-left coords |
4892 | | // drawImage(in HTMLImageElement image, in float dx, in float dy, in float dw, in float dh); |
4893 | | // -- render image from 0,0 at dx,dy top-left coords clipping it to dw,dh |
4894 | | // drawImage(in HTMLImageElement image, in float sx, in float sy, in float sw, in float sh, in float dx, in float dy, in float dw, in float dh); |
4895 | | // -- render the region defined by (sx,sy,sw,wh) in image-local space into the region (dx,dy,dw,dh) on the canvas |
4896 | | |
4897 | | // If only dx and dy are passed in then optional_argc should be 0. If only |
4898 | | // dx, dy, dw and dh are passed in then optional_argc should be 2. The only |
4899 | | // other valid value for optional_argc is 6 if sx, sy, sw, sh, dx, dy, dw and dh |
4900 | | // are all passed in. |
4901 | | |
4902 | | void |
4903 | | CanvasRenderingContext2D::DrawImage(const CanvasImageSource& aImage, |
4904 | | double aSx, double aSy, double aSw, |
4905 | | double aSh, double aDx, double aDy, |
4906 | | double aDw, double aDh, |
4907 | | uint8_t aOptional_argc, |
4908 | | ErrorResult& aError) |
4909 | 0 | { |
4910 | 0 | if (mDrawObserver) { |
4911 | 0 | mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage); |
4912 | 0 | } |
4913 | 0 |
|
4914 | 0 | MOZ_ASSERT(aOptional_argc == 0 || aOptional_argc == 2 || aOptional_argc == 6); |
4915 | 0 |
|
4916 | 0 | if (!ValidateRect(aDx, aDy, aDw, aDh, true)) { |
4917 | 0 | return; |
4918 | 0 | } |
4919 | 0 | if (aOptional_argc == 6) { |
4920 | 0 | if (!ValidateRect(aSx, aSy, aSw, aSh, true)) { |
4921 | 0 | return; |
4922 | 0 | } |
4923 | 0 | } |
4924 | 0 | |
4925 | 0 | RefPtr<SourceSurface> srcSurf; |
4926 | 0 | gfx::IntSize imgSize; |
4927 | 0 |
|
4928 | 0 | Element* element = nullptr; |
4929 | 0 |
|
4930 | 0 | EnsureTarget(); |
4931 | 0 | if (!IsTargetValid()) { |
4932 | 0 | return; |
4933 | 0 | } |
4934 | 0 | |
4935 | 0 | if (aImage.IsHTMLCanvasElement()) { |
4936 | 0 | HTMLCanvasElement* canvas = &aImage.GetAsHTMLCanvasElement(); |
4937 | 0 | element = canvas; |
4938 | 0 | nsIntSize size = canvas->GetSize(); |
4939 | 0 | if (size.width == 0 || size.height == 0) { |
4940 | 0 | aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
4941 | 0 | return; |
4942 | 0 | } |
4943 | 0 | } else if (aImage.IsImageBitmap()) { |
4944 | 0 | ImageBitmap& imageBitmap = aImage.GetAsImageBitmap(); |
4945 | 0 | srcSurf = imageBitmap.PrepareForDrawTarget(mTarget); |
4946 | 0 |
|
4947 | 0 | if (!srcSurf) { |
4948 | 0 | return; |
4949 | 0 | } |
4950 | 0 | |
4951 | 0 | imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height()); |
4952 | 0 | } |
4953 | 0 | else { |
4954 | 0 | if (aImage.IsHTMLImageElement()) { |
4955 | 0 | HTMLImageElement* img = &aImage.GetAsHTMLImageElement(); |
4956 | 0 | element = img; |
4957 | 0 | } else if (aImage.IsSVGImageElement()) { |
4958 | 0 | SVGImageElement* img = &aImage.GetAsSVGImageElement(); |
4959 | 0 | element = img; |
4960 | 0 | } else { |
4961 | 0 | HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement(); |
4962 | 0 | video->MarkAsContentSource(mozilla::dom::HTMLVideoElement::CallerAPI::DRAW_IMAGE); |
4963 | 0 | element = video; |
4964 | 0 | } |
4965 | 0 |
|
4966 | 0 | srcSurf = |
4967 | 0 | CanvasImageCache::LookupCanvas(element, mCanvasElement, &imgSize, mIsSkiaGL); |
4968 | 0 | } |
4969 | 0 |
|
4970 | 0 | nsLayoutUtils::DirectDrawInfo drawInfo; |
4971 | 0 |
|
4972 | 0 | if (!srcSurf) { |
4973 | 0 | // The canvas spec says that drawImage should draw the first frame |
4974 | 0 | // of animated images. We also don't want to rasterize vector images. |
4975 | 0 | uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME_IF_IMAGE | |
4976 | 0 | nsLayoutUtils::SFE_NO_RASTERIZING_VECTORS; |
4977 | 0 |
|
4978 | 0 | nsLayoutUtils::SurfaceFromElementResult res = |
4979 | 0 | CanvasRenderingContext2D::CachedSurfaceFromElement(element); |
4980 | 0 |
|
4981 | 0 | if (!res.mSourceSurface) { |
4982 | 0 | res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget); |
4983 | 0 | } |
4984 | 0 |
|
4985 | 0 | if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) { |
4986 | 0 | // The spec says to silently do nothing in the following cases: |
4987 | 0 | // - The element is still loading. |
4988 | 0 | // - The image is bad, but it's not in the broken state (i.e., we could |
4989 | 0 | // decode the headers and get the size). |
4990 | 0 | if (!res.mIsStillLoading && !res.mHasSize) { |
4991 | 0 | aError.Throw(NS_ERROR_NOT_AVAILABLE); |
4992 | 0 | } |
4993 | 0 | return; |
4994 | 0 | } |
4995 | 0 |
|
4996 | 0 | imgSize = res.mSize; |
4997 | 0 |
|
4998 | 0 | // Scale sw/sh based on aspect ratio |
4999 | 0 | if (aImage.IsHTMLVideoElement()) { |
5000 | 0 | HTMLVideoElement* video = &aImage.GetAsHTMLVideoElement(); |
5001 | 0 | int32_t displayWidth = video->VideoWidth(); |
5002 | 0 | int32_t displayHeight = video->VideoHeight(); |
5003 | 0 | if (displayWidth == 0 || displayHeight == 0) { |
5004 | 0 | return; |
5005 | 0 | } |
5006 | 0 | aSw *= (double)imgSize.width / (double)displayWidth; |
5007 | 0 | aSh *= (double)imgSize.height / (double)displayHeight; |
5008 | 0 | } |
5009 | 0 |
|
5010 | 0 | if (mCanvasElement) { |
5011 | 0 | CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement, |
5012 | 0 | res.mPrincipal, res.mIsWriteOnly, |
5013 | 0 | res.mCORSUsed); |
5014 | 0 | } |
5015 | 0 |
|
5016 | 0 | if (res.mSourceSurface) { |
5017 | 0 | if (res.mImageRequest) { |
5018 | 0 | CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mSourceSurface, imgSize, mIsSkiaGL); |
5019 | 0 | } |
5020 | 0 | srcSurf = res.mSourceSurface; |
5021 | 0 | } else { |
5022 | 0 | drawInfo = res.mDrawInfo; |
5023 | 0 | } |
5024 | 0 | } |
5025 | 0 |
|
5026 | 0 | if (aOptional_argc == 0) { |
5027 | 0 | aSx = aSy = 0.0; |
5028 | 0 | aDw = aSw = (double) imgSize.width; |
5029 | 0 | aDh = aSh = (double) imgSize.height; |
5030 | 0 | } else if (aOptional_argc == 2) { |
5031 | 0 | aSx = aSy = 0.0; |
5032 | 0 | aSw = (double) imgSize.width; |
5033 | 0 | aSh = (double) imgSize.height; |
5034 | 0 | } |
5035 | 0 |
|
5036 | 0 | if (aSw == 0.0 || aSh == 0.0) { |
5037 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
5038 | 0 | return; |
5039 | 0 | } |
5040 | 0 | |
5041 | 0 | ClipImageDimension(aSx, aSw, imgSize.width, aDx, aDw); |
5042 | 0 | ClipImageDimension(aSy, aSh, imgSize.height, aDy, aDh); |
5043 | 0 |
|
5044 | 0 | if (aSw <= 0.0 || aSh <= 0.0 || |
5045 | 0 | aDw <= 0.0 || aDh <= 0.0) { |
5046 | 0 | // source and/or destination are fully clipped, so nothing is painted |
5047 | 0 | return; |
5048 | 0 | } |
5049 | 0 | |
5050 | 0 | // Per spec, the smoothing setting applies only to scaling up a bitmap image. |
5051 | 0 | // When down-scaling the user agent is free to choose whether or not to smooth |
5052 | 0 | // the image. Nearest sampling when down-scaling is rarely desirable and |
5053 | 0 | // smoothing when down-scaling matches chromium's behavior. |
5054 | 0 | // If any dimension is up-scaled, we consider the image as being up-scaled. |
5055 | 0 | auto scale = mTarget->GetTransform().ScaleFactors(true); |
5056 | 0 | bool isDownScale = aDw * Abs(scale.width) < aSw && aDh * Abs(scale.height) < aSh; |
5057 | 0 |
|
5058 | 0 | SamplingFilter samplingFilter; |
5059 | 0 | AntialiasMode antialiasMode; |
5060 | 0 |
|
5061 | 0 | if (CurrentState().imageSmoothingEnabled || isDownScale) { |
5062 | 0 | samplingFilter = gfx::SamplingFilter::LINEAR; |
5063 | 0 | antialiasMode = AntialiasMode::DEFAULT; |
5064 | 0 | } else { |
5065 | 0 | samplingFilter = gfx::SamplingFilter::POINT; |
5066 | 0 | antialiasMode = AntialiasMode::NONE; |
5067 | 0 | } |
5068 | 0 |
|
5069 | 0 | const bool needBounds = NeedToCalculateBounds(); |
5070 | 0 | if (!IsTargetValid()) { |
5071 | 0 | return; |
5072 | 0 | } |
5073 | 0 | gfx::Rect bounds; |
5074 | 0 | if (needBounds) { |
5075 | 0 | bounds = gfx::Rect(aDx, aDy, aDw, aDh); |
5076 | 0 | bounds = mTarget->GetTransform().TransformBounds(bounds); |
5077 | 0 | } |
5078 | 0 |
|
5079 | 0 | if (!IsTargetValid()) { |
5080 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5081 | 0 | return; |
5082 | 0 | } |
5083 | 0 | |
5084 | 0 | if (srcSurf) { |
5085 | 0 | gfx::Rect sourceRect(aSx, aSy, aSw, aSh); |
5086 | 0 | if (element == mCanvasElement) { |
5087 | 0 | // srcSurf is a snapshot of mTarget. If we draw to mTarget now, we'll |
5088 | 0 | // trigger a COW copy of the whole canvas into srcSurf. That's a huge |
5089 | 0 | // waste if sourceRect doesn't cover the whole canvas. |
5090 | 0 | // We avoid copying the whole canvas by manually copying just the part |
5091 | 0 | // that we need. |
5092 | 0 | srcSurf = ExtractSubrect(srcSurf, &sourceRect, mTarget); |
5093 | 0 | } |
5094 | 0 |
|
5095 | 0 | AdjustedTarget tempTarget(this, bounds.IsEmpty() ? nullptr : &bounds); |
5096 | 0 | if (!tempTarget) { |
5097 | 0 | gfxDevCrash(LogReason::InvalidDrawTarget) << "Invalid adjusted target in Canvas2D " << gfx::hexa((DrawTarget*)mTarget) << ", " << NeedToDrawShadow() << NeedToApplyFilter(); |
5098 | 0 | return; |
5099 | 0 | } |
5100 | 0 |
|
5101 | 0 | auto op = UsedOperation(); |
5102 | 0 | if (!IsTargetValid() || !tempTarget) { |
5103 | 0 | return; |
5104 | 0 | } |
5105 | 0 | tempTarget->DrawSurface(srcSurf, |
5106 | 0 | gfx::Rect(aDx, aDy, aDw, aDh), |
5107 | 0 | sourceRect, |
5108 | 0 | DrawSurfaceOptions(samplingFilter, SamplingBounds::UNBOUNDED), |
5109 | 0 | DrawOptions(CurrentState().globalAlpha, op, antialiasMode)); |
5110 | 0 | } else { |
5111 | 0 | DrawDirectlyToCanvas(drawInfo, &bounds, |
5112 | 0 | gfx::Rect(aDx, aDy, aDw, aDh), |
5113 | 0 | gfx::Rect(aSx, aSy, aSw, aSh), |
5114 | 0 | imgSize); |
5115 | 0 | } |
5116 | 0 |
|
5117 | 0 | RedrawUser(gfxRect(aDx, aDy, aDw, aDh)); |
5118 | 0 | } |
5119 | | |
5120 | | void |
5121 | | CanvasRenderingContext2D::DrawDirectlyToCanvas( |
5122 | | const nsLayoutUtils::DirectDrawInfo& aImage, |
5123 | | gfx::Rect* aBounds, |
5124 | | gfx::Rect aDest, |
5125 | | gfx::Rect aSrc, |
5126 | | gfx::IntSize aImgSize) |
5127 | 0 | { |
5128 | 0 | MOZ_ASSERT(aSrc.width > 0 && aSrc.height > 0, |
5129 | 0 | "Need positive source width and height"); |
5130 | 0 |
|
5131 | 0 | AdjustedTarget tempTarget(this, aBounds->IsEmpty() ? nullptr: aBounds); |
5132 | 0 | if (!tempTarget) { |
5133 | 0 | return; |
5134 | 0 | } |
5135 | 0 | |
5136 | 0 | // Get any existing transforms on the context, including transformations used |
5137 | 0 | // for context shadow. |
5138 | 0 | Matrix matrix = tempTarget->GetTransform(); |
5139 | 0 | gfxMatrix contextMatrix = ThebesMatrix(matrix); |
5140 | 0 | gfxSize contextScale(contextMatrix.ScaleFactors(true)); |
5141 | 0 |
|
5142 | 0 | // Scale the dest rect to include the context scale. |
5143 | 0 | aDest.Scale(contextScale.width, contextScale.height); |
5144 | 0 |
|
5145 | 0 | // Scale the image size to the dest rect, and adjust the source rect to match. |
5146 | 0 | gfxSize scale(aDest.width / aSrc.width, aDest.height / aSrc.height); |
5147 | 0 | IntSize scaledImageSize = IntSize::Ceil(aImgSize.width * scale.width, |
5148 | 0 | aImgSize.height * scale.height); |
5149 | 0 | aSrc.Scale(scale.width, scale.height); |
5150 | 0 |
|
5151 | 0 | // We're wrapping tempTarget's (our) DrawTarget here, so we need to restore |
5152 | 0 | // the matrix even though this is a temp gfxContext. |
5153 | 0 | AutoRestoreTransform autoRestoreTransform(mTarget); |
5154 | 0 |
|
5155 | 0 | RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempTarget); |
5156 | 0 | if (!context) { |
5157 | 0 | gfxDevCrash(LogReason::InvalidContext) << "Canvas context problem"; |
5158 | 0 | return; |
5159 | 0 | } |
5160 | 0 | context->SetMatrixDouble(contextMatrix. |
5161 | 0 | PreScale(1.0 / contextScale.width, |
5162 | 0 | 1.0 / contextScale.height). |
5163 | 0 | PreTranslate(aDest.x - aSrc.x, aDest.y - aSrc.y)); |
5164 | 0 |
|
5165 | 0 | // FLAG_CLAMP is added for increased performance, since we never tile here. |
5166 | 0 | uint32_t modifiedFlags = aImage.mDrawingFlags | imgIContainer::FLAG_CLAMP; |
5167 | 0 |
|
5168 | 0 | CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels? |
5169 | 0 | SVGImageContext svgContext(Some(sz)); |
5170 | 0 |
|
5171 | 0 | auto result = aImage.mImgContainer-> |
5172 | 0 | Draw(context, scaledImageSize, |
5173 | 0 | ImageRegion::Create(gfxRect(aSrc.x, aSrc.y, aSrc.width, aSrc.height)), |
5174 | 0 | aImage.mWhichFrame, SamplingFilter::GOOD, Some(svgContext), modifiedFlags, CurrentState().globalAlpha); |
5175 | 0 |
|
5176 | 0 | if (result != ImgDrawResult::SUCCESS) { |
5177 | 0 | NS_WARNING("imgIContainer::Draw failed"); |
5178 | 0 | } |
5179 | 0 | } |
5180 | | |
5181 | | void |
5182 | | CanvasRenderingContext2D::SetGlobalCompositeOperation(const nsAString& aOp, |
5183 | | ErrorResult& aError) |
5184 | 0 | { |
5185 | 0 | CompositionOp comp_op; |
5186 | 0 |
|
5187 | 0 | #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ |
5188 | 0 | if (aOp.EqualsLiteral(cvsop)) \ |
5189 | 0 | comp_op = CompositionOp::OP_##op2d; |
5190 | 0 |
|
5191 | 0 | CANVAS_OP_TO_GFX_OP("copy", SOURCE) |
5192 | 0 | else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) |
5193 | 0 | else CANVAS_OP_TO_GFX_OP("source-in", IN) |
5194 | 0 | else CANVAS_OP_TO_GFX_OP("source-out", OUT) |
5195 | 0 | else CANVAS_OP_TO_GFX_OP("source-over", OVER) |
5196 | 0 | else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) |
5197 | 0 | else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) |
5198 | 0 | else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) |
5199 | 0 | else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) |
5200 | 0 | else CANVAS_OP_TO_GFX_OP("lighter", ADD) |
5201 | 0 | else CANVAS_OP_TO_GFX_OP("xor", XOR) |
5202 | 0 | else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) |
5203 | 0 | else CANVAS_OP_TO_GFX_OP("screen", SCREEN) |
5204 | 0 | else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) |
5205 | 0 | else CANVAS_OP_TO_GFX_OP("darken", DARKEN) |
5206 | 0 | else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) |
5207 | 0 | else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) |
5208 | 0 | else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) |
5209 | 0 | else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) |
5210 | 0 | else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) |
5211 | 0 | else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) |
5212 | 0 | else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) |
5213 | 0 | else CANVAS_OP_TO_GFX_OP("hue", HUE) |
5214 | 0 | else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) |
5215 | 0 | else CANVAS_OP_TO_GFX_OP("color", COLOR) |
5216 | 0 | else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) |
5217 | 0 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
5218 | 0 | else return; |
5219 | 0 | |
5220 | 0 | #undef CANVAS_OP_TO_GFX_OP |
5221 | 0 | CurrentState().op = comp_op; |
5222 | 0 | } |
5223 | | |
5224 | | void |
5225 | | CanvasRenderingContext2D::GetGlobalCompositeOperation(nsAString& aOp, |
5226 | | ErrorResult& aError) |
5227 | 0 | { |
5228 | 0 | CompositionOp comp_op = CurrentState().op; |
5229 | 0 |
|
5230 | 0 | #define CANVAS_OP_TO_GFX_OP(cvsop, op2d) \ |
5231 | 0 | if (comp_op == CompositionOp::OP_##op2d) \ |
5232 | 0 | aOp.AssignLiteral(cvsop); |
5233 | 0 |
|
5234 | 0 | CANVAS_OP_TO_GFX_OP("copy", SOURCE) |
5235 | 0 | else CANVAS_OP_TO_GFX_OP("destination-atop", DEST_ATOP) |
5236 | 0 | else CANVAS_OP_TO_GFX_OP("destination-in", DEST_IN) |
5237 | 0 | else CANVAS_OP_TO_GFX_OP("destination-out", DEST_OUT) |
5238 | 0 | else CANVAS_OP_TO_GFX_OP("destination-over", DEST_OVER) |
5239 | 0 | else CANVAS_OP_TO_GFX_OP("lighter", ADD) |
5240 | 0 | else CANVAS_OP_TO_GFX_OP("source-atop", ATOP) |
5241 | 0 | else CANVAS_OP_TO_GFX_OP("source-in", IN) |
5242 | 0 | else CANVAS_OP_TO_GFX_OP("source-out", OUT) |
5243 | 0 | else CANVAS_OP_TO_GFX_OP("source-over", OVER) |
5244 | 0 | else CANVAS_OP_TO_GFX_OP("xor", XOR) |
5245 | 0 | else CANVAS_OP_TO_GFX_OP("multiply", MULTIPLY) |
5246 | 0 | else CANVAS_OP_TO_GFX_OP("screen", SCREEN) |
5247 | 0 | else CANVAS_OP_TO_GFX_OP("overlay", OVERLAY) |
5248 | 0 | else CANVAS_OP_TO_GFX_OP("darken", DARKEN) |
5249 | 0 | else CANVAS_OP_TO_GFX_OP("lighten", LIGHTEN) |
5250 | 0 | else CANVAS_OP_TO_GFX_OP("color-dodge", COLOR_DODGE) |
5251 | 0 | else CANVAS_OP_TO_GFX_OP("color-burn", COLOR_BURN) |
5252 | 0 | else CANVAS_OP_TO_GFX_OP("hard-light", HARD_LIGHT) |
5253 | 0 | else CANVAS_OP_TO_GFX_OP("soft-light", SOFT_LIGHT) |
5254 | 0 | else CANVAS_OP_TO_GFX_OP("difference", DIFFERENCE) |
5255 | 0 | else CANVAS_OP_TO_GFX_OP("exclusion", EXCLUSION) |
5256 | 0 | else CANVAS_OP_TO_GFX_OP("hue", HUE) |
5257 | 0 | else CANVAS_OP_TO_GFX_OP("saturation", SATURATION) |
5258 | 0 | else CANVAS_OP_TO_GFX_OP("color", COLOR) |
5259 | 0 | else CANVAS_OP_TO_GFX_OP("luminosity", LUMINOSITY) |
5260 | 0 | else { |
5261 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5262 | 0 | } |
5263 | 0 |
|
5264 | 0 | #undef CANVAS_OP_TO_GFX_OP |
5265 | 0 | } |
5266 | | |
5267 | | void |
5268 | | CanvasRenderingContext2D::DrawWindow(nsGlobalWindowInner& aWindow, double aX, |
5269 | | double aY, double aW, double aH, |
5270 | | const nsAString& aBgColor, |
5271 | | uint32_t aFlags, ErrorResult& aError) |
5272 | 0 | { |
5273 | 0 | if (int32_t(aW) == 0 || int32_t(aH) == 0) { |
5274 | 0 | return; |
5275 | 0 | } |
5276 | 0 | |
5277 | 0 | // protect against too-large surfaces that will cause allocation |
5278 | 0 | // or overflow issues |
5279 | 0 | if (!Factory::CheckSurfaceSize(IntSize(int32_t(aW), int32_t(aH)), 0xffff)) { |
5280 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5281 | 0 | return; |
5282 | 0 | } |
5283 | 0 | |
5284 | 0 | // Flush layout updates |
5285 | 0 | if (!(aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH)) { |
5286 | 0 | nsContentUtils::FlushLayoutForTree(aWindow.AsInner()->GetOuterWindow()); |
5287 | 0 | } |
5288 | 0 |
|
5289 | 0 | CompositionOp op = UsedOperation(); |
5290 | 0 | bool discardContent = GlobalAlpha() == 1.0f |
5291 | 0 | && (op == CompositionOp::OP_OVER || op == CompositionOp::OP_SOURCE); |
5292 | 0 | const gfx::Rect drawRect(aX, aY, aW, aH); |
5293 | 0 | EnsureTarget(discardContent ? &drawRect : nullptr); |
5294 | 0 | if (!IsTargetValid()) { |
5295 | 0 | return; |
5296 | 0 | } |
5297 | 0 | |
5298 | 0 | RefPtr<nsPresContext> presContext; |
5299 | 0 | nsIDocShell* docshell = aWindow.GetDocShell(); |
5300 | 0 | if (docshell) { |
5301 | 0 | docshell->GetPresContext(getter_AddRefs(presContext)); |
5302 | 0 | } |
5303 | 0 | if (!presContext) { |
5304 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5305 | 0 | return; |
5306 | 0 | } |
5307 | 0 | |
5308 | 0 | nscolor backgroundColor; |
5309 | 0 | if (!ParseColor(aBgColor, &backgroundColor)) { |
5310 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5311 | 0 | return; |
5312 | 0 | } |
5313 | 0 | |
5314 | 0 | nsRect r(nsPresContext::CSSPixelsToAppUnits((float)aX), |
5315 | 0 | nsPresContext::CSSPixelsToAppUnits((float)aY), |
5316 | 0 | nsPresContext::CSSPixelsToAppUnits((float)aW), |
5317 | 0 | nsPresContext::CSSPixelsToAppUnits((float)aH)); |
5318 | 0 | uint32_t renderDocFlags = (nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | |
5319 | 0 | nsIPresShell::RENDER_DOCUMENT_RELATIVE); |
5320 | 0 | if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_CARET) { |
5321 | 0 | renderDocFlags |= nsIPresShell::RENDER_CARET; |
5322 | 0 | } |
5323 | 0 | if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DRAW_VIEW) { |
5324 | 0 | renderDocFlags &= ~(nsIPresShell::RENDER_IGNORE_VIEWPORT_SCROLLING | |
5325 | 0 | nsIPresShell::RENDER_DOCUMENT_RELATIVE); |
5326 | 0 | } |
5327 | 0 | if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_USE_WIDGET_LAYERS) { |
5328 | 0 | renderDocFlags |= nsIPresShell::RENDER_USE_WIDGET_LAYERS; |
5329 | 0 | } |
5330 | 0 | if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_ASYNC_DECODE_IMAGES) { |
5331 | 0 | renderDocFlags |= nsIPresShell::RENDER_ASYNC_DECODE_IMAGES; |
5332 | 0 | } |
5333 | 0 | if (aFlags & CanvasRenderingContext2D_Binding::DRAWWINDOW_DO_NOT_FLUSH) { |
5334 | 0 | renderDocFlags |= nsIPresShell::RENDER_DRAWWINDOW_NOT_FLUSHING; |
5335 | 0 | } |
5336 | 0 |
|
5337 | 0 | // gfxContext-over-Azure may modify the DrawTarget's transform, so |
5338 | 0 | // save and restore it |
5339 | 0 | Matrix matrix = mTarget->GetTransform(); |
5340 | 0 | double sw = matrix._11 * aW; |
5341 | 0 | double sh = matrix._22 * aH; |
5342 | 0 | if (!sw || !sh) { |
5343 | 0 | return; |
5344 | 0 | } |
5345 | 0 | |
5346 | 0 | RefPtr<gfxContext> thebes; |
5347 | 0 | RefPtr<DrawTarget> drawDT; |
5348 | 0 | // Rendering directly is faster and can be done if mTarget supports Azure |
5349 | 0 | // and does not need alpha blending. |
5350 | 0 | // Since the pre-transaction callback calls ReturnTarget, we can't have a |
5351 | 0 | // gfxContext wrapped around it when using a shared buffer provider because |
5352 | 0 | // the DrawTarget's shared buffer may be unmapped in ReturnTarget. |
5353 | 0 | op = CompositionOp::OP_ADD; |
5354 | 0 | if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) && |
5355 | 0 | GlobalAlpha() == 1.0f) { |
5356 | 0 | op = UsedOperation(); |
5357 | 0 | if (!IsTargetValid()) { |
5358 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5359 | 0 | return; |
5360 | 0 | } |
5361 | 0 | } |
5362 | 0 | if (op == CompositionOp::OP_OVER && |
5363 | 0 | (!mBufferProvider || |
5364 | 0 | (mBufferProvider->GetType() != LayersBackend::LAYERS_CLIENT && |
5365 | 0 | mBufferProvider->GetType() != LayersBackend::LAYERS_WR))) |
5366 | 0 | { |
5367 | 0 | thebes = gfxContext::CreateOrNull(mTarget); |
5368 | 0 | MOZ_ASSERT(thebes); // already checked the draw target above |
5369 | 0 | // (in SupportsAzureContentForDrawTarget) |
5370 | 0 | thebes->SetMatrix(matrix); |
5371 | 0 | } else { |
5372 | 0 | IntSize dtSize = IntSize::Ceil(sw, sh); |
5373 | 0 | if (!Factory::AllowedSurfaceSize(dtSize)) { |
5374 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5375 | 0 | return; |
5376 | 0 | } |
5377 | 0 | drawDT = |
5378 | 0 | gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(dtSize, |
5379 | 0 | SurfaceFormat::B8G8R8A8); |
5380 | 0 | if (!drawDT || !drawDT->IsValid()) { |
5381 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5382 | 0 | return; |
5383 | 0 | } |
5384 | 0 | |
5385 | 0 | thebes = gfxContext::CreateOrNull(drawDT); |
5386 | 0 | MOZ_ASSERT(thebes); // alrady checked the draw target above |
5387 | 0 | thebes->SetMatrix(Matrix::Scaling(matrix._11, matrix._22)); |
5388 | 0 | } |
5389 | 0 |
|
5390 | 0 | nsCOMPtr<nsIPresShell> shell = presContext->PresShell(); |
5391 | 0 |
|
5392 | 0 | Unused << shell->RenderDocument(r, renderDocFlags, backgroundColor, thebes); |
5393 | 0 | // If this canvas was contained in the drawn window, the pre-transaction callback |
5394 | 0 | // may have returned its DT. If so, we must reacquire it here. |
5395 | 0 | EnsureTarget(discardContent ? &drawRect : nullptr); |
5396 | 0 |
|
5397 | 0 | if (drawDT) { |
5398 | 0 | RefPtr<SourceSurface> snapshot = drawDT->Snapshot(); |
5399 | 0 | if (NS_WARN_IF(!snapshot)) { |
5400 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5401 | 0 | return; |
5402 | 0 | } |
5403 | 0 | RefPtr<DataSourceSurface> data = snapshot->GetDataSurface(); |
5404 | 0 | if (!data || !Factory::AllowedSurfaceSize(data->GetSize())) { |
5405 | 0 | gfxCriticalError() << "Unexpected invalid data source surface " << |
5406 | 0 | (data ? data->GetSize() : IntSize(0,0)); |
5407 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5408 | 0 | return; |
5409 | 0 | } |
5410 | 0 |
|
5411 | 0 | DataSourceSurface::MappedSurface rawData; |
5412 | 0 | if (NS_WARN_IF(!data->Map(DataSourceSurface::READ, &rawData))) { |
5413 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5414 | 0 | return; |
5415 | 0 | } |
5416 | 0 | RefPtr<SourceSurface> source = |
5417 | 0 | mTarget->CreateSourceSurfaceFromData(rawData.mData, |
5418 | 0 | data->GetSize(), |
5419 | 0 | rawData.mStride, |
5420 | 0 | data->GetFormat()); |
5421 | 0 | data->Unmap(); |
5422 | 0 |
|
5423 | 0 | if (!source) { |
5424 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5425 | 0 | return; |
5426 | 0 | } |
5427 | 0 | |
5428 | 0 | op = UsedOperation(); |
5429 | 0 | if (!IsTargetValid()) { |
5430 | 0 | aError.Throw(NS_ERROR_FAILURE); |
5431 | 0 | return; |
5432 | 0 | } |
5433 | 0 | gfx::Rect destRect(0, 0, aW, aH); |
5434 | 0 | gfx::Rect sourceRect(0, 0, sw, sh); |
5435 | 0 | mTarget->DrawSurface(source, destRect, sourceRect, |
5436 | 0 | DrawSurfaceOptions(gfx::SamplingFilter::POINT), |
5437 | 0 | DrawOptions(GlobalAlpha(), op, |
5438 | 0 | AntialiasMode::NONE)); |
5439 | 0 | } else { |
5440 | 0 | mTarget->SetTransform(matrix); |
5441 | 0 | } |
5442 | 0 |
|
5443 | 0 | // note that x and y are coordinates in the document that |
5444 | 0 | // we're drawing; x and y are drawn to 0,0 in current user |
5445 | 0 | // space. |
5446 | 0 | RedrawUser(gfxRect(0, 0, aW, aH)); |
5447 | 0 | } |
5448 | | |
5449 | | // |
5450 | | // device pixel getting/setting |
5451 | | // |
5452 | | |
5453 | | already_AddRefed<ImageData> |
5454 | | CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx, |
5455 | | double aSy, double aSw, double aSh, |
5456 | | nsIPrincipal& aSubjectPrincipal, |
5457 | | ErrorResult& aError) |
5458 | 0 | { |
5459 | 0 | if (mDrawObserver) { |
5460 | 0 | mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData); |
5461 | 0 | } |
5462 | 0 |
|
5463 | 0 | if (!mCanvasElement && !mDocShell) { |
5464 | 0 | NS_ERROR("No canvas element and no docshell in GetImageData!!!"); |
5465 | 0 | aError.Throw(NS_ERROR_DOM_SECURITY_ERR); |
5466 | 0 | return nullptr; |
5467 | 0 | } |
5468 | 0 |
|
5469 | 0 | // Check only if we have a canvas element; if we were created with a docshell, |
5470 | 0 | // then it's special internal use. |
5471 | 0 | if (mCanvasElement && mCanvasElement->IsWriteOnly() && |
5472 | 0 | // We could ask bindings for the caller type, but they already hand us a |
5473 | 0 | // JSContext, and we're at least _somewhat_ perf-sensitive (so may not |
5474 | 0 | // want to compute the caller type in the common non-write-only case), so |
5475 | 0 | // let's just use what we have. |
5476 | 0 | !nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::all_urlsPermission)) |
5477 | 0 | { |
5478 | 0 | // XXX ERRMSG we need to report an error to developers here! (bug 329026) |
5479 | 0 | aError.Throw(NS_ERROR_DOM_SECURITY_ERR); |
5480 | 0 | return nullptr; |
5481 | 0 | } |
5482 | 0 | |
5483 | 0 | if (!IsFinite(aSx) || !IsFinite(aSy) || |
5484 | 0 | !IsFinite(aSw) || !IsFinite(aSh)) { |
5485 | 0 | aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
5486 | 0 | return nullptr; |
5487 | 0 | } |
5488 | 0 | |
5489 | 0 | if (!aSw || !aSh) { |
5490 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
5491 | 0 | return nullptr; |
5492 | 0 | } |
5493 | 0 | |
5494 | 0 | int32_t x = JS::ToInt32(aSx); |
5495 | 0 | int32_t y = JS::ToInt32(aSy); |
5496 | 0 | int32_t wi = JS::ToInt32(aSw); |
5497 | 0 | int32_t hi = JS::ToInt32(aSh); |
5498 | 0 |
|
5499 | 0 | // Handle negative width and height by flipping the rectangle over in the |
5500 | 0 | // relevant direction. |
5501 | 0 | uint32_t w, h; |
5502 | 0 | if (aSw < 0) { |
5503 | 0 | w = -wi; |
5504 | 0 | x -= w; |
5505 | 0 | } else { |
5506 | 0 | w = wi; |
5507 | 0 | } |
5508 | 0 | if (aSh < 0) { |
5509 | 0 | h = -hi; |
5510 | 0 | y -= h; |
5511 | 0 | } else { |
5512 | 0 | h = hi; |
5513 | 0 | } |
5514 | 0 |
|
5515 | 0 | if (w == 0) { |
5516 | 0 | w = 1; |
5517 | 0 | } |
5518 | 0 | if (h == 0) { |
5519 | 0 | h = 1; |
5520 | 0 | } |
5521 | 0 |
|
5522 | 0 | JS::Rooted<JSObject*> array(aCx); |
5523 | 0 | aError = GetImageDataArray(aCx, x, y, w, h, aSubjectPrincipal, array.address()); |
5524 | 0 | if (aError.Failed()) { |
5525 | 0 | return nullptr; |
5526 | 0 | } |
5527 | 0 | MOZ_ASSERT(array); |
5528 | 0 |
|
5529 | 0 | RefPtr<ImageData> imageData = new ImageData(w, h, *array); |
5530 | 0 | return imageData.forget(); |
5531 | 0 | } |
5532 | | |
5533 | | nsresult |
5534 | | CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx, |
5535 | | int32_t aX, |
5536 | | int32_t aY, |
5537 | | uint32_t aWidth, |
5538 | | uint32_t aHeight, |
5539 | | nsIPrincipal& aSubjectPrincipal, |
5540 | | JSObject** aRetval) |
5541 | 0 | { |
5542 | 0 | if (mDrawObserver) { |
5543 | 0 | mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData); |
5544 | 0 | } |
5545 | 0 |
|
5546 | 0 | MOZ_ASSERT(aWidth && aHeight); |
5547 | 0 |
|
5548 | 0 | CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4; |
5549 | 0 | if (!len.isValid()) { |
5550 | 0 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
5551 | 0 | } |
5552 | 0 | |
5553 | 0 | CheckedInt<int32_t> rightMost = CheckedInt<int32_t>(aX) + aWidth; |
5554 | 0 | CheckedInt<int32_t> bottomMost = CheckedInt<int32_t>(aY) + aHeight; |
5555 | 0 |
|
5556 | 0 | if (!rightMost.isValid() || !bottomMost.isValid()) { |
5557 | 0 | return NS_ERROR_DOM_SYNTAX_ERR; |
5558 | 0 | } |
5559 | 0 | |
5560 | 0 | JS::Rooted<JSObject*> darray(aCx, JS_NewUint8ClampedArray(aCx, len.value())); |
5561 | 0 | if (!darray) { |
5562 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
5563 | 0 | } |
5564 | 0 | |
5565 | 0 | if (mZero) { |
5566 | 0 | *aRetval = darray; |
5567 | 0 | return NS_OK; |
5568 | 0 | } |
5569 | 0 | |
5570 | 0 | IntRect srcRect(0, 0, mWidth, mHeight); |
5571 | 0 | IntRect destRect(aX, aY, aWidth, aHeight); |
5572 | 0 | IntRect srcReadRect = srcRect.Intersect(destRect); |
5573 | 0 | if (srcReadRect.IsEmpty()) { |
5574 | 0 | *aRetval = darray; |
5575 | 0 | return NS_OK; |
5576 | 0 | } |
5577 | 0 | |
5578 | 0 | RefPtr<DataSourceSurface> readback; |
5579 | 0 | DataSourceSurface::MappedSurface rawData; |
5580 | 0 | RefPtr<SourceSurface> snapshot; |
5581 | 0 | if (!mTarget && mBufferProvider) { |
5582 | 0 | snapshot = mBufferProvider->BorrowSnapshot(); |
5583 | 0 | } else { |
5584 | 0 | EnsureTarget(); |
5585 | 0 | if (!IsTargetValid()) { |
5586 | 0 | return NS_ERROR_FAILURE; |
5587 | 0 | } |
5588 | 0 | snapshot = mTarget->Snapshot(); |
5589 | 0 | } |
5590 | 0 |
|
5591 | 0 | if (snapshot) { |
5592 | 0 | readback = snapshot->GetDataSurface(); |
5593 | 0 | } |
5594 | 0 |
|
5595 | 0 | if (!mTarget && mBufferProvider) { |
5596 | 0 | mBufferProvider->ReturnSnapshot(snapshot.forget()); |
5597 | 0 | } |
5598 | 0 |
|
5599 | 0 | if (!readback || !readback->Map(DataSourceSurface::READ, &rawData)) { |
5600 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
5601 | 0 | } |
5602 | 0 | |
5603 | 0 | IntRect dstWriteRect = srcReadRect; |
5604 | 0 | dstWriteRect.MoveBy(-aX, -aY); |
5605 | 0 |
|
5606 | 0 | // Check for site-specific permission. This check is not needed if the |
5607 | 0 | // canvas was created with a docshell (that is only done for special |
5608 | 0 | // internal uses). |
5609 | 0 | bool usePlaceholder = false; |
5610 | 0 | if (mCanvasElement) { |
5611 | 0 | nsCOMPtr<nsIDocument> ownerDoc = mCanvasElement->OwnerDoc(); |
5612 | 0 | usePlaceholder = !CanvasUtils::IsImageExtractionAllowed(ownerDoc, aCx, aSubjectPrincipal); |
5613 | 0 | } |
5614 | 0 |
|
5615 | 0 | do { |
5616 | 0 | JS::AutoCheckCannotGC nogc; |
5617 | 0 | bool isShared; |
5618 | 0 | uint8_t* data = JS_GetUint8ClampedArrayData(darray, &isShared, nogc); |
5619 | 0 | MOZ_ASSERT(!isShared); // Should not happen, data was created above |
5620 | 0 |
|
5621 | 0 | uint32_t srcStride = rawData.mStride; |
5622 | 0 | uint8_t* src = rawData.mData + srcReadRect.y * srcStride + srcReadRect.x * 4; |
5623 | 0 |
|
5624 | 0 | // Return all-white, opaque pixel data if no permission. |
5625 | 0 | if (usePlaceholder) { |
5626 | 0 | memset(data, 0xFF, len.value()); |
5627 | 0 | break; |
5628 | 0 | } |
5629 | 0 | |
5630 | 0 | uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4; |
5631 | 0 |
|
5632 | 0 | if (mOpaque) { |
5633 | 0 | SwizzleData(src, srcStride, SurfaceFormat::X8R8G8B8_UINT32, |
5634 | 0 | dst, aWidth * 4, SurfaceFormat::R8G8B8A8, |
5635 | 0 | dstWriteRect.Size()); |
5636 | 0 | } else { |
5637 | 0 | UnpremultiplyData(src, srcStride, SurfaceFormat::A8R8G8B8_UINT32, |
5638 | 0 | dst, aWidth * 4, SurfaceFormat::R8G8B8A8, |
5639 | 0 | dstWriteRect.Size()); |
5640 | 0 | } |
5641 | 0 | } while (false); |
5642 | 0 |
|
5643 | 0 | readback->Unmap(); |
5644 | 0 | *aRetval = darray; |
5645 | 0 | return NS_OK; |
5646 | 0 | } |
5647 | | |
5648 | | void |
5649 | | CanvasRenderingContext2D::EnsureErrorTarget() |
5650 | 0 | { |
5651 | 0 | if (sErrorTarget) { |
5652 | 0 | return; |
5653 | 0 | } |
5654 | 0 | |
5655 | 0 | RefPtr<DrawTarget> errorTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8); |
5656 | 0 | MOZ_ASSERT(errorTarget, "Failed to allocate the error target!"); |
5657 | 0 |
|
5658 | 0 | sErrorTarget = errorTarget; |
5659 | 0 | NS_ADDREF(sErrorTarget); |
5660 | 0 | } |
5661 | | |
5662 | | void |
5663 | | CanvasRenderingContext2D::FillRuleChanged() |
5664 | 0 | { |
5665 | 0 | if (mPath) { |
5666 | 0 | mPathBuilder = mPath->CopyToBuilder(CurrentState().fillRule); |
5667 | 0 | mPath = nullptr; |
5668 | 0 | } |
5669 | 0 | } |
5670 | | |
5671 | | void |
5672 | | CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx, |
5673 | | double aDy, ErrorResult& aError) |
5674 | 0 | { |
5675 | 0 | RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); |
5676 | 0 | DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject()); |
5677 | 0 | MOZ_ASSERT(inited); |
5678 | 0 |
|
5679 | 0 | aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy), |
5680 | 0 | aImageData.Width(), aImageData.Height(), |
5681 | 0 | &arr, false, 0, 0, 0, 0); |
5682 | 0 | } |
5683 | | |
5684 | | void |
5685 | | CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx, |
5686 | | double aDy, double aDirtyX, |
5687 | | double aDirtyY, double aDirtyWidth, |
5688 | | double aDirtyHeight, |
5689 | | ErrorResult& aError) |
5690 | 0 | { |
5691 | 0 | RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx()); |
5692 | 0 | DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject()); |
5693 | 0 | MOZ_ASSERT(inited); |
5694 | 0 |
|
5695 | 0 | aError = PutImageData_explicit(JS::ToInt32(aDx), JS::ToInt32(aDy), |
5696 | 0 | aImageData.Width(), aImageData.Height(), |
5697 | 0 | &arr, true, |
5698 | 0 | JS::ToInt32(aDirtyX), |
5699 | 0 | JS::ToInt32(aDirtyY), |
5700 | 0 | JS::ToInt32(aDirtyWidth), |
5701 | 0 | JS::ToInt32(aDirtyHeight)); |
5702 | 0 | } |
5703 | | |
5704 | | nsresult |
5705 | | CanvasRenderingContext2D::PutImageData_explicit(int32_t aX, int32_t aY, uint32_t aW, uint32_t aH, |
5706 | | dom::Uint8ClampedArray* aArray, |
5707 | | bool aHasDirtyRect, int32_t aDirtyX, int32_t aDirtyY, |
5708 | | int32_t aDirtyWidth, int32_t aDirtyHeight) |
5709 | 0 | { |
5710 | 0 | if (mDrawObserver) { |
5711 | 0 | mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData); |
5712 | 0 | } |
5713 | 0 |
|
5714 | 0 | if (aW == 0 || aH == 0) { |
5715 | 0 | return NS_ERROR_DOM_INVALID_STATE_ERR; |
5716 | 0 | } |
5717 | 0 | |
5718 | 0 | IntRect dirtyRect; |
5719 | 0 | IntRect imageDataRect(0, 0, aW, aH); |
5720 | 0 |
|
5721 | 0 | if (aHasDirtyRect) { |
5722 | 0 | // fix up negative dimensions |
5723 | 0 | if (aDirtyWidth < 0) { |
5724 | 0 | NS_ENSURE_TRUE(aDirtyWidth != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); |
5725 | 0 |
|
5726 | 0 | CheckedInt32 checkedDirtyX = CheckedInt32(aDirtyX) + aDirtyWidth; |
5727 | 0 |
|
5728 | 0 | if (!checkedDirtyX.isValid()) |
5729 | 0 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
5730 | 0 | |
5731 | 0 | aDirtyX = checkedDirtyX.value(); |
5732 | 0 | aDirtyWidth = -aDirtyWidth; |
5733 | 0 | } |
5734 | 0 |
|
5735 | 0 | if (aDirtyHeight < 0) { |
5736 | 0 | NS_ENSURE_TRUE(aDirtyHeight != INT_MIN, NS_ERROR_DOM_INDEX_SIZE_ERR); |
5737 | 0 |
|
5738 | 0 | CheckedInt32 checkedDirtyY = CheckedInt32(aDirtyY) + aDirtyHeight; |
5739 | 0 |
|
5740 | 0 | if (!checkedDirtyY.isValid()) |
5741 | 0 | return NS_ERROR_DOM_INDEX_SIZE_ERR; |
5742 | 0 | |
5743 | 0 | aDirtyY = checkedDirtyY.value(); |
5744 | 0 | aDirtyHeight = -aDirtyHeight; |
5745 | 0 | } |
5746 | 0 |
|
5747 | 0 | // bound the dirty rect within the imageData rectangle |
5748 | 0 | dirtyRect = imageDataRect.Intersect(IntRect(aDirtyX, aDirtyY, aDirtyWidth, aDirtyHeight)); |
5749 | 0 |
|
5750 | 0 | if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) |
5751 | 0 | return NS_OK; |
5752 | 0 | } else { |
5753 | 0 | dirtyRect = imageDataRect; |
5754 | 0 | } |
5755 | 0 |
|
5756 | 0 | dirtyRect.MoveBy(IntPoint(aX, aY)); |
5757 | 0 | dirtyRect = IntRect(0, 0, mWidth, mHeight).Intersect(dirtyRect); |
5758 | 0 |
|
5759 | 0 | if (dirtyRect.Width() <= 0 || dirtyRect.Height() <= 0) { |
5760 | 0 | return NS_OK; |
5761 | 0 | } |
5762 | 0 | |
5763 | 0 | aArray->ComputeLengthAndData(); |
5764 | 0 |
|
5765 | 0 | uint32_t dataLen = aArray->Length(); |
5766 | 0 |
|
5767 | 0 | uint32_t len = aW * aH * 4; |
5768 | 0 | if (dataLen != len) { |
5769 | 0 | return NS_ERROR_DOM_INVALID_STATE_ERR; |
5770 | 0 | } |
5771 | 0 | |
5772 | 0 | // The canvas spec says that the current path, transformation matrix, shadow attributes, |
5773 | 0 | // global alpha, the clipping region, and global composition operator must not affect the |
5774 | 0 | // getImageData() and putImageData() methods. |
5775 | 0 | const gfx::Rect putRect(dirtyRect); |
5776 | 0 | EnsureTarget(&putRect); |
5777 | 0 |
|
5778 | 0 | if (!IsTargetValid()) { |
5779 | 0 | return NS_ERROR_FAILURE; |
5780 | 0 | } |
5781 | 0 | |
5782 | 0 | DataSourceSurface::MappedSurface map; |
5783 | 0 | RefPtr<DataSourceSurface> sourceSurface; |
5784 | 0 | uint8_t* lockedBits = nullptr; |
5785 | 0 | uint8_t* dstData; |
5786 | 0 | IntSize dstSize; |
5787 | 0 | int32_t dstStride; |
5788 | 0 | SurfaceFormat dstFormat; |
5789 | 0 | if (mTarget->LockBits(&lockedBits, &dstSize, &dstStride, &dstFormat)) { |
5790 | 0 | dstData = lockedBits + dirtyRect.y * dstStride + dirtyRect.x * 4; |
5791 | 0 | } else { |
5792 | 0 | sourceSurface = |
5793 | 0 | Factory::CreateDataSourceSurface(dirtyRect.Size(), |
5794 | 0 | SurfaceFormat::B8G8R8A8, |
5795 | 0 | false); |
5796 | 0 |
|
5797 | 0 | // In certain scenarios, requesting larger than 8k image fails. Bug 803568 |
5798 | 0 | // covers the details of how to run into it, but the full detailed |
5799 | 0 | // investigation hasn't been done to determine the underlying cause. We |
5800 | 0 | // will just handle the failure to allocate the surface to avoid a crash. |
5801 | 0 | if (!sourceSurface) { |
5802 | 0 | return NS_ERROR_FAILURE; |
5803 | 0 | } |
5804 | 0 | if (!sourceSurface->Map(DataSourceSurface::READ_WRITE, &map)) { |
5805 | 0 | return NS_ERROR_FAILURE; |
5806 | 0 | } |
5807 | 0 | |
5808 | 0 | dstData = map.mData; |
5809 | 0 | if (!dstData) { |
5810 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
5811 | 0 | } |
5812 | 0 | dstStride = map.mStride; |
5813 | 0 | dstFormat = sourceSurface->GetFormat(); |
5814 | 0 | } |
5815 | 0 |
|
5816 | 0 | IntRect srcRect = dirtyRect - IntPoint(aX, aY); |
5817 | 0 | uint8_t* srcData = aArray->Data() + srcRect.y * (aW * 4) + srcRect.x * 4; |
5818 | 0 |
|
5819 | 0 | PremultiplyData(srcData, aW * 4, SurfaceFormat::R8G8B8A8, |
5820 | 0 | dstData, dstStride, |
5821 | 0 | mOpaque ? SurfaceFormat::X8R8G8B8_UINT32 : SurfaceFormat::A8R8G8B8_UINT32, |
5822 | 0 | dirtyRect.Size()); |
5823 | 0 |
|
5824 | 0 | if (lockedBits) { |
5825 | 0 | mTarget->ReleaseBits(lockedBits); |
5826 | 0 | } else if (sourceSurface) { |
5827 | 0 | sourceSurface->Unmap(); |
5828 | 0 | mTarget->CopySurface(sourceSurface, dirtyRect - dirtyRect.TopLeft(), dirtyRect.TopLeft()); |
5829 | 0 | } |
5830 | 0 |
|
5831 | 0 | Redraw(gfx::Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)); |
5832 | 0 |
|
5833 | 0 | return NS_OK; |
5834 | 0 | } |
5835 | | |
5836 | | static already_AddRefed<ImageData> |
5837 | | CreateImageData(JSContext* aCx, CanvasRenderingContext2D* aContext, |
5838 | | uint32_t aW, uint32_t aH, ErrorResult& aError) |
5839 | 0 | { |
5840 | 0 | if (aW == 0) |
5841 | 0 | aW = 1; |
5842 | 0 | if (aH == 0) |
5843 | 0 | aH = 1; |
5844 | 0 |
|
5845 | 0 | CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aW) * aH * 4; |
5846 | 0 | if (!len.isValid()) { |
5847 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
5848 | 0 | return nullptr; |
5849 | 0 | } |
5850 | 0 | |
5851 | 0 | // Create the fast typed array; it's initialized to 0 by default. |
5852 | 0 | JSObject* darray = Uint8ClampedArray::Create(aCx, aContext, len.value()); |
5853 | 0 | if (!darray) { |
5854 | 0 | aError.Throw(NS_ERROR_OUT_OF_MEMORY); |
5855 | 0 | return nullptr; |
5856 | 0 | } |
5857 | 0 | |
5858 | 0 | RefPtr<mozilla::dom::ImageData> imageData = |
5859 | 0 | new mozilla::dom::ImageData(aW, aH, *darray); |
5860 | 0 | return imageData.forget(); |
5861 | 0 | } |
5862 | | |
5863 | | already_AddRefed<ImageData> |
5864 | | CanvasRenderingContext2D::CreateImageData(JSContext* aCx, double aSw, |
5865 | | double aSh, ErrorResult& aError) |
5866 | 0 | { |
5867 | 0 | if (!aSw || !aSh) { |
5868 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
5869 | 0 | return nullptr; |
5870 | 0 | } |
5871 | 0 | |
5872 | 0 | int32_t wi = JS::ToInt32(aSw); |
5873 | 0 | int32_t hi = JS::ToInt32(aSh); |
5874 | 0 |
|
5875 | 0 | uint32_t w = Abs(wi); |
5876 | 0 | uint32_t h = Abs(hi); |
5877 | 0 | return mozilla::dom::CreateImageData(aCx, this, w, h, aError); |
5878 | 0 | } |
5879 | | |
5880 | | already_AddRefed<ImageData> |
5881 | | CanvasRenderingContext2D::CreateImageData(JSContext* aCx, |
5882 | | ImageData& aImagedata, |
5883 | | ErrorResult& aError) |
5884 | 0 | { |
5885 | 0 | return mozilla::dom::CreateImageData(aCx, this, aImagedata.Width(), |
5886 | 0 | aImagedata.Height(), aError); |
5887 | 0 | } |
5888 | | |
5889 | | static uint8_t g2DContextLayerUserData; |
5890 | | |
5891 | | |
5892 | | uint32_t |
5893 | | CanvasRenderingContext2D::SkiaGLTex() const |
5894 | 0 | { |
5895 | 0 | if (!mTarget) { |
5896 | 0 | return 0; |
5897 | 0 | } |
5898 | 0 | MOZ_ASSERT(IsTargetValid()); |
5899 | 0 | return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE); |
5900 | 0 | } |
5901 | | |
5902 | | void CanvasRenderingContext2D::RemoveDrawObserver() |
5903 | 0 | { |
5904 | 0 | if (mDrawObserver) { |
5905 | 0 | delete mDrawObserver; |
5906 | 0 | mDrawObserver = nullptr; |
5907 | 0 | } |
5908 | 0 | } |
5909 | | |
5910 | | already_AddRefed<Layer> |
5911 | | CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, |
5912 | | Layer* aOldLayer, |
5913 | | LayerManager* aManager) |
5914 | 0 | { |
5915 | 0 | if (mOpaque || mIsSkiaGL) { |
5916 | 0 | // If we're opaque then make sure we have a surface so we paint black |
5917 | 0 | // instead of transparent. |
5918 | 0 | // If we're using SkiaGL, then SkiaGLTex() below needs the target to |
5919 | 0 | // be accessible. |
5920 | 0 | EnsureTarget(); |
5921 | 0 | } |
5922 | 0 |
|
5923 | 0 | // Don't call EnsureTarget() ... if there isn't already a surface, then |
5924 | 0 | // we have nothing to paint and there is no need to create a surface just |
5925 | 0 | // to paint nothing. Also, EnsureTarget() can cause creation of a persistent |
5926 | 0 | // layer manager which must NOT happen during a paint. |
5927 | 0 | if (!mBufferProvider && !IsTargetValid()) { |
5928 | 0 | // No DidTransactionCallback will be received, so mark the context clean |
5929 | 0 | // now so future invalidations will be dispatched. |
5930 | 0 | MarkContextClean(); |
5931 | 0 | return nullptr; |
5932 | 0 | } |
5933 | 0 | |
5934 | 0 | if (!mResetLayer && aOldLayer) { |
5935 | 0 | auto userData = |
5936 | 0 | static_cast<CanvasRenderingContext2DUserData*>( |
5937 | 0 | aOldLayer->GetUserData(&g2DContextLayerUserData)); |
5938 | 0 |
|
5939 | 0 | CanvasInitializeData data; |
5940 | 0 |
|
5941 | 0 | if (mIsSkiaGL) { |
5942 | 0 | GLuint skiaGLTex = SkiaGLTex(); |
5943 | 0 | if (skiaGLTex) { |
5944 | 0 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
5945 | 0 | MOZ_ASSERT(glue); |
5946 | 0 | data.mGLContext = glue->GetGLContext(); |
5947 | 0 | data.mFrontbufferGLTex = skiaGLTex; |
5948 | 0 | } |
5949 | 0 | } |
5950 | 0 |
|
5951 | 0 | data.mBufferProvider = mBufferProvider; |
5952 | 0 |
|
5953 | 0 | if (userData && |
5954 | 0 | userData->IsForContext(this) && |
5955 | 0 | static_cast<CanvasLayer*>(aOldLayer)->CreateOrGetCanvasRenderer()->IsDataValid(data)) { |
5956 | 0 | RefPtr<Layer> ret = aOldLayer; |
5957 | 0 | return ret.forget(); |
5958 | 0 | } |
5959 | 0 | } |
5960 | 0 | |
5961 | 0 | RefPtr<CanvasLayer> canvasLayer = aManager->CreateCanvasLayer(); |
5962 | 0 | if (!canvasLayer) { |
5963 | 0 | NS_WARNING("CreateCanvasLayer returned null!"); |
5964 | 0 | // No DidTransactionCallback will be received, so mark the context clean |
5965 | 0 | // now so future invalidations will be dispatched. |
5966 | 0 | MarkContextClean(); |
5967 | 0 | return nullptr; |
5968 | 0 | } |
5969 | 0 | CanvasRenderingContext2DUserData* userData = nullptr; |
5970 | 0 | // Make the layer tell us whenever a transaction finishes (including |
5971 | 0 | // the current transaction), so we can clear our invalidation state and |
5972 | 0 | // start invalidating again. We need to do this for all layers since |
5973 | 0 | // callers of DrawWindow may be expecting to receive normal invalidation |
5974 | 0 | // notifications after this paint. |
5975 | 0 |
|
5976 | 0 | // The layer will be destroyed when we tear down the presentation |
5977 | 0 | // (at the latest), at which time this userData will be destroyed, |
5978 | 0 | // releasing the reference to the element. |
5979 | 0 | // The userData will receive DidTransactionCallbacks, which flush the |
5980 | 0 | // the invalidation state to indicate that the canvas is up to date. |
5981 | 0 | userData = new CanvasRenderingContext2DUserData(this); |
5982 | 0 | canvasLayer->SetUserData(&g2DContextLayerUserData, userData); |
5983 | 0 |
|
5984 | 0 | CanvasRenderer* canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer(); |
5985 | 0 | InitializeCanvasRenderer(aBuilder, canvasRenderer); |
5986 | 0 | uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; |
5987 | 0 | canvasLayer->SetContentFlags(flags); |
5988 | 0 |
|
5989 | 0 | mResetLayer = false; |
5990 | 0 |
|
5991 | 0 | return canvasLayer.forget(); |
5992 | 0 | } |
5993 | | |
5994 | | bool |
5995 | | CanvasRenderingContext2D::UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder, |
5996 | | WebRenderCanvasData* aCanvasData) |
5997 | 0 | { |
5998 | 0 | if (mOpaque || mIsSkiaGL) { |
5999 | 0 | // If we're opaque then make sure we have a surface so we paint black |
6000 | 0 | // instead of transparent. |
6001 | 0 | // If we're using SkiaGL, then SkiaGLTex() below needs the target to |
6002 | 0 | // be accessible. |
6003 | 0 | EnsureTarget(); |
6004 | 0 | } |
6005 | 0 |
|
6006 | 0 | // Don't call EnsureTarget() ... if there isn't already a surface, then |
6007 | 0 | // we have nothing to paint and there is no need to create a surface just |
6008 | 0 | // to paint nothing. Also, EnsureTarget() can cause creation of a persistent |
6009 | 0 | // layer manager which must NOT happen during a paint. |
6010 | 0 | if (!mBufferProvider && !IsTargetValid()) { |
6011 | 0 | // No DidTransactionCallback will be received, so mark the context clean |
6012 | 0 | // now so future invalidations will be dispatched. |
6013 | 0 | MarkContextClean(); |
6014 | 0 | // Clear CanvasRenderer of WebRenderCanvasData |
6015 | 0 | aCanvasData->ClearCanvasRenderer(); |
6016 | 0 | return false; |
6017 | 0 | } |
6018 | 0 | |
6019 | 0 | CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer(); |
6020 | 0 |
|
6021 | 0 | if(!mResetLayer && renderer) { |
6022 | 0 | CanvasInitializeData data; |
6023 | 0 |
|
6024 | 0 | if (mIsSkiaGL) { |
6025 | 0 | GLuint skiaGLTex = SkiaGLTex(); |
6026 | 0 | if (skiaGLTex) { |
6027 | 0 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
6028 | 0 | MOZ_ASSERT(glue); |
6029 | 0 | data.mGLContext = glue->GetGLContext(); |
6030 | 0 | data.mFrontbufferGLTex = skiaGLTex; |
6031 | 0 | } |
6032 | 0 | } |
6033 | 0 | data.mBufferProvider = mBufferProvider; |
6034 | 0 |
|
6035 | 0 | if (renderer->IsDataValid(data)) { |
6036 | 0 | return true; |
6037 | 0 | } |
6038 | 0 | } |
6039 | 0 | |
6040 | 0 | renderer = aCanvasData->CreateCanvasRenderer(); |
6041 | 0 | if (!InitializeCanvasRenderer(aBuilder, renderer)) { |
6042 | 0 | // Clear CanvasRenderer of WebRenderCanvasData |
6043 | 0 | aCanvasData->ClearCanvasRenderer(); |
6044 | 0 | return false; |
6045 | 0 | } |
6046 | 0 | |
6047 | 0 | MOZ_ASSERT(renderer); |
6048 | 0 | mResetLayer = false; |
6049 | 0 | return true; |
6050 | 0 | } |
6051 | | |
6052 | | bool |
6053 | | CanvasRenderingContext2D::InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder, |
6054 | | CanvasRenderer* aRenderer) |
6055 | 0 | { |
6056 | 0 | CanvasInitializeData data; |
6057 | 0 | data.mSize = GetSize(); |
6058 | 0 | data.mHasAlpha = !mOpaque; |
6059 | 0 | data.mPreTransCallback = CanvasRenderingContext2DUserData::PreTransactionCallback; |
6060 | 0 | data.mPreTransCallbackData = this; |
6061 | 0 | data.mDidTransCallback = CanvasRenderingContext2DUserData::DidTransactionCallback; |
6062 | 0 | data.mDidTransCallbackData = this; |
6063 | 0 |
|
6064 | 0 | if (!mBufferProvider) { |
6065 | 0 | // Force the creation of a buffer provider. |
6066 | 0 | EnsureTarget(); |
6067 | 0 | ReturnTarget(); |
6068 | 0 | if (!mBufferProvider) { |
6069 | 0 | MarkContextClean(); |
6070 | 0 | return false; |
6071 | 0 | } |
6072 | 0 | } |
6073 | 0 | |
6074 | 0 | if (mIsSkiaGL) { |
6075 | 0 | GLuint skiaGLTex = SkiaGLTex(); |
6076 | 0 | if (skiaGLTex) { |
6077 | 0 | SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); |
6078 | 0 | MOZ_ASSERT(glue); |
6079 | 0 | data.mGLContext = glue->GetGLContext(); |
6080 | 0 | data.mFrontbufferGLTex = skiaGLTex; |
6081 | 0 | } |
6082 | 0 | } |
6083 | 0 |
|
6084 | 0 | data.mBufferProvider = mBufferProvider; |
6085 | 0 |
|
6086 | 0 | aRenderer->Initialize(data); |
6087 | 0 | aRenderer->SetDirty(); |
6088 | 0 | return true; |
6089 | 0 | } |
6090 | | |
6091 | | void |
6092 | | CanvasRenderingContext2D::MarkContextClean() |
6093 | 0 | { |
6094 | 0 | if (mInvalidateCount > 0) { |
6095 | 0 | mPredictManyRedrawCalls = mInvalidateCount > kCanvasMaxInvalidateCount; |
6096 | 0 | } |
6097 | 0 | mIsEntireFrameInvalid = false; |
6098 | 0 | mInvalidateCount = 0; |
6099 | 0 | } |
6100 | | |
6101 | | void |
6102 | | CanvasRenderingContext2D::MarkContextCleanForFrameCapture() |
6103 | 0 | { |
6104 | 0 | mIsCapturedFrameInvalid = false; |
6105 | 0 | } |
6106 | | |
6107 | | bool |
6108 | | CanvasRenderingContext2D::IsContextCleanForFrameCapture() |
6109 | 0 | { |
6110 | 0 | return !mIsCapturedFrameInvalid; |
6111 | 0 | } |
6112 | | |
6113 | | bool |
6114 | | CanvasRenderingContext2D::ShouldForceInactiveLayer(LayerManager* aManager) |
6115 | 0 | { |
6116 | 0 | return !aManager->CanUseCanvasLayerForSize(GetSize()); |
6117 | 0 | } |
6118 | | |
6119 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPath, AddRef) |
6120 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPath, Release) |
6121 | | |
6122 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPath, mParent) |
6123 | | |
6124 | | CanvasPath::CanvasPath(nsISupports* aParent) |
6125 | | : mParent(aParent) |
6126 | 0 | { |
6127 | 0 | mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); |
6128 | 0 | } |
6129 | | |
6130 | | CanvasPath::CanvasPath(nsISupports* aParent, already_AddRefed<PathBuilder> aPathBuilder) |
6131 | | : mParent(aParent), mPathBuilder(aPathBuilder) |
6132 | 0 | { |
6133 | 0 | if (!mPathBuilder) { |
6134 | 0 | mPathBuilder = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget()->CreatePathBuilder(); |
6135 | 0 | } |
6136 | 0 | } |
6137 | | |
6138 | | JSObject* |
6139 | | CanvasPath::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
6140 | 0 | { |
6141 | 0 | return Path2D_Binding::Wrap(aCx, this, aGivenProto); |
6142 | 0 | } |
6143 | | |
6144 | | already_AddRefed<CanvasPath> |
6145 | | CanvasPath::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) |
6146 | 0 | { |
6147 | 0 | RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports()); |
6148 | 0 | return path.forget(); |
6149 | 0 | } |
6150 | | |
6151 | | already_AddRefed<CanvasPath> |
6152 | | CanvasPath::Constructor(const GlobalObject& aGlobal, CanvasPath& aCanvasPath, ErrorResult& aRv) |
6153 | 0 | { |
6154 | 0 | RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath( |
6155 | 0 | CanvasWindingRule::Nonzero, |
6156 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); |
6157 | 0 |
|
6158 | 0 | RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); |
6159 | 0 | return path.forget(); |
6160 | 0 | } |
6161 | | |
6162 | | already_AddRefed<CanvasPath> |
6163 | | CanvasPath::Constructor(const GlobalObject& aGlobal, const nsAString& aPathString, ErrorResult& aRv) |
6164 | 0 | { |
6165 | 0 | RefPtr<gfx::Path> tempPath = SVGContentUtils::GetPath(aPathString); |
6166 | 0 | if (!tempPath) { |
6167 | 0 | return Constructor(aGlobal, aRv); |
6168 | 0 | } |
6169 | 0 | |
6170 | 0 | RefPtr<CanvasPath> path = new CanvasPath(aGlobal.GetAsSupports(), tempPath->CopyToBuilder()); |
6171 | 0 | return path.forget(); |
6172 | 0 | } |
6173 | | |
6174 | | void |
6175 | | CanvasPath::ClosePath() |
6176 | 0 | { |
6177 | 0 | EnsurePathBuilder(); |
6178 | 0 |
|
6179 | 0 | mPathBuilder->Close(); |
6180 | 0 | } |
6181 | | |
6182 | | void |
6183 | | CanvasPath::MoveTo(double aX, double aY) |
6184 | 0 | { |
6185 | 0 | EnsurePathBuilder(); |
6186 | 0 |
|
6187 | 0 | mPathBuilder->MoveTo(Point(ToFloat(aX), ToFloat(aY))); |
6188 | 0 | } |
6189 | | |
6190 | | void |
6191 | | CanvasPath::LineTo(double aX, double aY) |
6192 | 0 | { |
6193 | 0 | EnsurePathBuilder(); |
6194 | 0 |
|
6195 | 0 | mPathBuilder->LineTo(Point(ToFloat(aX), ToFloat(aY))); |
6196 | 0 | } |
6197 | | |
6198 | | void |
6199 | | CanvasPath::QuadraticCurveTo(double aCpx, double aCpy, double aX, double aY) |
6200 | 0 | { |
6201 | 0 | EnsurePathBuilder(); |
6202 | 0 |
|
6203 | 0 | mPathBuilder->QuadraticBezierTo(gfx::Point(ToFloat(aCpx), ToFloat(aCpy)), |
6204 | 0 | gfx::Point(ToFloat(aX), ToFloat(aY))); |
6205 | 0 | } |
6206 | | |
6207 | | void |
6208 | | CanvasPath::BezierCurveTo(double aCp1x, double aCp1y, |
6209 | | double aCp2x, double aCp2y, |
6210 | | double aX, double aY) |
6211 | 0 | { |
6212 | 0 | BezierTo(gfx::Point(ToFloat(aCp1x), ToFloat(aCp1y)), |
6213 | 0 | gfx::Point(ToFloat(aCp2x), ToFloat(aCp2y)), |
6214 | 0 | gfx::Point(ToFloat(aX), ToFloat(aY))); |
6215 | 0 | } |
6216 | | |
6217 | | void |
6218 | | CanvasPath::ArcTo(double aX1, double aY1, double aX2, double aY2, double aRadius, |
6219 | | ErrorResult& aError) |
6220 | 0 | { |
6221 | 0 | if (aRadius < 0) { |
6222 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
6223 | 0 | return; |
6224 | 0 | } |
6225 | 0 | |
6226 | 0 | EnsurePathBuilder(); |
6227 | 0 |
|
6228 | 0 | // Current point in user space! |
6229 | 0 | Point p0 = mPathBuilder->CurrentPoint(); |
6230 | 0 | Point p1(aX1, aY1); |
6231 | 0 | Point p2(aX2, aY2); |
6232 | 0 |
|
6233 | 0 | // Execute these calculations in double precision to avoid cumulative |
6234 | 0 | // rounding errors. |
6235 | 0 | double dir, a2, b2, c2, cosx, sinx, d, anx, any, |
6236 | 0 | bnx, bny, x3, y3, x4, y4, cx, cy, angle0, angle1; |
6237 | 0 | bool anticlockwise; |
6238 | 0 |
|
6239 | 0 | if (p0 == p1 || p1 == p2 || aRadius == 0) { |
6240 | 0 | LineTo(p1.x, p1.y); |
6241 | 0 | return; |
6242 | 0 | } |
6243 | 0 | |
6244 | 0 | // Check for colinearity |
6245 | 0 | dir = (p2.x - p1.x) * (p0.y - p1.y) + (p2.y - p1.y) * (p1.x - p0.x); |
6246 | 0 | if (dir == 0) { |
6247 | 0 | LineTo(p1.x, p1.y); |
6248 | 0 | return; |
6249 | 0 | } |
6250 | 0 | |
6251 | 0 | |
6252 | 0 | // XXX - Math for this code was already available from the non-azure code |
6253 | 0 | // and would be well tested. Perhaps converting to bezier directly might |
6254 | 0 | // be more efficient longer run. |
6255 | 0 | a2 = (p0.x-aX1)*(p0.x-aX1) + (p0.y-aY1)*(p0.y-aY1); |
6256 | 0 | b2 = (aX1-aX2)*(aX1-aX2) + (aY1-aY2)*(aY1-aY2); |
6257 | 0 | c2 = (p0.x-aX2)*(p0.x-aX2) + (p0.y-aY2)*(p0.y-aY2); |
6258 | 0 | cosx = (a2+b2-c2)/(2*sqrt(a2*b2)); |
6259 | 0 |
|
6260 | 0 | sinx = sqrt(1 - cosx*cosx); |
6261 | 0 | d = aRadius / ((1 - cosx) / sinx); |
6262 | 0 |
|
6263 | 0 | anx = (aX1-p0.x) / sqrt(a2); |
6264 | 0 | any = (aY1-p0.y) / sqrt(a2); |
6265 | 0 | bnx = (aX1-aX2) / sqrt(b2); |
6266 | 0 | bny = (aY1-aY2) / sqrt(b2); |
6267 | 0 | x3 = aX1 - anx*d; |
6268 | 0 | y3 = aY1 - any*d; |
6269 | 0 | x4 = aX1 - bnx*d; |
6270 | 0 | y4 = aY1 - bny*d; |
6271 | 0 | anticlockwise = (dir < 0); |
6272 | 0 | cx = x3 + any*aRadius*(anticlockwise ? 1 : -1); |
6273 | 0 | cy = y3 - anx*aRadius*(anticlockwise ? 1 : -1); |
6274 | 0 | angle0 = atan2((y3-cy), (x3-cx)); |
6275 | 0 | angle1 = atan2((y4-cy), (x4-cx)); |
6276 | 0 |
|
6277 | 0 |
|
6278 | 0 | LineTo(x3, y3); |
6279 | 0 |
|
6280 | 0 | Arc(cx, cy, aRadius, angle0, angle1, anticlockwise, aError); |
6281 | 0 | } |
6282 | | |
6283 | | void |
6284 | | CanvasPath::Rect(double aX, double aY, double aW, double aH) |
6285 | 0 | { |
6286 | 0 | MoveTo(aX, aY); |
6287 | 0 | LineTo(aX + aW, aY); |
6288 | 0 | LineTo(aX + aW, aY + aH); |
6289 | 0 | LineTo(aX, aY + aH); |
6290 | 0 | ClosePath(); |
6291 | 0 | } |
6292 | | |
6293 | | void |
6294 | | CanvasPath::Arc(double aX, double aY, double aRadius, |
6295 | | double aStartAngle, double aEndAngle, bool aAnticlockwise, |
6296 | | ErrorResult& aError) |
6297 | 0 | { |
6298 | 0 | if (aRadius < 0.0) { |
6299 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
6300 | 0 | return; |
6301 | 0 | } |
6302 | 0 | |
6303 | 0 | EnsurePathBuilder(); |
6304 | 0 |
|
6305 | 0 | ArcToBezier(this, Point(aX, aY), Size(aRadius, aRadius), aStartAngle, aEndAngle, aAnticlockwise); |
6306 | 0 | } |
6307 | | |
6308 | | void |
6309 | | CanvasPath::Ellipse(double x, double y, double radiusX, double radiusY, |
6310 | | double rotation, double startAngle, double endAngle, |
6311 | | bool anticlockwise, ErrorResult& error) |
6312 | 0 | { |
6313 | 0 | if (radiusX < 0.0 || radiusY < 0.0) { |
6314 | 0 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
6315 | 0 | return; |
6316 | 0 | } |
6317 | 0 | |
6318 | 0 | EnsurePathBuilder(); |
6319 | 0 |
|
6320 | 0 | ArcToBezier(this, Point(x, y), Size(radiusX, radiusY), startAngle, endAngle, |
6321 | 0 | anticlockwise, rotation); |
6322 | 0 | } |
6323 | | |
6324 | | void |
6325 | | CanvasPath::LineTo(const gfx::Point& aPoint) |
6326 | 0 | { |
6327 | 0 | EnsurePathBuilder(); |
6328 | 0 |
|
6329 | 0 | mPathBuilder->LineTo(aPoint); |
6330 | 0 | } |
6331 | | |
6332 | | void |
6333 | | CanvasPath::BezierTo(const gfx::Point& aCP1, |
6334 | | const gfx::Point& aCP2, |
6335 | | const gfx::Point& aCP3) |
6336 | 0 | { |
6337 | 0 | EnsurePathBuilder(); |
6338 | 0 |
|
6339 | 0 | mPathBuilder->BezierTo(aCP1, aCP2, aCP3); |
6340 | 0 | } |
6341 | | |
6342 | | void |
6343 | | CanvasPath::AddPath(CanvasPath& aCanvasPath, const Optional<NonNull<SVGMatrix>>& aMatrix) |
6344 | 0 | { |
6345 | 0 | RefPtr<gfx::Path> tempPath = aCanvasPath.GetPath( |
6346 | 0 | CanvasWindingRule::Nonzero, |
6347 | 0 | gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get()); |
6348 | 0 |
|
6349 | 0 | if (aMatrix.WasPassed()) { |
6350 | 0 | const SVGMatrix& m = aMatrix.Value(); |
6351 | 0 | Matrix transform(m.A(), m.B(), m.C(), m.D(), m.E(), m.F()); |
6352 | 0 |
|
6353 | 0 | if (!transform.IsIdentity()) { |
6354 | 0 | RefPtr<PathBuilder> tempBuilder = tempPath->TransformedCopyToBuilder(transform, FillRule::FILL_WINDING); |
6355 | 0 | tempPath = tempBuilder->Finish(); |
6356 | 0 | } |
6357 | 0 | } |
6358 | 0 |
|
6359 | 0 | EnsurePathBuilder(); // in case a path is added to itself |
6360 | 0 | tempPath->StreamToSink(mPathBuilder); |
6361 | 0 | } |
6362 | | |
6363 | | already_AddRefed<gfx::Path> |
6364 | | CanvasPath::GetPath(const CanvasWindingRule& aWinding, const DrawTarget* aTarget) const |
6365 | 0 | { |
6366 | 0 | FillRule fillRule = FillRule::FILL_WINDING; |
6367 | 0 | if (aWinding == CanvasWindingRule::Evenodd) { |
6368 | 0 | fillRule = FillRule::FILL_EVEN_ODD; |
6369 | 0 | } |
6370 | 0 |
|
6371 | 0 | if (mPath && |
6372 | 0 | (mPath->GetBackendType() == aTarget->GetBackendType()) && |
6373 | 0 | (mPath->GetFillRule() == fillRule)) { |
6374 | 0 | RefPtr<gfx::Path> path(mPath); |
6375 | 0 | return path.forget(); |
6376 | 0 | } |
6377 | 0 | |
6378 | 0 | if (!mPath) { |
6379 | 0 | // if there is no path, there must be a pathbuilder |
6380 | 0 | MOZ_ASSERT(mPathBuilder); |
6381 | 0 | mPath = mPathBuilder->Finish(); |
6382 | 0 | if (!mPath) { |
6383 | 0 | RefPtr<gfx::Path> path(mPath); |
6384 | 0 | return path.forget(); |
6385 | 0 | } |
6386 | 0 | |
6387 | 0 | mPathBuilder = nullptr; |
6388 | 0 | } |
6389 | 0 |
|
6390 | 0 | // retarget our backend if we're used with a different backend |
6391 | 0 | if (mPath->GetBackendType() != aTarget->GetBackendType()) { |
6392 | 0 | RefPtr<PathBuilder> tmpPathBuilder = aTarget->CreatePathBuilder(fillRule); |
6393 | 0 | mPath->StreamToSink(tmpPathBuilder); |
6394 | 0 | mPath = tmpPathBuilder->Finish(); |
6395 | 0 | } else if (mPath->GetFillRule() != fillRule) { |
6396 | 0 | RefPtr<PathBuilder> tmpPathBuilder = mPath->CopyToBuilder(fillRule); |
6397 | 0 | mPath = tmpPathBuilder->Finish(); |
6398 | 0 | } |
6399 | 0 |
|
6400 | 0 | RefPtr<gfx::Path> path(mPath); |
6401 | 0 | return path.forget(); |
6402 | 0 | } |
6403 | | |
6404 | | void |
6405 | | CanvasPath::EnsurePathBuilder() const |
6406 | 0 | { |
6407 | 0 | if (mPathBuilder) { |
6408 | 0 | return; |
6409 | 0 | } |
6410 | 0 | |
6411 | 0 | // if there is not pathbuilder, there must be a path |
6412 | 0 | MOZ_ASSERT(mPath); |
6413 | 0 | mPathBuilder = mPath->CopyToBuilder(); |
6414 | 0 | mPath = nullptr; |
6415 | 0 | } |
6416 | | |
6417 | | size_t |
6418 | | BindingJSObjectMallocBytes(CanvasRenderingContext2D* aContext) |
6419 | 0 | { |
6420 | 0 | return aContext->GetWidth() * aContext->GetHeight() * 4; |
6421 | 0 | } |
6422 | | |
6423 | | } // namespace dom |
6424 | | } // namespace mozilla |