Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/canvas/CanvasRenderingContextHelper.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 "CanvasRenderingContextHelper.h"
7
#include "ImageBitmapRenderingContext.h"
8
#include "ImageEncoder.h"
9
#include "mozilla/dom/CanvasRenderingContext2D.h"
10
#include "mozilla/Telemetry.h"
11
#include "mozilla/UniquePtr.h"
12
#include "MozFramebuffer.h"
13
#include "nsContentUtils.h"
14
#include "nsDOMJSUtils.h"
15
#include "nsIScriptContext.h"
16
#include "nsJSUtils.h"
17
#include "WebGL1Context.h"
18
#include "WebGL2Context.h"
19
20
namespace mozilla {
21
namespace dom {
22
23
void
24
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
25
                                     nsIGlobalObject* aGlobal,
26
                                     BlobCallback& aCallback,
27
                                     const nsAString& aType,
28
                                     JS::Handle<JS::Value> aParams,
29
                                     bool aUsePlaceholder,
30
                                     ErrorResult& aRv)
31
0
{
32
0
  // Encoder callback when encoding is complete.
33
0
  class EncodeCallback : public EncodeCompleteCallback
34
0
  {
35
0
  public:
36
0
    EncodeCallback(nsIGlobalObject* aGlobal, BlobCallback* aCallback)
37
0
      : mGlobal(aGlobal)
38
0
      , mBlobCallback(aCallback) {}
39
0
40
0
    // This is called on main thread.
41
0
    nsresult ReceiveBlob(already_AddRefed<Blob> aBlob) override
42
0
    {
43
0
      RefPtr<Blob> blob = aBlob;
44
0
45
0
      RefPtr<Blob> newBlob = Blob::Create(mGlobal, blob->Impl());
46
0
47
0
      ErrorResult rv;
48
0
      mBlobCallback->Call(newBlob, rv);
49
0
50
0
      mGlobal = nullptr;
51
0
      mBlobCallback = nullptr;
52
0
53
0
      return rv.StealNSResult();
54
0
    }
55
0
56
0
    nsCOMPtr<nsIGlobalObject> mGlobal;
57
0
    RefPtr<BlobCallback> mBlobCallback;
58
0
  };
59
0
60
0
  RefPtr<EncodeCompleteCallback> callback =
61
0
    new EncodeCallback(aGlobal, &aCallback);
62
0
63
0
  ToBlob(aCx, aGlobal, callback, aType, aParams, aUsePlaceholder, aRv);
64
0
}
65
66
void
67
CanvasRenderingContextHelper::ToBlob(JSContext* aCx,
68
                                     nsIGlobalObject* aGlobal,
69
                                     EncodeCompleteCallback* aCallback,
70
                                     const nsAString& aType,
71
                                     JS::Handle<JS::Value> aParams,
72
                                     bool aUsePlaceholder,
73
                                     ErrorResult& aRv)
74
0
{
75
0
  nsAutoString type;
76
0
  nsContentUtils::ASCIIToLower(aType, type);
77
0
78
0
  nsAutoString params;
79
0
  bool usingCustomParseOptions;
80
0
  aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
81
0
  if (aRv.Failed()) {
82
0
    return;
83
0
  }
84
0
85
0
  if (mCurrentContext) {
86
0
    // We disallow canvases of width or height zero, and set them to 1, so
87
0
    // we will have a discrepancy with the sizes of the canvas and the context.
88
0
    // That discrepancy is OK, the rest are not.
89
0
    nsIntSize elementSize = GetWidthHeight();
90
0
    if ((elementSize.width != mCurrentContext->GetWidth() &&
91
0
         (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
92
0
        (elementSize.height != mCurrentContext->GetHeight() &&
93
0
         (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
94
0
      aRv.Throw(NS_ERROR_FAILURE);
95
0
      return;
96
0
    }
97
0
  }
98
0
99
0
  UniquePtr<uint8_t[]> imageBuffer;
100
0
  int32_t format = 0;
101
0
  if (mCurrentContext) {
102
0
    imageBuffer = mCurrentContext->GetImageBuffer(&format);
103
0
  }
104
0
105
0
  RefPtr<EncodeCompleteCallback> callback = aCallback;
106
0
107
0
  aRv = ImageEncoder::ExtractDataAsync(type,
108
0
                                       params,
109
0
                                       usingCustomParseOptions,
110
0
                                       std::move(imageBuffer),
111
0
                                       format,
112
0
                                       GetWidthHeight(),
113
0
                                       aUsePlaceholder,
114
0
                                       callback);
115
0
}
116
117
already_AddRefed<nsICanvasRenderingContextInternal>
118
CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType)
119
0
{
120
0
  return CreateContextHelper(aContextType, layers::LayersBackend::LAYERS_NONE);
121
0
}
122
123
already_AddRefed<nsICanvasRenderingContextInternal>
124
CanvasRenderingContextHelper::CreateContextHelper(CanvasContextType aContextType,
125
                                                  layers::LayersBackend aCompositorBackend)
126
0
{
127
0
  MOZ_ASSERT(aContextType != CanvasContextType::NoContext);
128
0
  RefPtr<nsICanvasRenderingContextInternal> ret;
129
0
130
0
  switch (aContextType) {
131
0
  case CanvasContextType::NoContext:
132
0
    break;
133
0
134
0
  case CanvasContextType::Canvas2D:
135
0
    Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
136
0
    ret = new CanvasRenderingContext2D(aCompositorBackend);
137
0
    break;
138
0
139
0
  case CanvasContextType::WebGL1:
140
0
    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
141
0
142
0
    ret = WebGL1Context::Create();
143
0
    if (!ret)
144
0
      return nullptr;
145
0
146
0
    break;
147
0
148
0
  case CanvasContextType::WebGL2:
149
0
    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
150
0
151
0
    ret = WebGL2Context::Create();
152
0
    if (!ret)
153
0
      return nullptr;
154
0
155
0
    break;
156
0
157
0
  case CanvasContextType::ImageBitmap:
158
0
    ret = new ImageBitmapRenderingContext();
159
0
160
0
    break;
161
0
  }
162
0
  MOZ_ASSERT(ret);
163
0
164
0
  return ret.forget();
165
0
}
166
167
already_AddRefed<nsISupports>
168
CanvasRenderingContextHelper::GetContext(JSContext* aCx,
169
                                         const nsAString& aContextId,
170
                                         JS::Handle<JS::Value> aContextOptions,
171
                                         ErrorResult& aRv)
172
0
{
173
0
  CanvasContextType contextType;
174
0
  if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType))
175
0
    return nullptr;
176
0
177
0
  if (!mCurrentContext) {
178
0
    // This canvas doesn't have a context yet.
179
0
    RefPtr<nsICanvasRenderingContextInternal> context;
180
0
    context = CreateContext(contextType);
181
0
    if (!context) {
182
0
      return nullptr;
183
0
    }
184
0
185
0
    // Ensure that the context participates in CC.  Note that returning a
186
0
    // CC participant from QI doesn't addref.
187
0
    nsXPCOMCycleCollectionParticipant* cp = nullptr;
188
0
    CallQueryInterface(context, &cp);
189
0
    if (!cp) {
190
0
      aRv.Throw(NS_ERROR_FAILURE);
191
0
      return nullptr;
192
0
    }
193
0
194
0
    mCurrentContext = context.forget();
195
0
    mCurrentContextType = contextType;
196
0
197
0
    nsresult rv = UpdateContext(aCx, aContextOptions, aRv);
198
0
    if (NS_FAILED(rv)) {
199
0
      // See bug 645792 and bug 1215072.
200
0
      // We want to throw only if dictionary initialization fails,
201
0
      // so only in case aRv has been set to some error value.
202
0
      if (contextType == CanvasContextType::WebGL1)
203
0
        Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 0);
204
0
      else if (contextType == CanvasContextType::WebGL2)
205
0
        Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 0);
206
0
      return nullptr;
207
0
    }
208
0
    if (contextType == CanvasContextType::WebGL1)
209
0
      Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_SUCCESS, 1);
210
0
    else if (contextType == CanvasContextType::WebGL2)
211
0
      Telemetry::Accumulate(Telemetry::CANVAS_WEBGL2_SUCCESS, 1);
212
0
  } else {
213
0
    // We already have a context of some type.
214
0
    if (contextType != mCurrentContextType)
215
0
      return nullptr;
216
0
  }
