Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/js/xpconnect/loader/mozJSSubScriptLoader.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 "mozJSSubScriptLoader.h"
8
#include "mozJSComponentLoader.h"
9
#include "mozJSLoaderUtils.h"
10
11
#include "nsIURI.h"
12
#include "nsIIOService.h"
13
#include "nsIChannel.h"
14
#include "nsIInputStream.h"
15
#include "nsNetCID.h"
16
#include "nsNetUtil.h"
17
#include "nsIFileURL.h"
18
19
#include "jsapi.h"
20
#include "jsfriendapi.h"
21
#include "xpcprivate.h" // For xpc::OptionsBase
22
#include "js/CompilationAndEvaluation.h"
23
#include "js/SourceBufferHolder.h"
24
#include "js/Wrapper.h"
25
26
#include "mozilla/ContentPrincipal.h"
27
#include "mozilla/dom/Promise.h"
28
#include "mozilla/dom/ToJSValue.h"
29
#include "mozilla/dom/ScriptLoader.h"
30
#include "mozilla/HoldDropJSObjects.h"
31
#include "mozilla/ScriptPreloader.h"
32
#include "mozilla/SystemPrincipal.h"
33
#include "mozilla/scache/StartupCache.h"
34
#include "mozilla/scache/StartupCacheUtils.h"
35
#include "mozilla/Unused.h"
36
#include "nsContentUtils.h"
37
#include "nsString.h"
38
#include "nsCycleCollectionParticipant.h"
39
#include "GeckoProfiler.h"
40
41
using namespace mozilla::scache;
42
using namespace JS;
43
using namespace xpc;
44
using namespace mozilla;
45
using namespace mozilla::dom;
46
47
class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase {
48
public:
49
    explicit LoadSubScriptOptions(JSContext* cx = xpc_GetSafeJSContext(),
50
                                  JSObject* options = nullptr)
51
        : OptionsBase(cx, options)
52
        , target(cx)
53
        , charset(VoidString())
54
        , ignoreCache(false)
55
        , async(false)
56
        , wantReturnValue(false)
57
0
    { }
58
59
0
    virtual bool Parse() override {
60
0
      return ParseObject("target", &target) &&
61
0
             ParseString("charset", charset) &&
62
0
             ParseBoolean("ignoreCache", &ignoreCache) &&
63
0
             ParseBoolean("async", &async) &&
64
0
             ParseBoolean("wantReturnValue", &wantReturnValue);
65
0
    }
66
67
    RootedObject target;
68
    nsString charset;
69
    bool ignoreCache;
70
    bool async;
71
    bool wantReturnValue;
72
};
73
74
75
/* load() error msgs, XXX localize? */
76
#define LOAD_ERROR_NOSERVICE "Error creating IO Service."
77
#define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)"
78
0
#define LOAD_ERROR_NOSCHEME "Failed to get URI scheme.  This is bad."
79
0
#define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI."
80
0
#define LOAD_ERROR_NOSTREAM  "Error opening input stream (invalid filename?)"
81
0
#define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)"
82
0
#define LOAD_ERROR_BADCHARSET "Error converting to specified charset"
83
#define LOAD_ERROR_NOSPEC "Failed to get URI spec.  This is bad."
84
0
#define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large"
85
86
mozJSSubScriptLoader::mozJSSubScriptLoader()
87
0
{
88
0
}
89
90
mozJSSubScriptLoader::~mozJSSubScriptLoader()
91
0
{
92
0
}
93
94
NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader)
95
96
0
#define JSSUB_CACHE_PREFIX(aType) "jssubloader/" aType
97
98
static void
99
SubscriptCachePath(JSContext* cx, nsIURI* uri, JS::HandleObject targetObj, nsACString& cachePath)
100
0
{
101
0
    // StartupCache must distinguish between non-syntactic vs global when
102
0
    // computing the cache key.
103
0
    if (!JS_IsGlobalObject(targetObj)) {
104
0
        cachePath.AssignLiteral(JSSUB_CACHE_PREFIX("non-syntactic"));
105
0
    } else {
106
0
        cachePath.AssignLiteral(JSSUB_CACHE_PREFIX("global"));
107
0
    }
108
0
    PathifyURI(uri, cachePath);
109
0
}
110
111
static void
112
ReportError(JSContext* cx, const nsACString& msg)
113
0
{
114
0
    NS_ConvertUTF8toUTF16 ucMsg(msg);
115
0
116
0
    RootedValue exn(cx);
117
0
    if (xpc::NonVoidStringToJsval(cx, ucMsg, &exn)) {
118
0
        JS_SetPendingException(cx, exn);
119
0
    }
120
0
}
121
122
static void
123
ReportError(JSContext* cx, const char* origMsg, nsIURI* uri)
124
0
{
125
0
    if (!uri) {
126
0
        ReportError(cx, nsDependentCString(origMsg));
127
0
        return;
128
0
    }
129
0
130
0
    nsAutoCString spec;
131
0
    nsresult rv = uri->GetSpec(spec);
132
0
    if (NS_FAILED(rv)) {
133
0
        spec.AssignLiteral("(unknown)");
134
0
    }
135
0
136
0
    nsAutoCString msg(origMsg);
137
0
    msg.AppendLiteral(": ");
138
0
    msg.Append(spec);
139
0
    ReportError(cx, msg);
140
0
}
141
142
static bool
143
PrepareScript(nsIURI* uri,
144
              JSContext* cx,
145
              bool wantGlobalScript,
146
              const char* uriStr,
147
              const nsAString& charset,
148
              const char* buf,
149
              int64_t len,
150
              bool wantReturnValue,
151
              MutableHandleScript script)
