1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Vladimir Vukicevic <vladimir@pobox.com>
19 : * Portions created by the Initial Developer are Copyright (C) 2005
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsHTMLCanvasElement.h"
39 :
40 : #include "mozilla/Base64.h"
41 : #include "nsNetUtil.h"
42 : #include "prmem.h"
43 : #include "nsDOMFile.h"
44 : #include "CheckedInt.h"
45 :
46 : #include "nsIScriptSecurityManager.h"
47 : #include "nsIXPConnect.h"
48 : #include "jsapi.h"
49 : #include "nsContentUtils.h"
50 : #include "nsJSUtils.h"
51 : #include "nsMathUtils.h"
52 : #include "nsStreamUtils.h"
53 : #include "mozilla/Preferences.h"
54 : #include "mozilla/Telemetry.h"
55 :
56 : #include "nsFrameManager.h"
57 : #include "nsDisplayList.h"
58 : #include "ImageLayers.h"
59 : #include "BasicLayers.h"
60 : #include "imgIEncoder.h"
61 :
62 : #include "nsIWritablePropertyBag2.h"
63 :
64 : #define DEFAULT_CANVAS_WIDTH 300
65 : #define DEFAULT_CANVAS_HEIGHT 150
66 :
67 : using namespace mozilla;
68 : using namespace mozilla::dom;
69 : using namespace mozilla::layers;
70 :
71 : nsGenericHTMLElement*
72 0 : NS_NewHTMLCanvasElement(already_AddRefed<nsINodeInfo> aNodeInfo,
73 : FromParser aFromParser)
74 : {
75 0 : return new nsHTMLCanvasElement(aNodeInfo);
76 : }
77 :
78 0 : nsHTMLCanvasElement::nsHTMLCanvasElement(already_AddRefed<nsINodeInfo> aNodeInfo)
79 0 : : nsGenericHTMLElement(aNodeInfo), mWriteOnly(false)
80 : {
81 0 : }
82 :
83 0 : nsHTMLCanvasElement::~nsHTMLCanvasElement()
84 : {
85 0 : }
86 :
87 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLCanvasElement)
88 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLCanvasElement,
89 : nsGenericHTMLElement)
90 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mCurrentContext)
91 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
92 :
93 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLCanvasElement,
94 : nsGenericHTMLElement)
95 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCurrentContext)
96 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
97 :
98 0 : NS_IMPL_ADDREF_INHERITED(nsHTMLCanvasElement, nsGenericElement)
99 0 : NS_IMPL_RELEASE_INHERITED(nsHTMLCanvasElement, nsGenericElement)
100 :
101 0 : DOMCI_NODE_DATA(HTMLCanvasElement, nsHTMLCanvasElement)
102 :
103 0 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLCanvasElement)
104 0 : NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLCanvasElement,
105 : nsIDOMHTMLCanvasElement,
106 : nsICanvasElementExternal)
107 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLCanvasElement,
108 : nsGenericHTMLElement)
109 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLCanvasElement)
110 :
111 0 : NS_IMPL_ELEMENT_CLONE(nsHTMLCanvasElement)
112 :
113 : nsIntSize
114 0 : nsHTMLCanvasElement::GetWidthHeight()
115 : {
116 0 : nsIntSize size(DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT);
117 : const nsAttrValue* value;
118 :
119 0 : if ((value = GetParsedAttr(nsGkAtoms::width)) &&
120 0 : value->Type() == nsAttrValue::eInteger)
121 : {
122 0 : size.width = value->GetIntegerValue();
123 : }
124 :
125 0 : if ((value = GetParsedAttr(nsGkAtoms::height)) &&
126 0 : value->Type() == nsAttrValue::eInteger)
127 : {
128 0 : size.height = value->GetIntegerValue();
129 : }
130 :
131 : return size;
132 : }
133 :
134 0 : NS_IMPL_UINT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Width, width, DEFAULT_CANVAS_WIDTH)
135 0 : NS_IMPL_UINT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Height, height, DEFAULT_CANVAS_HEIGHT)
136 0 : NS_IMPL_BOOL_ATTR(nsHTMLCanvasElement, MozOpaque, moz_opaque)
137 :
138 : nsresult
139 0 : nsHTMLCanvasElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
140 : nsIAtom* aPrefix, const nsAString& aValue,
141 : bool aNotify)
142 : {
143 : nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
144 0 : aNotify);
145 0 : if (NS_SUCCEEDED(rv) && mCurrentContext &&
146 : (aName == nsGkAtoms::width || aName == nsGkAtoms::height || aName == nsGkAtoms::moz_opaque))
147 : {
148 0 : rv = UpdateContext();
149 0 : NS_ENSURE_SUCCESS(rv, rv);
150 : }
151 :
152 0 : return rv;
153 : }
154 :
155 : nsresult
156 0 : nsHTMLCanvasElement::CopyInnerTo(nsGenericElement* aDest) const
157 : {
158 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
159 0 : NS_ENSURE_SUCCESS(rv, rv);
160 0 : if (aDest->OwnerDoc()->IsStaticDocument()) {
161 0 : nsHTMLCanvasElement* dest = static_cast<nsHTMLCanvasElement*>(aDest);
162 0 : nsCOMPtr<nsISupports> cxt;
163 0 : dest->GetContext(NS_LITERAL_STRING("2d"), JSVAL_VOID, getter_AddRefs(cxt));
164 0 : nsCOMPtr<nsIDOMCanvasRenderingContext2D> context2d = do_QueryInterface(cxt);
165 0 : if (context2d) {
166 0 : context2d->DrawImage(const_cast<nsHTMLCanvasElement*>(this),
167 0 : 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0);
168 : }
169 : }
170 0 : return rv;
171 : }
172 :
173 : nsChangeHint
174 0 : nsHTMLCanvasElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
175 : PRInt32 aModType) const
176 : {
177 : nsChangeHint retval =
178 0 : nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
179 0 : if (aAttribute == nsGkAtoms::width ||
180 : aAttribute == nsGkAtoms::height)
181 : {
182 0 : NS_UpdateHint(retval, NS_STYLE_HINT_REFLOW);
183 0 : } else if (aAttribute == nsGkAtoms::moz_opaque)
184 : {
185 0 : NS_UpdateHint(retval, NS_STYLE_HINT_VISUAL);
186 : }
187 0 : return retval;
188 : }
189 :
190 : bool
191 0 : nsHTMLCanvasElement::ParseAttribute(PRInt32 aNamespaceID,
192 : nsIAtom* aAttribute,
193 : const nsAString& aValue,
194 : nsAttrValue& aResult)
195 : {
196 0 : if (aNamespaceID == kNameSpaceID_None &&
197 : (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) {
198 0 : return aResult.ParseNonNegativeIntValue(aValue);
199 : }
200 :
201 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
202 0 : aResult);
203 : }
204 :
205 :
206 : // nsHTMLCanvasElement::toDataURL
207 :
208 : NS_IMETHODIMP
209 0 : nsHTMLCanvasElement::ToDataURL(const nsAString& aType, nsIVariant* aParams,
210 : PRUint8 optional_argc, nsAString& aDataURL)
211 : {
212 : // do a trust check if this is a write-only canvas
213 0 : if (mWriteOnly && !nsContentUtils::IsCallerTrustedForRead()) {
214 0 : return NS_ERROR_DOM_SECURITY_ERR;
215 : }
216 :
217 0 : return ToDataURLImpl(aType, aParams, aDataURL);
218 : }
219 :
220 : // nsHTMLCanvasElement::mozFetchAsStream
221 :
222 : NS_IMETHODIMP
223 0 : nsHTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback,
224 : const nsAString& aType)
225 : {
226 0 : if (!nsContentUtils::IsCallerChrome())
227 0 : return NS_ERROR_FAILURE;
228 :
229 : nsresult rv;
230 0 : bool fellBackToPNG = false;
231 0 : nsCOMPtr<nsIInputStream> inputData;
232 :
233 0 : rv = ExtractData(aType, EmptyString(), getter_AddRefs(inputData), fellBackToPNG);
234 0 : NS_ENSURE_SUCCESS(rv, rv);
235 :
236 0 : nsCOMPtr<nsIAsyncInputStream> asyncData = do_QueryInterface(inputData, &rv);
237 0 : NS_ENSURE_SUCCESS(rv, rv);
238 :
239 0 : nsCOMPtr<nsIThread> mainThread;
240 0 : rv = NS_GetMainThread(getter_AddRefs(mainThread));
241 0 : NS_ENSURE_SUCCESS(rv, rv);
242 :
243 0 : nsCOMPtr<nsIInputStreamCallback> asyncCallback;
244 0 : rv = NS_NewInputStreamReadyEvent(getter_AddRefs(asyncCallback), aCallback, mainThread);
245 0 : NS_ENSURE_SUCCESS(rv, rv);
246 :
247 0 : return asyncCallback->OnInputStreamReady(asyncData);
248 : }
249 :
250 : nsresult
251 0 : nsHTMLCanvasElement::ExtractData(const nsAString& aType,
252 : const nsAString& aOptions,
253 : nsIInputStream** aStream,
254 : bool& aFellBackToPNG)
255 : {
256 : // note that if we don't have a current context, the spec says we're
257 : // supposed to just return transparent black pixels of the canvas
258 : // dimensions.
259 0 : nsRefPtr<gfxImageSurface> emptyCanvas;
260 0 : nsIntSize size = GetWidthHeight();
261 0 : if (!mCurrentContext) {
262 0 : emptyCanvas = new gfxImageSurface(gfxIntSize(size.width, size.height), gfxASurface::ImageFormatARGB32);
263 0 : if (emptyCanvas->CairoStatus()) {
264 0 : return NS_ERROR_INVALID_ARG;
265 : }
266 : }
267 :
268 : nsresult rv;
269 :
270 : // get image bytes
271 0 : nsCOMPtr<nsIInputStream> imgStream;
272 0 : NS_ConvertUTF16toUTF8 encoderType(aType);
273 :
274 : try_again:
275 0 : if (mCurrentContext) {
276 0 : rv = mCurrentContext->GetInputStream(encoderType.get(),
277 0 : nsPromiseFlatString(aOptions).get(),
278 0 : getter_AddRefs(imgStream));
279 : } else {
280 : // no context, so we have to encode the empty image we created above
281 0 : nsCString enccid("@mozilla.org/image/encoder;2?type=");
282 0 : enccid += encoderType;
283 :
284 0 : nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get(), &rv);
285 0 : if (NS_SUCCEEDED(rv) && encoder) {
286 0 : rv = encoder->InitFromData(emptyCanvas->Data(),
287 : size.width * size.height * 4,
288 : size.width,
289 : size.height,
290 : size.width * 4,
291 : imgIEncoder::INPUT_FORMAT_HOSTARGB,
292 0 : aOptions);
293 0 : if (NS_SUCCEEDED(rv)) {
294 0 : imgStream = do_QueryInterface(encoder);
295 : }
296 : } else {
297 0 : rv = NS_ERROR_FAILURE;
298 : }
299 : }
300 :
301 0 : if (NS_FAILED(rv) && !aFellBackToPNG) {
302 : // Try image/png instead.
303 : // XXX ERRMSG we need to report an error to developers here! (bug 329026)
304 0 : aFellBackToPNG = true;
305 0 : encoderType.AssignLiteral("image/png");
306 0 : goto try_again;
307 : }
308 :
309 0 : NS_ENSURE_SUCCESS(rv, rv);
310 :
311 0 : imgStream.forget(aStream);
312 0 : return NS_OK;
313 : }
314 :
315 : nsresult
316 0 : nsHTMLCanvasElement::ToDataURLImpl(const nsAString& aMimeType,
317 : nsIVariant* aEncoderOptions,
318 : nsAString& aDataURL)
319 : {
320 0 : bool fallbackToPNG = false;
321 :
322 0 : nsIntSize size = GetWidthHeight();
323 0 : if (size.height == 0 || size.width == 0) {
324 0 : aDataURL = NS_LITERAL_STRING("data:,");
325 0 : return NS_OK;
326 : }
327 :
328 0 : nsAutoString type;
329 0 : nsresult rv = nsContentUtils::ASCIIToLower(aMimeType, type);
330 0 : if (NS_FAILED(rv)) {
331 0 : return rv;
332 : }
333 :
334 0 : nsAutoString params;
335 :
336 : // Quality parameter is only valid for the image/jpeg MIME type
337 0 : if (type.EqualsLiteral("image/jpeg")) {
338 : PRUint16 vartype;
339 :
340 0 : if (aEncoderOptions &&
341 0 : NS_SUCCEEDED(aEncoderOptions->GetDataType(&vartype)) &&
342 : vartype <= nsIDataType::VTYPE_DOUBLE) {
343 :
344 : double quality;
345 : // Quality must be between 0.0 and 1.0, inclusive
346 0 : if (NS_SUCCEEDED(aEncoderOptions->GetAsDouble(&quality)) &&
347 : quality >= 0.0 && quality <= 1.0) {
348 0 : params.AppendLiteral("quality=");
349 0 : params.AppendInt(NS_lround(quality * 100.0));
350 : }
351 : }
352 : }
353 :
354 : // If we haven't parsed the params check for proprietary options.
355 : // The proprietary option -moz-parse-options will take a image lib encoder
356 : // parse options string as is and pass it to the encoder.
357 0 : bool usingCustomParseOptions = false;
358 0 : if (params.Length() == 0) {
359 0 : NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
360 0 : nsAutoString paramString;
361 0 : if (NS_SUCCEEDED(aEncoderOptions->GetAsAString(paramString)) &&
362 0 : StringBeginsWith(paramString, mozParseOptions)) {
363 : nsDependentSubstring parseOptions = Substring(paramString,
364 : mozParseOptions.Length(),
365 0 : paramString.Length() -
366 0 : mozParseOptions.Length());
367 0 : params.Append(parseOptions);
368 0 : usingCustomParseOptions = true;
369 : }
370 : }
371 :
372 0 : nsCOMPtr<nsIInputStream> stream;
373 0 : rv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG);
374 :
375 : // If there are unrecognized custom parse options, we should fall back to
376 : // the default values for the encoder without any options at all.
377 0 : if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
378 0 : fallbackToPNG = false;
379 0 : rv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG);
380 : }
381 :
382 0 : NS_ENSURE_SUCCESS(rv, rv);
383 :
384 : // build data URL string
385 0 : if (fallbackToPNG)
386 0 : aDataURL = NS_LITERAL_STRING("data:image/png;base64,");
387 : else
388 0 : aDataURL = NS_LITERAL_STRING("data:") + type +
389 0 : NS_LITERAL_STRING(";base64,");
390 :
391 : PRUint32 count;
392 0 : rv = stream->Available(&count);
393 0 : NS_ENSURE_SUCCESS(rv, rv);
394 :
395 0 : return Base64EncodeInputStream(stream, aDataURL, count, aDataURL.Length());
396 : }
397 :
398 : NS_IMETHODIMP
399 0 : nsHTMLCanvasElement::MozGetAsFile(const nsAString& aName,
400 : const nsAString& aType,
401 : PRUint8 optional_argc,
402 : nsIDOMFile** aResult)
403 : {
404 : // do a trust check if this is a write-only canvas
405 0 : if ((mWriteOnly) &&
406 0 : !nsContentUtils::IsCallerTrustedForRead()) {
407 0 : return NS_ERROR_DOM_SECURITY_ERR;
408 : }
409 :
410 0 : return MozGetAsFileImpl(aName, aType, aResult);
411 : }
412 :
413 : nsresult
414 0 : nsHTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
415 : const nsAString& aType,
416 : nsIDOMFile** aResult)
417 : {
418 0 : bool fallbackToPNG = false;
419 :
420 0 : nsCOMPtr<nsIInputStream> stream;
421 0 : nsresult rv = ExtractData(aType, EmptyString(), getter_AddRefs(stream),
422 0 : fallbackToPNG);
423 0 : NS_ENSURE_SUCCESS(rv, rv);
424 :
425 0 : nsAutoString type(aType);
426 0 : if (fallbackToPNG) {
427 0 : type.AssignLiteral("image/png");
428 : }
429 :
430 : PRUint32 imgSize;
431 0 : rv = stream->Available(&imgSize);
432 0 : NS_ENSURE_SUCCESS(rv, rv);
433 :
434 0 : void* imgData = nsnull;
435 0 : rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
436 0 : NS_ENSURE_SUCCESS(rv, rv);
437 :
438 : // The DOMFile takes ownership of the buffer
439 : nsRefPtr<nsDOMMemoryFile> file =
440 0 : new nsDOMMemoryFile(imgData, imgSize, aName, type);
441 :
442 0 : file.forget(aResult);
443 0 : return NS_OK;
444 : }
445 :
446 : nsresult
447 0 : nsHTMLCanvasElement::GetContextHelper(const nsAString& aContextId,
448 : bool aForceThebes,
449 : nsICanvasRenderingContextInternal **aContext)
450 : {
451 0 : NS_ENSURE_ARG(aContext);
452 :
453 0 : NS_ConvertUTF16toUTF8 ctxId(aContextId);
454 :
455 : // check that ctxId is clamped to A-Za-z0-9_-
456 0 : for (PRUint32 i = 0; i < ctxId.Length(); i++) {
457 0 : if ((ctxId[i] < 'A' || ctxId[i] > 'Z') &&
458 0 : (ctxId[i] < 'a' || ctxId[i] > 'z') &&
459 0 : (ctxId[i] < '0' || ctxId[i] > '9') &&
460 0 : (ctxId[i] != '-') &&
461 0 : (ctxId[i] != '_'))
462 : {
463 : // XXX ERRMSG we need to report an error to developers here! (bug 329026)
464 0 : return NS_OK;
465 : }
466 : }
467 :
468 0 : nsCString ctxString("@mozilla.org/content/canvas-rendering-context;1?id=");
469 0 : ctxString.Append(ctxId);
470 :
471 0 : if (aForceThebes && ctxId.EqualsASCII("2d")) {
472 0 : ctxString.AssignASCII("@mozilla.org/content/2dthebes-canvas-rendering-context;1");
473 : }
474 :
475 : nsresult rv;
476 : nsCOMPtr<nsICanvasRenderingContextInternal> ctx =
477 0 : do_CreateInstance(ctxString.get(), &rv);
478 0 : if (rv == NS_ERROR_OUT_OF_MEMORY) {
479 0 : *aContext = nsnull;
480 0 : return NS_ERROR_OUT_OF_MEMORY;
481 : }
482 0 : if (NS_FAILED(rv)) {
483 0 : *aContext = nsnull;
484 : // XXX ERRMSG we need to report an error to developers here! (bug 329026)
485 0 : return NS_OK;
486 : }
487 :
488 0 : rv = ctx->SetCanvasElement(this);
489 0 : if (NS_FAILED(rv)) {
490 0 : *aContext = nsnull;
491 0 : return rv;
492 : }
493 :
494 0 : ctx.forget(aContext);
495 :
496 0 : return rv;
497 : }
498 :
499 : NS_IMETHODIMP
500 0 : nsHTMLCanvasElement::GetContext(const nsAString& aContextId,
501 : const jsval& aContextOptions,
502 : nsISupports **aContext)
503 : {
504 : nsresult rv;
505 :
506 0 : bool forceThebes = false;
507 :
508 0 : while (mCurrentContextId.IsEmpty()) {
509 0 : rv = GetContextHelper(aContextId, forceThebes, getter_AddRefs(mCurrentContext));
510 0 : NS_ENSURE_SUCCESS(rv, rv);
511 0 : if (!mCurrentContext) {
512 0 : return NS_OK;
513 : }
514 :
515 : // Ensure that the context participates in CC. Note that returning a
516 : // CC participant from QI doesn't addref.
517 0 : nsXPCOMCycleCollectionParticipant *cp = nsnull;
518 0 : CallQueryInterface(mCurrentContext, &cp);
519 0 : if (!cp) {
520 0 : mCurrentContext = nsnull;
521 0 : return NS_ERROR_FAILURE;
522 : }
523 :
524 0 : nsCOMPtr<nsIWritablePropertyBag2> contextProps;
525 0 : if (!JSVAL_IS_NULL(aContextOptions) &&
526 0 : !JSVAL_IS_VOID(aContextOptions))
527 : {
528 0 : JSContext *cx = nsContentUtils::GetCurrentJSContext();
529 :
530 : // note: if any contexts end up supporting something other
531 : // than objects, e.g. plain strings, then we'll need to expand
532 : // this to know how to create nsISupportsStrings etc.
533 0 : if (JSVAL_IS_OBJECT(aContextOptions)) {
534 0 : contextProps = do_CreateInstance("@mozilla.org/hash-property-bag;1");
535 :
536 0 : JSObject *opts = JSVAL_TO_OBJECT(aContextOptions);
537 0 : JS::AutoIdArray props(cx, JS_Enumerate(cx, opts));
538 0 : for (size_t i = 0; !!props && i < props.length(); ++i) {
539 0 : jsid propid = props[i];
540 : jsval propname, propval;
541 0 : if (!JS_IdToValue(cx, propid, &propname) ||
542 0 : !JS_GetPropertyById(cx, opts, propid, &propval)) {
543 0 : continue;
544 : }
545 :
546 0 : JSString *propnameString = JS_ValueToString(cx, propname);
547 0 : nsDependentJSString pstr;
548 0 : if (!propnameString || !pstr.init(cx, propnameString)) {
549 0 : mCurrentContext = nsnull;
550 0 : return NS_ERROR_FAILURE;
551 : }
552 :
553 0 : if (JSVAL_IS_BOOLEAN(propval)) {
554 0 : contextProps->SetPropertyAsBool(pstr, JSVAL_TO_BOOLEAN(propval));
555 0 : } else if (JSVAL_IS_INT(propval)) {
556 0 : contextProps->SetPropertyAsInt32(pstr, JSVAL_TO_INT(propval));
557 0 : } else if (JSVAL_IS_DOUBLE(propval)) {
558 0 : contextProps->SetPropertyAsDouble(pstr, JSVAL_TO_DOUBLE(propval));
559 0 : } else if (JSVAL_IS_STRING(propval)) {
560 0 : JSString *propvalString = JS_ValueToString(cx, propval);
561 0 : nsDependentJSString vstr;
562 0 : if (!propvalString || !vstr.init(cx, propvalString)) {
563 0 : mCurrentContext = nsnull;
564 0 : return NS_ERROR_FAILURE;
565 : }
566 :
567 0 : contextProps->SetPropertyAsAString(pstr, vstr);
568 : }
569 : }
570 : }
571 : }
572 :
573 0 : rv = UpdateContext(contextProps);
574 0 : if (NS_FAILED(rv)) {
575 0 : if (!forceThebes) {
576 : // Try again with a Thebes context
577 0 : forceThebes = true;
578 0 : continue;
579 : }
580 0 : return rv;
581 : }
582 :
583 0 : mCurrentContextId.Assign(aContextId);
584 0 : break;
585 : }
586 0 : if (!mCurrentContextId.Equals(aContextId)) {
587 : //XXX eventually allow for more than one active context on a given canvas
588 0 : return NS_OK;
589 : }
590 :
591 0 : NS_ADDREF (*aContext = mCurrentContext);
592 0 : return NS_OK;
593 : }
594 :
595 : NS_IMETHODIMP
596 0 : nsHTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
597 : nsISupports **aContext)
598 : {
599 0 : if(!nsContentUtils::IsCallerTrustedForRead()) {
600 : // XXX ERRMSG we need to report an error to developers here! (bug 329026)
601 0 : return NS_ERROR_DOM_SECURITY_ERR;
602 : }
603 :
604 : // We only support 2d shmem contexts for now.
605 0 : if (!aContextId.Equals(NS_LITERAL_STRING("2d")))
606 0 : return NS_ERROR_INVALID_ARG;
607 :
608 0 : if (mCurrentContextId.IsEmpty()) {
609 0 : nsresult rv = GetContextHelper(aContextId, false, getter_AddRefs(mCurrentContext));
610 0 : NS_ENSURE_SUCCESS(rv, rv);
611 0 : if (!mCurrentContext) {
612 0 : return NS_OK;
613 : }
614 :
615 0 : mCurrentContext->SetIsIPC(true);
616 :
617 0 : rv = UpdateContext();
618 0 : NS_ENSURE_SUCCESS(rv, rv);
619 :
620 0 : mCurrentContextId.Assign(aContextId);
621 0 : } else if (!mCurrentContextId.Equals(aContextId)) {
622 : //XXX eventually allow for more than one active context on a given canvas
623 0 : return NS_ERROR_INVALID_ARG;
624 : }
625 :
626 0 : NS_ADDREF (*aContext = mCurrentContext);
627 0 : return NS_OK;
628 : }
629 :
630 : nsresult
631 0 : nsHTMLCanvasElement::UpdateContext(nsIPropertyBag *aNewContextOptions)
632 : {
633 0 : if (!mCurrentContext)
634 0 : return NS_OK;
635 :
636 0 : nsIntSize sz = GetWidthHeight();
637 :
638 0 : nsresult rv = mCurrentContext->SetIsOpaque(GetIsOpaque());
639 0 : if (NS_FAILED(rv)) {
640 0 : mCurrentContext = nsnull;
641 0 : mCurrentContextId.Truncate();
642 0 : return rv;
643 : }
644 :
645 0 : rv = mCurrentContext->SetContextOptions(aNewContextOptions);
646 0 : if (NS_FAILED(rv)) {
647 0 : mCurrentContext = nsnull;
648 0 : mCurrentContextId.Truncate();
649 0 : return rv;
650 : }
651 :
652 0 : rv = mCurrentContext->SetDimensions(sz.width, sz.height);
653 0 : if (NS_FAILED(rv)) {
654 0 : mCurrentContext = nsnull;
655 0 : mCurrentContextId.Truncate();
656 0 : return rv;
657 : }
658 :
659 0 : return rv;
660 : }
661 :
662 : nsIFrame *
663 0 : nsHTMLCanvasElement::GetPrimaryCanvasFrame()
664 : {
665 0 : return GetPrimaryFrame(Flush_Frames);
666 : }
667 :
668 : nsIntSize
669 0 : nsHTMLCanvasElement::GetSize()
670 : {
671 0 : return GetWidthHeight();
672 : }
673 :
674 : bool
675 0 : nsHTMLCanvasElement::IsWriteOnly()
676 : {
677 0 : return mWriteOnly;
678 : }
679 :
680 : void
681 0 : nsHTMLCanvasElement::SetWriteOnly()
682 : {
683 0 : mWriteOnly = true;
684 0 : }
685 :
686 : void
687 0 : nsHTMLCanvasElement::InvalidateCanvasContent(const gfxRect* damageRect)
688 : {
689 : // We don't need to flush anything here; if there's no frame or if
690 : // we plan to reframe we don't need to invalidate it anyway.
691 0 : nsIFrame *frame = GetPrimaryFrame();
692 0 : if (!frame)
693 0 : return;
694 :
695 0 : frame->MarkLayersActive(nsChangeHint(0));
696 :
697 0 : nsRect invalRect;
698 0 : nsRect contentArea = frame->GetContentRect();
699 0 : if (damageRect) {
700 0 : nsIntSize size = GetWidthHeight();
701 0 : if (size.width != 0 && size.height != 0) {
702 :
703 : // damageRect and size are in CSS pixels; contentArea is in appunits
704 : // We want a rect in appunits; so avoid doing pixels-to-appunits and
705 : // vice versa conversion here.
706 0 : gfxRect realRect(*damageRect);
707 : realRect.Scale(contentArea.width / gfxFloat(size.width),
708 0 : contentArea.height / gfxFloat(size.height));
709 0 : realRect.RoundOut();
710 :
711 : // then make it a nsRect
712 0 : invalRect = nsRect(realRect.X(), realRect.Y(),
713 0 : realRect.Width(), realRect.Height());
714 :
715 0 : invalRect = invalRect.Intersect(nsRect(nsPoint(0,0), contentArea.Size()));
716 : }
717 : } else {
718 0 : invalRect = nsRect(nsPoint(0, 0), contentArea.Size());
719 : }
720 0 : invalRect.MoveBy(contentArea.TopLeft() - frame->GetPosition());
721 :
722 0 : Layer* layer = frame->InvalidateLayer(invalRect, nsDisplayItem::TYPE_CANVAS);
723 0 : if (layer) {
724 0 : static_cast<CanvasLayer*>(layer)->Updated();
725 : }
726 : }
727 :
728 : void
729 0 : nsHTMLCanvasElement::InvalidateCanvas()
730 : {
731 : // We don't need to flush anything here; if there's no frame or if
732 : // we plan to reframe we don't need to invalidate it anyway.
733 0 : nsIFrame *frame = GetPrimaryFrame();
734 0 : if (!frame)
735 0 : return;
736 :
737 0 : frame->Invalidate(frame->GetContentRect() - frame->GetPosition());
738 : }
739 :
740 : PRInt32
741 0 : nsHTMLCanvasElement::CountContexts()
742 : {
743 0 : if (mCurrentContext)
744 0 : return 1;
745 :
746 0 : return 0;
747 : }
748 :
749 : nsICanvasRenderingContextInternal *
750 0 : nsHTMLCanvasElement::GetContextAtIndex(PRInt32 index)
751 : {
752 0 : if (mCurrentContext && index == 0)
753 0 : return mCurrentContext;
754 :
755 0 : return NULL;
756 : }
757 :
758 : bool
759 0 : nsHTMLCanvasElement::GetIsOpaque()
760 : {
761 0 : return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque);
762 : }
763 :
764 : already_AddRefed<CanvasLayer>
765 0 : nsHTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
766 : CanvasLayer *aOldLayer,
767 : LayerManager *aManager)
768 : {
769 0 : if (!mCurrentContext)
770 0 : return nsnull;
771 :
772 0 : return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager);
773 : }
774 :
775 : bool
776 0 : nsHTMLCanvasElement::ShouldForceInactiveLayer(LayerManager *aManager)
777 : {
778 0 : return !mCurrentContext || mCurrentContext->ShouldForceInactiveLayer(aManager);
779 : }
780 :
781 : void
782 0 : nsHTMLCanvasElement::MarkContextClean()
783 : {
784 0 : if (!mCurrentContext)
785 0 : return;
786 :
787 0 : mCurrentContext->MarkContextClean();
788 : }
789 :
790 : NS_IMETHODIMP_(nsIntSize)
791 0 : nsHTMLCanvasElement::GetSizeExternal()
792 : {
793 0 : return GetWidthHeight();
794 : }
795 :
796 : NS_IMETHODIMP
797 0 : nsHTMLCanvasElement::RenderContextsExternal(gfxContext *aContext, gfxPattern::GraphicsFilter aFilter)
798 : {
799 0 : if (!mCurrentContext)
800 0 : return NS_OK;
801 :
802 0 : return mCurrentContext->Render(aContext, aFilter);
803 : }
804 :
805 : nsresult NS_NewCanvasRenderingContext2DThebes(nsIDOMCanvasRenderingContext2D** aResult);
806 : nsresult NS_NewCanvasRenderingContext2DAzure(nsIDOMCanvasRenderingContext2D** aResult);
807 :
808 : nsresult
809 0 : NS_NewCanvasRenderingContext2D(nsIDOMCanvasRenderingContext2D** aResult)
810 : {
811 0 : Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
812 0 : if (Preferences::GetBool("gfx.canvas.azure.enabled", false)) {
813 0 : nsresult rv = NS_NewCanvasRenderingContext2DAzure(aResult);
814 : // If Azure fails, fall back to a classic canvas.
815 0 : if (NS_SUCCEEDED(rv)) {
816 0 : return rv;
817 : }
818 : }
819 :
820 0 : return NS_NewCanvasRenderingContext2DThebes(aResult);
821 4392 : }
|