217
0
218
0
  nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
219
0
  return context.forget();
220
0
}
221
222
nsresult
223
CanvasRenderingContextHelper::UpdateContext(JSContext* aCx,
224
                                            JS::Handle<JS::Value> aNewContextOptions,
225
                                            ErrorResult& aRvForDictionaryInit)
226
0
{
227
0
  if (!mCurrentContext)
228
0
    return NS_OK;
229
0
230
0
  nsIntSize sz = GetWidthHeight();
231
0
232
0
  nsCOMPtr<nsICanvasRenderingContextInternal> currentContext = mCurrentContext;
233
0
234
0
  currentContext->SetOpaqueValueFromOpaqueAttr(GetOpaqueAttr());
235
0
236
0
  nsresult rv = currentContext->SetContextOptions(aCx, aNewContextOptions,
237
0
                                         aRvForDictionaryInit);
238
0
  if (NS_FAILED(rv)) {
239
0
    mCurrentContext = nullptr;
240
0
    return rv;
241
0
  }
242
0
243
0
  rv = currentContext->SetDimensions(sz.width, sz.height);
244
0
  if (NS_FAILED(rv)) {
245
0
    mCurrentContext = nullptr;
246
0
  }
247
0
248
0
  return rv;
249
0
}
250
251
nsresult
252
CanvasRenderingContextHelper::ParseParams(JSContext* aCx,
253
                                          const nsAString& aType,
254
                                          const JS::Value& aEncoderOptions,
255
                                          nsAString& outParams,
256
                                          bool* const outUsingCustomParseOptions)