152
0
{
153
0
    JS::CompileOptions options(cx);
154
0
    options.setFileAndLine(uriStr, 1)
155
0
           .setNoScriptRval(!wantReturnValue);
156
0
    if (!charset.IsVoid()) {
157
0
        char16_t* scriptBuf = nullptr;
158
0
        size_t scriptLength = 0;
159
0
160
0
        nsresult rv =
161
0
            ScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast<const uint8_t*>(buf), len,
162
0
                                         charset, nullptr, scriptBuf, scriptLength);
163
0
164
0
        JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength,
165
0
                                      JS::SourceBufferHolder::GiveOwnership);
166
0
167
0
        if (NS_FAILED(rv)) {
168
0
            ReportError(cx, LOAD_ERROR_BADCHARSET, uri);
169
0
            return false;
170
0
        }
171
0
172
0
        if (wantGlobalScript) {
173
0
            return JS::Compile(cx, options, srcBuf, script);
174
0
        }
175
0
        return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script);
176
0
    }
177
0
    // We only use lazy source when no special encoding is specified because
178
0
    // the lazy source loader doesn't know the encoding.
179
0
    options.setSourceIsLazy(true);
180
0
    if (wantGlobalScript) {
181
0
        return JS::CompileLatin1(cx, options, buf, len, script);
182
0
    }
183
0
    return JS::CompileLatin1ForNonSyntacticScope(cx, options, buf, len, script);
184
0
}
185
186
static bool
187
EvalScript(JSContext* cx,
188
           HandleObject targetObj,
189
           HandleObject loadScope,
190
           MutableHandleValue retval,
191
           nsIURI* uri,
192
           bool startupCache,
193
           bool preloadCache,
194
           MutableHandleScript script)
