Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/xpconnect/loader/ChromeScriptLoader.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "PrecompiledScript.h"
8
9
#include "nsIURI.h"
10
#include "nsIChannel.h"
11
#include "nsNetUtil.h"
12
#include "nsThreadUtils.h"
13
14
#include "jsapi.h"
15
#include "jsfriendapi.h"
16
#include "js/CompilationAndEvaluation.h"
17
#include "js/SourceBufferHolder.h"
18
#include "js/Utility.h"
19
20
#include "mozilla/Attributes.h"
21
#include "mozilla/dom/ChromeUtils.h"
22
#include "mozilla/dom/Promise.h"
23
#include "mozilla/dom/ScriptLoader.h"
24
#include "mozilla/HoldDropJSObjects.h"
25
#include "mozilla/SystemGroup.h"
26
#include "nsCCUncollectableMarker.h"
27
#include "nsCycleCollectionParticipant.h"
28
29
using namespace JS;
30
using namespace mozilla;
31
using namespace mozilla::dom;
32
33
class AsyncScriptCompiler final : public nsIIncrementalStreamLoaderObserver
34
                                , public Runnable
35
{
36
public:
37
    // Note: References to this class are never held by cycle-collected objects.
38
    // If at any point a reference is returned to a caller, please update this
39
    // class to implement cycle collection.
40
    NS_DECL_ISUPPORTS_INHERITED
41
    NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
42
    NS_DECL_NSIRUNNABLE
43
44
    AsyncScriptCompiler(JSContext* aCx,
45
                        nsIGlobalObject* aGlobal,
46
                        const nsACString& aURL,
47
                        Promise* aPromise)
48
      : mozilla::Runnable("AsyncScriptCompiler")
49
      , mOptions(aCx)
50
      , mURL(aURL)
51
      , mGlobalObject(aGlobal)
52
      , mPromise(aPromise)
53
      , mToken(nullptr)
54
      , mScriptLength(0)
55
0
    {}
56
57
    MOZ_MUST_USE nsresult Start(JSContext* aCx,
58
                                const CompileScriptOptionsDictionary& aOptions,
59
                                nsIPrincipal* aPrincipal);
60
61
    inline void
62
    SetToken(JS::OffThreadToken* aToken)
63
0
    {
64
0
        mToken = aToken;
65
0
    }
66
67
protected:
68
0
    virtual ~AsyncScriptCompiler() {
69
0
        if (mPromise->State() == Promise::PromiseState::Pending) {
70
0
            mPromise->MaybeReject(NS_ERROR_FAILURE);
71
0
        }
72
0
    }
73
74
private:
75
    void Reject(JSContext* aCx);
76
    void Reject(JSContext* aCx, const char* aMxg);
77
78
    bool StartCompile(JSContext* aCx);
79
    void FinishCompile(JSContext* aCx);
80
    void Finish(JSContext* aCx, Handle<JSScript*> script);
81
82
    OwningCompileOptions        mOptions;
83
    nsCString                   mURL;
84
    nsCOMPtr<nsIGlobalObject>   mGlobalObject;
85
    RefPtr<Promise>             mPromise;
86
    nsString                    mCharset;
87
    JS::OffThreadToken*         mToken;
88
    UniqueTwoByteChars          mScriptText;
89
    size_t                      mScriptLength;
90
};
91
92
NS_IMPL_QUERY_INTERFACE_INHERITED(AsyncScriptCompiler, Runnable, nsIIncrementalStreamLoaderObserver)
93
NS_IMPL_ADDREF_INHERITED(AsyncScriptCompiler, Runnable)
94
NS_IMPL_RELEASE_INHERITED(AsyncScriptCompiler, Runnable)
95
96
nsresult
97
AsyncScriptCompiler::Start(JSContext* aCx,
98
                           const CompileScriptOptionsDictionary& aOptions,
99
                           nsIPrincipal* aPrincipal)
100
0
{
101
0
    mCharset = aOptions.mCharset;
102
0
103
0
    mOptions.setNoScriptRval(!aOptions.mHasReturnValue)
104
0
            .setCanLazilyParse(aOptions.mLazilyParse);
105
0
106
0
    if (NS_WARN_IF(!mOptions.setFile(aCx, mURL.get()))) {
107
0
        return NS_ERROR_OUT_OF_MEMORY;
108
0
    }
109
0
110
0
    nsCOMPtr<nsIURI> uri;
111
0
    nsresult rv = NS_NewURI(getter_AddRefs(uri), mURL);
112
0
    NS_ENSURE_SUCCESS(rv, rv);
113
0
114
0
    nsCOMPtr<nsIChannel> channel;
115
0
    rv = NS_NewChannel(getter_AddRefs(channel),
116
0
                       uri, aPrincipal,
117
0
                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
118
0
                       nsIContentPolicy::TYPE_OTHER);
119
0
    NS_ENSURE_SUCCESS(rv, rv);
120
0
121
0
    nsCOMPtr<nsIIncrementalStreamLoader> loader;
122
0
    rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), this);
123
0
    NS_ENSURE_SUCCESS(rv, rv);
124
0
125
0
    return channel->AsyncOpen2(loader);