257
0
{
258
0
  // Quality parameter is only valid for the image/jpeg MIME type
259
0
  if (aType.EqualsLiteral("image/jpeg")) {
260
0
    if (aEncoderOptions.isNumber()) {
261
0
      double quality = aEncoderOptions.toNumber();
262
0
      // Quality must be between 0.0 and 1.0, inclusive
263
0
      if (quality >= 0.0 && quality <= 1.0) {
264
0
        outParams.AppendLiteral("quality=");
265
0
        outParams.AppendInt(NS_lround(quality * 100.0));
266
0
      }
267
0
    }
268
0
  }
269
0
270
0
  // If we haven't parsed the aParams check for proprietary options.
271
0
  // The proprietary option -moz-parse-options will take a image lib encoder
272
0
  // parse options string as is and pass it to the encoder.
273
0
  *outUsingCustomParseOptions = false;
274
0
  if (outParams.Length() == 0 && aEncoderOptions.isString()) {
275
0
    NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:");
276
0
    nsAutoJSString paramString;
277
0
    if (!paramString.init(aCx, aEncoderOptions.toString())) {
278
0
      return NS_ERROR_FAILURE;
279
0
    }
280
0
    if (StringBeginsWith(paramString, mozParseOptions)) {
281
0
      nsDependentSubstring parseOptions = Substring(paramString,
282
0
                                                    mozParseOptions.Length(),
283
0
                                                    paramString.Length() -
284
0
                                                    mozParseOptions.Length());
285
0
      outParams.Append(parseOptions);
286
0
      *outUsingCustomParseOptions = true;
287
0
    }
288
0
  }
289
0
290
0
  return NS_OK;
291
0
}
292
293
} // namespace dom
294
} // namespace mozilla