195
0
{
196
0
    MOZ_ASSERT(!js::IsWrapper(targetObj));
197
0
198
0
    if (JS_IsGlobalObject(targetObj)) {
199
0
        if (!JS::CloneAndExecuteScript(cx, script, retval)) {
200
0
            return false;
201
0
        }
202
0
    } else if (js::IsJSMEnvironment(targetObj)) {
203
0
        if (!ExecuteInJSMEnvironment(cx, script, targetObj)) {
204
0
            return false;
205
0
        }
206
0
        retval.setUndefined();
207
0
    } else {
208
0
        JS::AutoObjectVector envChain(cx);
209
0
        if (!envChain.append(targetObj)) {
210
0
            return false;
211
0
        }
212
0
        if (!loadScope) {
213
0
            // A null loadScope means we are cross-compartment. In this case, we
214
0
            // should check the target isn't in the JSM loader shared-global or
215
0
            // we will contaiminate all JSMs in the compartment.
216
0
            //
217
0
            // NOTE: If loadScope is already a shared-global JSM, we can't
218
0
            // determine which JSM the target belongs to and have to assume it
219
0
            // is in our JSM.
220
0
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
221
0
            JSObject* targetGlobal = JS::GetNonCCWObjectGlobal(targetObj);
222
0
            MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(targetGlobal),
223
0
                                  "Don't load subscript into target in a shared-global JSM");
224
0
#endif
225
0
            if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
226
0
                return false;
227
0
            }
228
0
        } else if (JS_IsGlobalObject(loadScope)) {
229
0
            if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
230
0
                return false;
231
0
            }
232
0
        } else {
233
0
            MOZ_ASSERT(js::IsJSMEnvironment(loadScope));
234
0
            if (!js::ExecuteInJSMEnvironment(cx, script, loadScope, envChain)) {
235
0
                return false;
236
0
            }
237
0
            retval.setUndefined();
238
0
        }
239
0
    }
240
0
241
0
    JSAutoRealm rar(cx, targetObj);
242
0
    if (!JS_WrapValue(cx, retval)) {
243
0
        return false;
244
0
    }
245
0
246
0
    if (script && (startupCache || preloadCache)) {
247
0
        nsAutoCString cachePath;
248
0
        SubscriptCachePath(cx, uri, targetObj, cachePath);
249
0
250
0
        nsCString uriStr;
251
0
        if (preloadCache && NS_SUCCEEDED(uri->GetSpec(uriStr))) {
252
0
            // Note that, when called during startup, this will keep the
253
0
            // original JSScript object alive for an indefinite amount of time.
254
0
            // This has the side-effect of keeping the global that the script
255
0
            // was compiled for alive, too.
256
0
            //
257
0
            // For most startups, the global in question will be the
258
0
            // CompilationScope, since we pre-compile any scripts that were
259
0
            // needed during the last startup in that scope. But for startups
260
0
            // when a non-cached script is used (e.g., after add-on
261
0
            // installation), this may be a Sandbox global, which may be
262
0
            // nuked but held alive by the JSScript. We can avoid this problem
263
0
            // by using a different scope when compiling the script. See
264
0
            // useCompilationScope in ReadScript().
265
0
            //
266
0
            // In general, this isn't a problem, since add-on Sandboxes which
267
0
            // use the script preloader are not destroyed until add-on shutdown,
268
0
            // and when add-ons are uninstalled or upgraded, the preloader cache
269
0
            // is immediately flushed after shutdown. But it's possible to
270
0
            // disable and reenable an add-on without uninstalling it, leading
271
0
            // to cached scripts being held alive, and tied to nuked Sandbox
272
0
            // globals. Given the unusual circumstances required to trigger
273
0
            // this, it's not a major concern. But it should be kept in mind.
274
0
            ScriptPreloader::GetSingleton().NoteScript(uriStr, cachePath, script);
275
0
        }
276
0
277
0
        if (startupCache) {
278
0
            JSAutoRealm ar(cx, script);
279
0
            WriteCachedScript(StartupCache::GetSingleton(), cachePath, cx, script);
280
0
        }
281
0
    }
282
0
283
0
    return true;
284
0
}
285
286
class AsyncScriptLoader : public nsIIncrementalStreamLoaderObserver
287
{
288
public:
289
    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
290
    NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
291
292
    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader)