126
0
}
127
128
static void
129
OffThreadScriptLoaderCallback(JS::OffThreadToken* aToken, void* aCallbackData)
130
0
{
131
0
    RefPtr<AsyncScriptCompiler> scriptCompiler = dont_AddRef(
132
0
        static_cast<AsyncScriptCompiler*>(aCallbackData));
133
0
134
0
    scriptCompiler->SetToken(aToken);
135
0
136
0
    SystemGroup::Dispatch(TaskCategory::Other, scriptCompiler.forget());
137
0
}
138
139
bool
140
AsyncScriptCompiler::StartCompile(JSContext* aCx)
141
0
{
142
0
    Rooted<JSObject*> global(aCx, mGlobalObject->GetGlobalJSObject());
143
0
144
0
    JS::SourceBufferHolder srcBuf(std::move(mScriptText), mScriptLength);
145
0
    if (JS::CanCompileOffThread(aCx, mOptions, mScriptLength)) {
146
0
        if (!JS::CompileOffThread(aCx, mOptions, srcBuf,
147
0
                                  OffThreadScriptLoaderCallback,
148
0
                                  static_cast<void*>(this))) {
149
0
            return false;
150
0
        }
151
0
152
0
        NS_ADDREF(this);
153
0
        return true;
154
0
    }
155
0
156
0
    Rooted<JSScript*> script(aCx);
157
0
    if (!JS::Compile(aCx, mOptions, srcBuf, &script)) {
158
0
        return false;
159
0
    }
160
0
161
0
    Finish(aCx, script);
162
0
    return true;
163
0
}
164
165
NS_IMETHODIMP
166
AsyncScriptCompiler::Run()
167
0
{
168
0
    AutoJSAPI jsapi;
169
0
    if (jsapi.Init(mGlobalObject)) {
170
0
        FinishCompile(jsapi.cx());
171
0
    } else {
172
0
        jsapi.Init();
173
0
        JS::CancelOffThreadScript(jsapi.cx(), mToken);
174
0
175
0
        mPromise->MaybeReject(NS_ERROR_FAILURE);
176
0
    }
177
0
178
0
    return NS_OK;
179
0
}
180
181
void
182
AsyncScriptCompiler::FinishCompile(JSContext* aCx)
183
0
{
184
0
    Rooted<JSScript*> script(aCx, JS::FinishOffThreadScript(aCx, mToken));
185
0
    if (script) {
186
0
        Finish(aCx, script);
187
0
    } else {
188
0
        Reject(aCx);
189
0
    }
190
0
}
191
192
193
void
194
AsyncScriptCompiler::Finish(JSContext* aCx, Handle<JSScript*> aScript)
195
0
{
196
0
    RefPtr<PrecompiledScript> result = new PrecompiledScript(mGlobalObject, aScript, mOptions);
197
0
198
0
    mPromise->MaybeResolve(result);
199
0
}
200
201
void
202
AsyncScriptCompiler::Reject(JSContext* aCx)
203
0
{
204
0
    RootedValue value(aCx, JS::UndefinedValue());
205
0
    if (JS_GetPendingException(aCx, &value)) {
206
0
        JS_ClearPendingException(aCx);
207
0
    }
208
0
    mPromise->MaybeReject(aCx, value);
209
0
}
210
211
void
212
AsyncScriptCompiler::Reject(JSContext* aCx, const char* aMsg)
213
0
{
214
0
    nsAutoString msg;
215
0
    msg.AppendASCII(aMsg);
216
0
    msg.AppendLiteral(": ");
217
0
    AppendUTF8toUTF16(mURL, msg);
218
0
219
0
    RootedValue exn(aCx);
220
0
    if (xpc::NonVoidStringToJsval(aCx, msg, &exn)) {
221
0
        JS_SetPendingException(aCx, exn);
222
0
    }
223
0
224
0
    Reject(aCx);
225
0
}
226
227
NS_IMETHODIMP
228
AsyncScriptCompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
229
                                       nsISupports* aContext,
230
                                       uint32_t aDataLength,
231
                                       const uint8_t* aData,
232
                                       uint32_t *aConsumedData)
233
0
{
234
0
    return NS_OK;
235
0
}
236
237
NS_IMETHODIMP
238
AsyncScriptCompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
239
                                      nsISupports* aContext,
240
                                      nsresult aStatus,
241
                                      uint32_t aLength,
242
                                      const uint8_t* aBuf)
