Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLCanvasElement.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  *   Vladimir Vukicevic <vladimir@pobox.com>
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #include "nsIDOMHTMLCanvasElement.h"
00039 #include "nsGenericHTMLElement.h"
00040 #include "nsPresContext.h"
00041 #include "nsIPresShell.h"
00042 #include "nsHTMLAtoms.h"
00043 #include "nsSize.h"
00044 #include "nsIFrame.h"
00045 #include "nsIDocument.h"
00046 #include "nsIDOMDocument.h"
00047 #include "nsDOMError.h"
00048 #include "nsNodeInfoManager.h"
00049 #include "plbase64.h"
00050 #include "nsNetUtil.h"
00051 #include "prmem.h"
00052 
00053 #include "nsIScriptSecurityManager.h"
00054 #include "nsIXPConnect.h"
00055 #include "jsapi.h"
00056 
00057 #include "nsICanvasElement.h"
00058 #include "nsIRenderingContext.h"
00059 
00060 #include "nsICanvasRenderingContextInternal.h"
00061 
00062 #define DEFAULT_CANVAS_WIDTH 300
00063 #define DEFAULT_CANVAS_HEIGHT 150
00064 
00065 class nsHTMLCanvasElement : public nsGenericHTMLElement,
00066                             public nsIDOMHTMLCanvasElement,
00067                             public nsICanvasElement
00068 {
00069 public:
00070   nsHTMLCanvasElement(nsINodeInfo *aNodeInfo);
00071   virtual ~nsHTMLCanvasElement();
00072 
00073   // nsISupports
00074   NS_DECL_ISUPPORTS_INHERITED
00075 
00076   // nsIDOMNode
00077   NS_FORWARD_NSIDOMNODE_NO_CLONENODE(nsGenericHTMLElement::)
00078 
00079   // nsIDOMElement
00080   NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLElement::)
00081 
00082   // nsIDOMHTMLElement
00083   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
00084 
00085   // nsIDOMHTMLCanvasElement
00086   NS_DECL_NSIDOMHTMLCANVASELEMENT
00087 
00088   // nsICanvasElement
00089   NS_IMETHOD GetPrimaryCanvasFrame(nsIFrame **aFrame);
00090   NS_IMETHOD GetSize(PRUint32 *width, PRUint32 *height);
00091   NS_IMETHOD RenderContexts(nsIRenderingContext *ctx);
00092   NS_IMETHOD RenderContextsToSurface(struct _cairo_surface *surf);
00093   virtual PRBool IsWriteOnly();
00094   virtual void SetWriteOnly();
00095 
00096   NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
00097   nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
00098   PRBool ParseAttribute(nsIAtom* aAttribute, const nsAString& aValue, nsAttrValue& aResult);
00099   nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, PRInt32 aModType) const;
00100 
00101   // SetAttr override.  C++ is stupid, so have to override both
00102   // overloaded methods.
00103   nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00104                    const nsAString& aValue, PRBool aNotify)
00105   {
00106     return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
00107   }
00108   virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00109                            nsIAtom* aPrefix, const nsAString& aValue,
00110                            PRBool aNotify);
00111 protected:
00112   nsIntSize GetWidthHeight();
00113   nsresult UpdateContext();
00114   nsresult ToDataURLImpl(const nsAString& aMimeType,
00115                          const nsAString& aEncoderOptions,
00116                          nsAString& aDataURL);
00117 
00118   nsString mCurrentContextId;
00119   nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
00120   
00121 public:
00122   // Record whether this canvas should be write-only or not.
00123   // We set this when script paints an image from a different origin.
00124   // We also transitively set it when script paints a canvas which
00125   // is itself write-only.
00126   PRPackedBool             mWriteOnly;
00127 };
00128 
00129 nsGenericHTMLElement*
00130 NS_NewHTMLCanvasElement(nsINodeInfo *aNodeInfo, PRBool aFromParser)
00131 {
00132   return new nsHTMLCanvasElement(aNodeInfo);
00133 }
00134 
00135 nsHTMLCanvasElement::nsHTMLCanvasElement(nsINodeInfo *aNodeInfo)
00136   : nsGenericHTMLElement(aNodeInfo), mWriteOnly(PR_FALSE)
00137 {
00138 }
00139 
00140 nsHTMLCanvasElement::~nsHTMLCanvasElement()
00141 {
00142   if (mCurrentContext) {
00143     nsCOMPtr<nsICanvasRenderingContextInternal> internalctx(do_QueryInterface(mCurrentContext));
00144     internalctx->SetCanvasElement(nsnull);
00145     mCurrentContext = nsnull;
00146   }
00147 }
00148 
00149 NS_IMPL_ADDREF_INHERITED(nsHTMLCanvasElement, nsGenericElement)
00150 NS_IMPL_RELEASE_INHERITED(nsHTMLCanvasElement, nsGenericElement)
00151 
00152 NS_HTML_CONTENT_INTERFACE_MAP_BEGIN(nsHTMLCanvasElement, nsGenericElement)
00153   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLCanvasElement)
00154   NS_INTERFACE_MAP_ENTRY(nsICanvasElement)
00155   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLCanvasElement)
00156 NS_HTML_CONTENT_INTERFACE_MAP_END
00157 
00158 NS_IMPL_DOM_CLONENODE(nsHTMLCanvasElement)
00159 
00160 nsIntSize
00161 nsHTMLCanvasElement::GetWidthHeight()
00162 {
00163   nsIntSize size(0,0);
00164   const nsAttrValue* value;
00165 
00166   if ((value = GetParsedAttr(nsHTMLAtoms::width)) &&
00167       value->Type() == nsAttrValue::eInteger)
00168   {
00169       size.width = value->GetIntegerValue();
00170   }
00171 
00172   if ((value = GetParsedAttr(nsHTMLAtoms::height)) &&
00173       value->Type() == nsAttrValue::eInteger)
00174   {
00175       size.height = value->GetIntegerValue();
00176   }
00177 
00178   if (size.width <= 0)
00179     size.width = DEFAULT_CANVAS_WIDTH;
00180   if (size.height <= 0)
00181     size.height = DEFAULT_CANVAS_HEIGHT;
00182 
00183   return size;
00184 }
00185 
00186 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Width, width, DEFAULT_CANVAS_WIDTH)
00187 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLCanvasElement, Height, height, DEFAULT_CANVAS_HEIGHT)
00188 
00189 nsresult
00190 nsHTMLCanvasElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
00191                              nsIAtom* aPrefix, const nsAString& aValue,
00192                              PRBool aNotify)
00193 {
00194   nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
00195                                               aNotify);
00196   if (NS_SUCCEEDED(rv) && mCurrentContext &&
00197       (aName == nsHTMLAtoms::width || aName == nsHTMLAtoms::height))
00198   {
00199     rv = UpdateContext();
00200     NS_ENSURE_SUCCESS(rv, rv);
00201   }
00202 
00203   return rv;
00204 }
00205 
00206 nsChangeHint
00207 nsHTMLCanvasElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
00208                                             PRInt32 aModType) const
00209 {
00210   nsChangeHint retval =
00211     nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
00212   if (aAttribute == nsHTMLAtoms::width ||
00213       aAttribute == nsHTMLAtoms::height)
00214   {
00215     NS_UpdateHint(retval, NS_STYLE_HINT_REFLOW);
00216   }
00217   return retval;
00218 }
00219 
00220 static void
00221 MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
00222                       nsRuleData* aData)
00223 {
00224   nsGenericHTMLElement::MapImageMarginAttributeInto(aAttributes, aData);
00225   nsGenericHTMLElement::MapCommonAttributesInto(aAttributes, aData);
00226 }
00227 
00228 nsMapRuleToAttributesFunc
00229 nsHTMLCanvasElement::GetAttributeMappingFunction() const
00230 {
00231   return &MapAttributesIntoRule;
00232 }
00233 
00234 static const nsGenericElement::MappedAttributeEntry
00235 sImageMarginAttributeMap[] = {
00236   { &nsHTMLAtoms::hspace },
00237   { &nsHTMLAtoms::vspace },
00238   { nsnull }
00239 };
00240 
00241 NS_IMETHODIMP_(PRBool)
00242 nsHTMLCanvasElement::IsAttributeMapped(const nsIAtom* aAttribute) const
00243 {
00244   static const MappedAttributeEntry* const map[] = {
00245     sCommonAttributeMap,
00246     sImageMarginAttributeMap
00247   };
00248 
00249   return FindAttributeDependence(aAttribute, map, NS_ARRAY_LENGTH(map));
00250 }
00251 
00252 PRBool
00253 nsHTMLCanvasElement::ParseAttribute(nsIAtom* aAttribute,
00254                                     const nsAString& aValue,
00255                                     nsAttrValue& aResult)
00256 {
00257   if ((aAttribute == nsHTMLAtoms::width) ||
00258       (aAttribute == nsHTMLAtoms::height))
00259   {
00260     return aResult.ParseIntWithBounds(aValue, 0);
00261   }
00262 
00263   if (ParseImageAttribute(aAttribute, aValue, aResult))
00264     return PR_TRUE;
00265 
00266   return nsGenericHTMLElement::ParseAttribute(aAttribute, aValue, aResult);
00267 }
00268 
00269 // nsHTMLCanvasElement::toDataURL
00270 
00271 NS_IMETHODIMP
00272 nsHTMLCanvasElement::ToDataURL(nsAString& aDataURL)
00273 {
00274   nsresult rv;
00275 
00276   nsCOMPtr<nsIXPCNativeCallContext> ncc;
00277   rv = nsContentUtils::XPConnect()->
00278     GetCurrentNativeCallContext(getter_AddRefs(ncc));
00279   NS_ENSURE_SUCCESS(rv, rv);
00280 
00281   if (!ncc)
00282     return NS_ERROR_FAILURE;
00283 
00284   JSContext *ctx = nsnull;
00285 
00286   rv = ncc->GetJSContext(&ctx);
00287   NS_ENSURE_SUCCESS(rv, rv);
00288 
00289   PRUint32 argc;
00290   jsval *argv = nsnull;
00291 
00292   ncc->GetArgc(&argc);
00293   ncc->GetArgvPtr(&argv);
00294 
00295   if (mWriteOnly || argc >= 2) {
00296     // do a trust check if this is a write-only canvas
00297     // or if we're trying to use the 2-arg form
00298     nsCOMPtr<nsIScriptSecurityManager> ssm =
00299         do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
00300     if (!ssm)
00301         return NS_ERROR_FAILURE;
00302 
00303     PRBool isTrusted = PR_FALSE;
00304     PRBool isChrome = PR_FALSE;
00305     PRBool hasCap = PR_FALSE;
00306 
00307     // The secman really should handle UniversalXPConnect case, since that
00308     // should include UniversalBrowserRead... doesn't right now, though.
00309     if ((NS_SUCCEEDED(ssm->SubjectPrincipalIsSystem(&isChrome)) && isChrome) ||
00310         (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalBrowserRead", &hasCap)) && hasCap) ||
00311         (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalXPConnect", &hasCap)) && hasCap))
00312     {
00313         isTrusted = PR_TRUE;
00314     }
00315 
00316     if (!isTrusted)
00317       return NS_ERROR_DOM_SECURITY_ERR;
00318   }
00319 
00320   // 0-arg case; convert to png
00321   if (argc == 0) {
00322     return ToDataURLImpl(NS_LITERAL_STRING("image/png"), EmptyString(), aDataURL);
00323   }
00324 
00325   // 1-arg case; convert to given mime type
00326   if (argc == 1) {
00327     if (!JSVAL_IS_STRING(argv[0]))
00328       return NS_ERROR_DOM_SYNTAX_ERR;
00329     JSString *type = JS_ValueToString(ctx, argv[0]);
00330     return ToDataURLImpl (nsDependentString(NS_REINTERPRET_CAST(PRUnichar*,(JS_GetStringChars(type)))),
00331                           EmptyString(), aDataURL);
00332   }
00333 
00334   // 2-arg case; trusted only (checked above), convert to mime type with params
00335   if (argc == 2) {
00336     if (!JSVAL_IS_STRING(argv[0]) && !JSVAL_IS_STRING(argv[1]))
00337       return NS_ERROR_DOM_SYNTAX_ERR;
00338 
00339     JSString *type, *params;
00340     type = JS_ValueToString(ctx, argv[0]);
00341     params = JS_ValueToString(ctx, argv[1]);
00342 
00343     return ToDataURLImpl (nsDependentString(NS_REINTERPRET_CAST(PRUnichar*,JS_GetStringChars(type))),
00344                           nsDependentString(NS_REINTERPRET_CAST(PRUnichar*,JS_GetStringChars(params))),
00345                           aDataURL);
00346   }
00347 
00348   return NS_ERROR_DOM_SYNTAX_ERR;
00349 }
00350 
00351 
00352 // nsHTMLCanvasElement::toDataURLAs
00353 //
00354 // Native-callers only
00355 
00356 NS_IMETHODIMP
00357 nsHTMLCanvasElement::ToDataURLAs(const nsAString& aMimeType,
00358                                  const nsAString& aEncoderOptions,
00359                                  nsAString& aDataURL)
00360 {
00361   return ToDataURLImpl(aMimeType, aEncoderOptions, aDataURL);
00362 }
00363 
00364 nsresult
00365 nsHTMLCanvasElement::ToDataURLImpl(const nsAString& aMimeType,
00366                                    const nsAString& aEncoderOptions,
00367                                    nsAString& aDataURL)
00368 {
00369   nsresult rv;
00370   
00371   // if there's no context, it's an error to call toDataURL.
00372   if (!mCurrentContext)
00373     return NS_ERROR_FAILURE;
00374 
00375   // get image bytes
00376   nsCOMPtr<nsIInputStream> imgStream;
00377   NS_ConvertUTF16toUTF8 aMimeType8(aMimeType);
00378   rv = mCurrentContext->GetInputStream(aMimeType8, aEncoderOptions,
00379                                        getter_AddRefs(imgStream));
00380   // XXX ERRMSG we need to report an error to developers here! (bug 329026)
00381   NS_ENSURE_SUCCESS(rv, rv);
00382 
00383   // Generally, there will be only one chunk of data, and it will be available
00384   // for us to read right away, so optimize this case.
00385   PRUint32 bufSize;
00386   rv = imgStream->Available(&bufSize);
00387   NS_ENSURE_SUCCESS(rv, rv);
00388 
00389   // ...leave a little extra room so we can call read again and make sure we
00390   // got everything. 16 bytes for better padding (maybe)
00391   bufSize += 16;
00392   PRUint32 imgSize = 0;
00393   char* imgData = (char*)PR_Malloc(bufSize);
00394   if (! imgData)
00395     return NS_ERROR_OUT_OF_MEMORY;
00396   PRUint32 numReadThisTime = 0;
00397   while ((rv = imgStream->Read(&imgData[imgSize], bufSize - imgSize,
00398                          &numReadThisTime)) == NS_OK && numReadThisTime > 0) {
00399     imgSize += numReadThisTime;
00400     if (imgSize == bufSize) {
00401       // need a bigger buffer, just double
00402       bufSize *= 2;
00403       char* newImgData = (char*)PR_Realloc(imgData, bufSize);
00404       if (! newImgData) {
00405         PR_Free(imgData);
00406         return NS_ERROR_OUT_OF_MEMORY;
00407       }
00408       imgData = newImgData;
00409     }
00410   }
00411 
00412   // base 64, result will be NULL terminated
00413   char* encodedImg = PL_Base64Encode(imgData, imgSize, nsnull);
00414   PR_Free(imgData);
00415   if (!encodedImg) // not sure why this would fail
00416     return NS_ERROR_OUT_OF_MEMORY;
00417 
00418   // build data URL string
00419   aDataURL = NS_LITERAL_STRING("data:") + aMimeType +
00420     NS_LITERAL_STRING(";base64,") + NS_ConvertUTF8toUTF16(encodedImg);
00421 
00422   PR_Free(encodedImg);
00423 
00424   return NS_OK;
00425 }
00426 
00427 NS_IMETHODIMP
00428 nsHTMLCanvasElement::GetContext(const nsAString& aContextId,
00429                                 nsISupports **aContext)
00430 {
00431   nsresult rv;
00432 
00433   if (mCurrentContextId.IsEmpty()) {
00434     nsCString ctxId;
00435     ctxId.Assign(NS_LossyConvertUTF16toASCII(aContextId));
00436 
00437     // check that ctxId is clamped to A-Za-z0-9_-
00438     for (PRUint32 i = 0; i < ctxId.Length(); i++) {
00439       if ((ctxId[i] < 'A' || ctxId[i] > 'Z') &&
00440           (ctxId[i] < 'a' || ctxId[i] > 'z') &&
00441           (ctxId[i] < '0' || ctxId[i] > '9') &&
00442           (ctxId[i] != '-') &&
00443           (ctxId[i] != '_'))
00444       {
00445         // XXX ERRMSG we need to report an error to developers here! (bug 329026)
00446         return NS_ERROR_INVALID_ARG;
00447       }
00448     }
00449 
00450     nsCString ctxString("@mozilla.org/content/canvas-rendering-context;1?id=");
00451     ctxString.Append(ctxId);
00452 
00453     mCurrentContext = do_CreateInstance(nsPromiseFlatCString(ctxString).get(), &rv);
00454     if (rv == NS_ERROR_OUT_OF_MEMORY)
00455       return NS_ERROR_OUT_OF_MEMORY;
00456     if (NS_FAILED(rv))
00457       // XXX ERRMSG we need to report an error to developers here! (bug 329026)
00458       return NS_ERROR_INVALID_ARG;
00459 
00460     rv = mCurrentContext->SetCanvasElement(this);
00461     if (NS_FAILED(rv)) {
00462       mCurrentContext = nsnull;
00463       return rv;
00464     }
00465 
00466     rv = UpdateContext();
00467     if (NS_FAILED(rv)) {
00468       mCurrentContext = nsnull;
00469       return rv;
00470     }
00471 
00472     mCurrentContextId.Assign(aContextId);
00473   } else if (!mCurrentContextId.Equals(aContextId)) {
00474     //XXX eventually allow for more than one active context on a given canvas
00475     return NS_ERROR_INVALID_ARG;
00476   }
00477 
00478   NS_ADDREF (*aContext = mCurrentContext);
00479   return NS_OK;
00480 }
00481 
00482 nsresult
00483 nsHTMLCanvasElement::UpdateContext()
00484 {
00485   nsresult rv = NS_OK;
00486   if (mCurrentContext) {
00487     nsIntSize sz = GetWidthHeight();
00488     rv = mCurrentContext->SetDimensions(sz.width, sz.height);
00489   }
00490 
00491   return rv;
00492 }
00493 
00494 NS_IMETHODIMP
00495 nsHTMLCanvasElement::GetPrimaryCanvasFrame(nsIFrame **aFrame)
00496 {
00497   *aFrame = GetPrimaryFrame(PR_TRUE);
00498   return NS_OK;
00499 }
00500 
00501 NS_IMETHODIMP
00502 nsHTMLCanvasElement::GetSize(PRUint32 *width, PRUint32 *height)
00503 {
00504   nsIntSize sz = GetWidthHeight();
00505   *width = sz.width;
00506   *height = sz.height;
00507 
00508   return NS_OK;
00509 }
00510 
00511 NS_IMETHODIMP
00512 nsHTMLCanvasElement::RenderContexts(nsIRenderingContext *rc)
00513 {
00514   if (!mCurrentContext)
00515     return NS_OK;
00516 
00517   return mCurrentContext->Render(rc);
00518 }
00519 
00520 NS_IMETHODIMP
00521 nsHTMLCanvasElement::RenderContextsToSurface(struct _cairo_surface *surf)
00522 {
00523   if (!mCurrentContext)
00524     return NS_OK;
00525 
00526   return mCurrentContext->RenderToSurface(surf);
00527 }
00528 
00529 PRBool
00530 nsHTMLCanvasElement::IsWriteOnly()
00531 {
00532   return mWriteOnly;
00533 }
00534 
00535 void
00536 nsHTMLCanvasElement::SetWriteOnly()
00537 {
00538   mWriteOnly = PR_TRUE;
00539 }