293
294
    AsyncScriptLoader(nsIChannel* aChannel, bool aWantReturnValue,
295
                      JSObject* aTargetObj, JSObject* aLoadScope,
296
                      const nsAString& aCharset, bool aCache,
297
                      Promise* aPromise)
298
        : mChannel(aChannel)
299
        , mTargetObj(aTargetObj)
300
        , mLoadScope(aLoadScope)
301
        , mPromise(aPromise)
302
        , mCharset(aCharset)
303
        , mWantReturnValue(aWantReturnValue)
304
        , mCache(aCache)
305
0
    {
306
0
        // Needed for the cycle collector to manage mTargetObj.
307
0
        mozilla::HoldJSObjects(this);
308
0
    }
309
310
private:
311
0
    virtual ~AsyncScriptLoader() {
312
0
        mozilla::DropJSObjects(this);
313
0
    }
314
315
    RefPtr<nsIChannel> mChannel;
316
    Heap<JSObject*> mTargetObj;
317
    Heap<JSObject*> mLoadScope;
318
    RefPtr<Promise> mPromise;
319
    nsString mCharset;
320
    bool mWantReturnValue;
321
    bool mCache;
322
};
323
324
NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader)
325
326
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader)
327
0
  NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver)
328
0
NS_INTERFACE_MAP_END
329
330
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader)
331
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
332
0
  tmp->mTargetObj = nullptr;
333
0
  tmp->mLoadScope = nullptr;
334
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
335
336
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader)
337
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
338
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
339
340
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader)
341
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj)
342
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLoadScope)
343
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
344
345
NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader)
346
NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader)
347
348
class MOZ_STACK_CLASS AutoRejectPromise
349
{
350
public:
351
    AutoRejectPromise(AutoEntryScript& aAutoEntryScript,
352
                      Promise* aPromise,
353
                      nsIGlobalObject* aGlobalObject)
354
        : mAutoEntryScript(aAutoEntryScript)
355
        , mPromise(aPromise)
356
0
        , mGlobalObject(aGlobalObject) {}
357
358
0
    ~AutoRejectPromise() {
359
0
        if (mPromise) {
360
0
            JSContext* cx = mAutoEntryScript.cx();
361
0
            RootedValue rejectionValue(cx, JS::UndefinedValue());
362
0
            if (mAutoEntryScript.HasException()) {
363
0
                Unused << mAutoEntryScript.PeekException(&rejectionValue);
364
0
            }
365
0
            mPromise->MaybeReject(cx, rejectionValue);
366
0
        }
367
0
    }
368
369
0
    void ResolvePromise(HandleValue aResolveValue) {
370
0
        mPromise->MaybeResolve(aResolveValue);
371
0
        mPromise = nullptr;
372
0
    }
373
374
private:
375
    AutoEntryScript& mAutoEntryScript;
376
    RefPtr<Promise> mPromise;
377
    nsCOMPtr<nsIGlobalObject> mGlobalObject;
378
};
379
380
NS_IMETHODIMP
381
AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader,
382
                                     nsISupports* aContext,
383
                                     uint32_t aDataLength,
384
                                     const uint8_t* aData,
385
                                     uint32_t *aConsumedData)
386
0
{
387
0
    return NS_OK;
388
0
}
389
390
NS_IMETHODIMP
391
AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader,
392
                                    nsISupports* aContext,
393
                                    nsresult aStatus,
394
                                    uint32_t aLength,
395
                                    const uint8_t* aBuf)