243
0
{
244
0
    AutoJSAPI jsapi;
245
0
    if (!jsapi.Init(mGlobalObject)) {
246
0
        mPromise->MaybeReject(NS_ERROR_FAILURE);
247
0
        return NS_OK;
248
0
    }
249
0
250
0
    JSContext* cx = jsapi.cx();
251
0
252
0
    if (NS_FAILED(aStatus)) {
253
0
        Reject(cx, "Unable to load script");
254
0
        return NS_OK;
255
0
    }
256
0
257
0
    nsresult rv = ScriptLoader::ConvertToUTF16(
258
0
        nullptr, aBuf, aLength, mCharset, nullptr, mScriptText, mScriptLength);
259
0
    if (NS_FAILED(rv)) {
260
0
        Reject(cx, "Unable to decode script");
261
0
        return NS_OK;
262
0
    }
263
0
264
0
    if (!StartCompile(cx)) {
265
0
        Reject(cx);
266
0
    }
267
0
268
0
    return NS_OK;
269
0
}
270
271
272
namespace mozilla {
273
namespace dom {
274
275
/* static */ already_AddRefed<Promise>
276
ChromeUtils::CompileScript(GlobalObject& aGlobal,
277
                           const nsAString& aURL,
278
                           const CompileScriptOptionsDictionary& aOptions,
279
                           ErrorResult& aRv)
280
0
{
281
0
    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
282
0
    MOZ_ASSERT(global);
283
0
284
0
    RefPtr<Promise> promise = Promise::Create(global, aRv);
285
0
    if (aRv.Failed()) {
286
0
        return nullptr;
287
0
    }
288
0
289
0
    NS_ConvertUTF16toUTF8 url(aURL);
290
0
    RefPtr<AsyncScriptCompiler> compiler = new AsyncScriptCompiler(aGlobal.Context(), global, url, promise);
291
0
292
0
    nsresult rv = compiler->Start(aGlobal.Context(), aOptions, aGlobal.GetSubjectPrincipal());
293
0
    if (NS_FAILED(rv)) {
294
0
        promise->MaybeReject(rv);
295
0
    }
296
0
297
0
    return promise.forget();
298
0
}
299
300
PrecompiledScript::PrecompiledScript(nsISupports* aParent, Handle<JSScript*> aScript,
301
                                     JS::ReadOnlyCompileOptions& aOptions)
302
    : mParent(aParent)
303
    , mScript(aScript)
304
    , mURL(aOptions.filename())
305
    , mHasReturnValue(!aOptions.noScriptRval)
306
0
{
307
0
    MOZ_ASSERT(aParent);
308
0
    MOZ_ASSERT(aScript);
309
0
310
0
    mozilla::HoldJSObjects(this);
311
0
};
312
313
PrecompiledScript::~PrecompiledScript()
314
0
{
315
0
    mozilla::DropJSObjects(this);
316
0
}
317
318
void
319
PrecompiledScript::ExecuteInGlobal(JSContext* aCx, HandleObject aGlobal,
320
                                   MutableHandleValue aRval,
321
                                   ErrorResult& aRv)
322
0
{
323
0
    {
324
0
        RootedObject targetObj(aCx, JS_FindCompilationScope(aCx, aGlobal));
325
0
        JSAutoRealm ar(aCx, targetObj);
326
0
327
0
        Rooted<JSScript*> script(aCx, mScript);
328
0
        if (!JS::CloneAndExecuteScript(aCx, script, aRval)) {
329
0
            aRv.NoteJSContextException(aCx);
330
0
            return;
331
0
        }
332
0
    }
333
0
334
0
    JS_WrapValue(aCx, aRval);
335
0
}
336
337
void
338
PrecompiledScript::GetUrl(nsAString& aUrl)
339
0
{
340
0
    CopyUTF8toUTF16(mURL, aUrl);
341
0
}
342
343
bool
344
PrecompiledScript::HasReturnValue()
345
0
{
346
0
    return mHasReturnValue;
347
0
}
348
349
JSObject*
350
PrecompiledScript::WrapObject(JSContext* aCx, HandleObject aGivenProto)
351
0
{
352
0
    return PrecompiledScript_Binding::Wrap(aCx, this, aGivenProto);
353
0
}
354
355
bool
356
PrecompiledScript::IsBlackForCC(bool aTracingNeeded)
357
0
{
358
0
    return (nsCCUncollectableMarker::sGeneration &&
359
0
            HasKnownLiveWrapper() &&
360
0
            (!aTracingNeeded || HasNothingToTrace(this)));
361
0
}
362
363
NS_IMPL_CYCLE_COLLECTION_CLASS(PrecompiledScript)
364
365
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PrecompiledScript)
366
0
    NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
367
0
    NS_INTERFACE_MAP_ENTRY(nsISupports)
368
0
NS_INTERFACE_MAP_END
369
370
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PrecompiledScript)
371
0
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
372
0
    NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
373
0
374
0
    tmp->mScript = nullptr;
375
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
376
377
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PrecompiledScript)
378
0
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
379
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
380
381
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PrecompiledScript)
382
0
    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScript)
383
0
    NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
384
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
385
386
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(PrecompiledScript)
387
0
    if (tmp->IsBlackForCC(false)) {
388
0
        tmp->mScript.exposeToActiveJS();
389
0
        return true;
390
0
    }
391
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
392
0
393
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(PrecompiledScript)
394
0
    return tmp->IsBlackForCC(true);
395
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
396
397
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(PrecompiledScript)
398
0
    return tmp->IsBlackForCC(false);
399
0
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
400
401
NS_IMPL_CYCLE_COLLECTING_ADDREF(PrecompiledScript)
402
NS_IMPL_CYCLE_COLLECTING_RELEASE(PrecompiledScript)
403
404
} // namespace dom
405
} // namespace mozilla