396
0
{
397
0
    nsCOMPtr<nsIURI> uri;
398
0
    mChannel->GetURI(getter_AddRefs(uri));
399
0
400
0
    nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(mTargetObj);
401
0
    AutoEntryScript aes(globalObject, "async loadSubScript");
402
0
    AutoRejectPromise autoPromise(aes, mPromise, globalObject);
403
0
    JSContext* cx = aes.cx();
404
0
405
0
    if (NS_FAILED(aStatus)) {
406
0
        ReportError(cx, "Unable to load script.", uri);
407
0
    }
408
0
    // Just notify that we are done with this load.
409
0
    NS_ENSURE_SUCCESS(aStatus, NS_OK);
410
0
411
0
    if (aLength == 0) {
412
0
        ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
413
0
        return NS_OK;
414
0
    }
415
0
416
0
    if (aLength > INT32_MAX) {
417
0
        ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
418
0
        return NS_OK;
419
0
    }
420
0
421
0
    RootedScript script(cx);
422
0
    nsAutoCString spec;
423
0
    nsresult rv = uri->GetSpec(spec);
424
0
    NS_ENSURE_SUCCESS(rv, rv);
425
0
426
0
    RootedObject targetObj(cx, mTargetObj);
427
0
    RootedObject loadScope(cx, mLoadScope);
428
0
429
0
    if (!PrepareScript(uri, cx, JS_IsGlobalObject(targetObj), spec.get(),
430
0
                       mCharset, reinterpret_cast<const char*>(aBuf), aLength,
431
0
                       mWantReturnValue, &script))
432
0
    {
433
0
        return NS_OK;
434
0
    }
435
0
436
0
    JS::Rooted<JS::Value> retval(cx);
437
0
    if (EvalScript(cx, targetObj, loadScope, &retval, uri, mCache,
438
0
                   mCache && !mWantReturnValue,
439
0
                   &script)) {
440
0
        autoPromise.ResolvePromise(retval);
441
0
    }
442
0
443
0
    return NS_OK;
444
0
}
445
446
nsresult
447
mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri,
448
                                      HandleObject targetObj,
449
                                      HandleObject loadScope,
450
                                      const nsAString& charset,
451
                                      nsIIOService* serv,
452
                                      bool wantReturnValue,
453
                                      bool cache,
454
                                      MutableHandleValue retval)
455
0
{
456
0
    nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(targetObj);
457
0
    ErrorResult result;
458
0
459
0
    AutoJSAPI jsapi;
460
0
    if (NS_WARN_IF(!jsapi.Init(globalObject))) {
461
0
        return NS_ERROR_UNEXPECTED;
462
0
    }
463
0
464
0
    RefPtr<Promise> promise = Promise::Create(globalObject, result);
465
0
    if (result.Failed()) {
466
0
        return result.StealNSResult();
467
0
    }
468
0
469
0
    DebugOnly<bool> asJS = ToJSValue(jsapi.cx(), promise, retval);
470
0
    MOZ_ASSERT(asJS, "Should not fail to convert the promise to a JS value");
471
0
472
0
    // We create a channel and call SetContentType, to avoid expensive MIME type
473
0
    // lookups (bug 632490).
474
0
    nsCOMPtr<nsIChannel> channel;
475
0
    nsresult rv;
476
0
    rv = NS_NewChannel(getter_AddRefs(channel),
477
0
                       uri,
478
0
                       nsContentUtils::GetSystemPrincipal(),
479
0
                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
480
0
                       nsIContentPolicy::TYPE_OTHER,
481
0
                       nullptr,  // aPerformanceStorage
482
0
                       nullptr,  // aLoadGroup
483
0
                       nullptr,  // aCallbacks
484
0
                       nsIRequest::LOAD_NORMAL,
485
0
                       serv);
486
0
487
0
    if (!NS_SUCCEEDED(rv)) {
488
0
        return rv;
489
0
    }
490
0
491
0
    channel->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
492
0
493
0
    RefPtr<AsyncScriptLoader> loadObserver =
494
0
        new AsyncScriptLoader(channel,
495
0
                              wantReturnValue,
496
0
                              targetObj,
497
0
                              loadScope,
498
0
                              charset,
499
0
                              cache,
500
0
                              promise);
501
0
502
0
    nsCOMPtr<nsIIncrementalStreamLoader> loader;
503
0
    rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
504
0
    NS_ENSURE_SUCCESS(rv, rv);
505
0
506
0
    nsCOMPtr<nsIStreamListener> listener = loader.get();
507
0
    return channel->AsyncOpen2(listener);
508
0
}
509
510
bool
511
mozJSSubScriptLoader::ReadScript(nsIURI* uri,
512
                                 JSContext* cx,
513
                                 HandleObject targetObj,
514
                                 const nsAString& charset,
515
                                 const char* uriStr,
516
                                 nsIIOService* serv,
517
                                 bool wantReturnValue,
518
                                 bool useCompilationScope,
519
                                 MutableHandleScript script)
520
0
{
521
0
    script.set(nullptr);
522
0
523
0
    // We create a channel and call SetContentType, to avoid expensive MIME type
524
0
    // lookups (bug 632490).
525
0
    nsCOMPtr<nsIChannel> chan;
526
0
    nsCOMPtr<nsIInputStream> instream;
527
0
    nsresult rv;
528
0
    rv = NS_NewChannel(getter_AddRefs(chan),
529
0
                       uri,
530
0
                       nsContentUtils::GetSystemPrincipal(),
531
0
                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
532
0
                       nsIContentPolicy::TYPE_OTHER,
533
0
                       nullptr,  // PerformanceStorage
534
0
                       nullptr,  // aLoadGroup
535
0
                       nullptr,  // aCallbacks
536
0
                       nsIRequest::LOAD_NORMAL,
537
0
                       serv);
538
0
539
0
    if (NS_SUCCEEDED(rv)) {
540
0
        chan->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
541
0
        rv = chan->Open2(getter_AddRefs(instream));
542
0
    }
543
0
544
0
    if (NS_FAILED(rv)) {
545
0
        ReportError(cx, LOAD_ERROR_NOSTREAM, uri);
546
0
        return false;
547
0
    }
548
0
549
0
    int64_t len = -1;
550
0
551
0
    rv = chan->GetContentLength(&len);
552
0
    if (NS_FAILED(rv) || len == -1) {
553
0
        ReportError(cx, LOAD_ERROR_NOCONTENT, uri);
554
0
        return false;
555
0
    }
556
0
557
0
    if (len > INT32_MAX) {
558
0
        ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri);
559
0
        return false;
560
0
    }
561
0
562
0
    nsCString buf;
563
0
    rv = NS_ReadInputStreamToString(instream, buf, len);
564
0
    NS_ENSURE_SUCCESS(rv, false);
565
0
566
0
    Maybe<JSAutoRealm> ar;
567
0
568
0
    // Note that when using the ScriptPreloader cache with loadSubScript, there
569
0
    // will be a side-effect of keeping the global that the script was compiled
570
0
    // for alive. See note above in EvalScript().
571
0
    //
572
0
    // This will compile the script in XPConnect compilation scope. When the
573
0
    // script is evaluated, it will be cloned into the target scope to be
574
0
    // executed, avoiding leaks on the first session when we don't have a
575
0
    // startup cache.
576
0
    if (useCompilationScope) {
577
0
        ar.emplace(cx, xpc::CompilationScope());
578
0
    }
579
0
580
0
    return PrepareScript(uri, cx, JS_IsGlobalObject(targetObj),
581
0
                         uriStr, charset, buf.get(), len, wantReturnValue,
582
0
                         script);
583
0
}
584
585
NS_IMETHODIMP
586
mozJSSubScriptLoader::LoadSubScript(const nsAString& url,
587
                                    HandleValue target,
588
                                    const nsAString& charset,
589
                                    JSContext* cx,
590
                                    MutableHandleValue retval)
591
0
{
592
0
    /*
593
0
     * Loads a local url and evals it into the current cx
594
0
     * Synchronous (an async version would be cool too.)
595
0
     *   url: The url to load.  Must be local so that it can be loaded
596
0
     *        synchronously.
597
0
     *   targetObj: Optional object to eval the script onto (defaults to context
598
0
     *              global)
599
0
     *   charset: Optional character set to use for reading
600
0
     *   returns: Whatever jsval the script pointed to by the url returns.
601
0
     * Should ONLY (O N L Y !) be called from JavaScript code.
602
0
     */
603
0
    LoadSubScriptOptions options(cx);
604
0
    options.charset = charset;
605
0
    options.target = target.isObject() ? &target.toObject() : nullptr;
606
0
    return DoLoadSubScriptWithOptions(url, options, cx, retval);
607
0
}
608
609
610
NS_IMETHODIMP
611
mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url,
612
                                               HandleValue optionsVal,
613
                                               JSContext* cx,
614
                                               MutableHandleValue retval)
615
0
{
616
0
    if (!optionsVal.isObject()) {
617
0
        return NS_ERROR_INVALID_ARG;
618
0
    }
619
0
    LoadSubScriptOptions options(cx, &optionsVal.toObject());
620
0
    if (!options.Parse()) {
621
0
        return NS_ERROR_INVALID_ARG;
622
0
    }
623
0
    return DoLoadSubScriptWithOptions(url, options, cx, retval);
624
0
}
625
626
nsresult
627
mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
628
                                                 LoadSubScriptOptions& options,
629
                                                 JSContext* cx,
630
                                                 MutableHandleValue retval)
631
0
{
632
0
    nsresult rv = NS_OK;
633
0
    RootedObject targetObj(cx);
634
0
    RootedObject loadScope(cx);
635
0
    mozJSComponentLoader* loader = mozJSComponentLoader::Get();
636
0
    loader->FindTargetObject(cx, &loadScope);
637
0
638
0
    if (options.target) {
639
0
        targetObj = options.target;
640
0
    } else {
641
0
        targetObj = loadScope;
642
0
    }
643
0
644
0
    targetObj = JS_FindCompilationScope(cx, targetObj);
645
0
    if (!targetObj || !loadScope) {
646
0
        return NS_ERROR_FAILURE;
647
0
    }
648
0
649
0
    MOZ_ASSERT(!js::IsWrapper(targetObj),
650
0
               "JS_FindCompilationScope must unwrap");
651
0
652
0
    if (js::GetObjectCompartment(loadScope) != js::GetObjectCompartment(targetObj)) {
653
0
        loadScope = nullptr;
654
0
    }
655
0
656
0
    /* load up the url.  From here on, failures are reflected as ``custom''
657
0
     * js exceptions */
658
0
    nsCOMPtr<nsIURI> uri;
659
0
    nsAutoCString uriStr;
660
0
    nsAutoCString scheme;
661
0
662
0
    // Figure out who's calling us
663
0
    JS::AutoFilename filename;
664
0
    if (!JS::DescribeScriptedCaller(cx, &filename)) {
665
0
        // No scripted frame means we don't know who's calling, bail.
666
0
        return NS_ERROR_FAILURE;
667
0
    }
668
0
669
0
    JSAutoRealm ar(cx, targetObj);
670
0
671
0
    nsCOMPtr<nsIIOService> serv = do_GetService(NS_IOSERVICE_CONTRACTID);
672
0
    if (!serv) {
673
0
        ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSERVICE));
674
0
        return NS_OK;
675
0
    }
676
0
677
0
    NS_LossyConvertUTF16toASCII asciiUrl(url);
678
0
    AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
679
0
        "mozJSSubScriptLoader::DoLoadSubScriptWithOptions", OTHER, asciiUrl);
680
0
681
0
    // Make sure to explicitly create the URI, since we'll need the
682
0
    // canonicalized spec.
683
0
    rv = NS_NewURI(getter_AddRefs(uri), asciiUrl.get(), nullptr, serv);
684
0
    if (NS_FAILED(rv)) {
685
0
        ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOURI));
686
0
        return NS_OK;
687
0
    }
688
0
689
0
    rv = uri->GetSpec(uriStr);
690
0
    if (NS_FAILED(rv)) {
691
0
        ReportError(cx, NS_LITERAL_CSTRING(LOAD_ERROR_NOSPEC));
692
0
        return NS_OK;
693
0
    }
694
0
695
0
    rv = uri->GetScheme(scheme);
696
0
    if (NS_FAILED(rv)) {
697
0
        ReportError(cx, LOAD_ERROR_NOSCHEME, uri);
698
0
        return NS_OK;
699
0
    }
700
0
701
0
    if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app") &&
702
0
        !scheme.EqualsLiteral("blob")) {
703
0
        // This might be a URI to a local file, though!
704
0
        nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(uri);
705
0
        nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(innerURI);
706
0
        if (!fileURL) {
707
0
            ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL, uri);
708
0
            return NS_OK;
709
0
        }
710
0
711
0
        // For file URIs prepend the filename with the filename of the
712
0
        // calling script, and " -> ". See bug 418356.
713
0
        nsAutoCString tmp(filename.get());
714
0
        tmp.AppendLiteral(" -> ");
715
0
        tmp.Append(uriStr);
716
0
717
0
        uriStr = tmp;
718
0
    }
719
0
720
0
    // Suppress caching if we're compiling as content or if we're loading a
721
0
    // blob: URI.
722
0
    bool useCompilationScope = false;
723
0
    auto* principal = BasePrincipal::Cast(GetObjectPrincipal(targetObj));
724
0
    bool isSystem = principal->Is<SystemPrincipal>();
725
0
    if (!isSystem && principal->Is<ContentPrincipal>()) {
726
0
        auto* content = principal->As<ContentPrincipal>();
727
0
728
0
        nsAutoCString scheme;
729
0
        content->mCodebase->GetScheme(scheme);
730
0
731
0
        // We want to enable caching for scripts with Activity Stream's
732
0
        // codebase URLs.
733
0
        if (scheme.EqualsLiteral("about")) {
734
0
            nsAutoCString filePath;
735
0
            content->mCodebase->GetFilePath(filePath);
736
0
737
0
            useCompilationScope = filePath.EqualsLiteral("home") ||
738
0
                                  filePath.EqualsLiteral("newtab") ||
739
0
                                  filePath.EqualsLiteral("welcome");
740
0
            isSystem = true;
741
0
        }
742
0
    }
743
0
    bool ignoreCache = options.ignoreCache || !isSystem || scheme.EqualsLiteral("blob");
744
0
745
0
    StartupCache* cache = ignoreCache ? nullptr : StartupCache::GetSingleton();
746
0
747
0
    nsAutoCString cachePath;
748
0
    SubscriptCachePath(cx, uri, targetObj, cachePath);
749
0
750
0
    RootedScript script(cx);
751
0
    if (!options.ignoreCache) {
752
0
        if (!options.wantReturnValue) {
753
0
            script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
754
0
        }
755
0
        if (!script && cache) {
756
0
            rv = ReadCachedScript(cache, cachePath, cx, &script);
757
0
        }
758
0
        if (NS_FAILED(rv) || !script) {
759
0
            // ReadCachedScript may have set a pending exception.
760
0
            JS_ClearPendingException(cx);
761
0
        }
762
0
    }
763
0
764
0
    // If we are doing an async load, trigger it and bail out.
765
0
    if (!script && options.async) {
766
0
        return ReadScriptAsync(uri, targetObj, loadScope, options.charset, serv,
767
0
                               options.wantReturnValue, !!cache, retval);
768
0
    }
769
0
770
0
    if (script) {
771
0
        // |script| came from the cache, so don't bother writing it
772
0
        // |back there.
773
0
        cache = nullptr;
774
0
    } else if (!ReadScript(uri, cx, targetObj, options.charset,
775
0
                        static_cast<const char*>(uriStr.get()), serv,
776
0
                        options.wantReturnValue, useCompilationScope, &script)) {
777
0
        return NS_OK;
778
0
    }
779
0
780
0
    Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache,
781
0
                         !ignoreCache && !options.wantReturnValue,
782
0
                         &script);
783
0
    return NS_OK;
784
0
}