summaryrefslogtreecommitdiffstats
path: root/dom/xbl/nsXBLDocumentInfo.cpp
blob: 764c5cc05091d067998ad251fd39ba91c236c009 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/DebugOnly.h"

#include "nsXBLDocumentInfo.h"
#include "nsIDocument.h"
#include "nsXBLPrototypeBinding.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScriptContext.h"
#include "nsIDOMDocument.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "nsIURI.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
#include "nsIChromeRegistry.h"
#include "nsIPrincipal.h"
#include "nsJSPrincipals.h"
#include "nsIScriptSecurityManager.h"
#include "nsContentUtils.h"
#include "nsDOMJSUtils.h"
#include "mozilla/Services.h"
#include "xpcpublic.h"
#include "mozilla/scache/StartupCache.h"
#include "mozilla/scache/StartupCacheUtils.h"
#include "nsCCUncollectableMarker.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/URL.h"

using namespace mozilla;
using namespace mozilla::scache;
using namespace mozilla::dom;

static const char kXBLCachePrefix[] = "xblcache";

/* Implementation file */
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXBLDocumentInfo)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXBLDocumentInfo)
  if (tmp->mBindingTable) {
    for (auto iter = tmp->mBindingTable->ConstIter();
         !iter.Done(); iter.Next()) {
      iter.UserData()->Unlink();
    }
  }
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXBLDocumentInfo)
  if (tmp->mDocument &&
      nsCCUncollectableMarker::InGeneration(cb, tmp->mDocument->GetMarkedCCGeneration())) {
    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
  }
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
  if (tmp->mBindingTable) {
    for (auto iter = tmp->mBindingTable->ConstIter();
         !iter.Done(); iter.Next()) {
      iter.UserData()->Traverse(cb);
    }
  }
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXBLDocumentInfo)
  if (tmp->mBindingTable) {
    for (auto iter = tmp->mBindingTable->ConstIter();
         !iter.Done(); iter.Next()) {
      iter.UserData()->Trace(aCallbacks, aClosure);
    }
  }
NS_IMPL_CYCLE_COLLECTION_TRACE_END

static void
UnmarkXBLJSObject(JS::GCCellPtr aPtr, const char* aName, void* aClosure)
{
  JS::ExposeObjectToActiveJS(&aPtr.as<JSObject>());
}

void
nsXBLDocumentInfo::MarkInCCGeneration(uint32_t aGeneration)
{
  if (mDocument) {
    mDocument->MarkUncollectableForCCGeneration(aGeneration);
  }
  // Unmark any JS we hold
  if (mBindingTable) {
    for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
      iter.UserData()->Trace(TraceCallbackFunc(UnmarkXBLJSObject), nullptr);
    }
  }
}

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXBLDocumentInfo)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXBLDocumentInfo)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXBLDocumentInfo)

nsXBLDocumentInfo::nsXBLDocumentInfo(nsIDocument* aDocument)
  : mDocument(aDocument),
    mScriptAccess(true),
    mIsChrome(false),
    mFirstBinding(nullptr)
{
  nsIURI* uri = aDocument->GetDocumentURI();
  if (IsChromeURI(uri)) {
    // Cache whether or not this chrome XBL can execute scripts.
    nsCOMPtr<nsIXULChromeRegistry> reg =
      mozilla::services::GetXULChromeRegistryService();
    if (reg) {
      bool allow = true;
      reg->AllowScriptsForPackage(uri, &allow);
      mScriptAccess = allow;
    }
    mIsChrome = true;
  } else {
    // If this binding isn't running with system principal, then it's running
    // from a remote-XUL whitelisted domain. This is already a not-really-
    // supported configuration (among other things, we don't use XBL scopes in
    // that configuration for compatibility reasons). But we should still at
    // least make an effort to prevent binding code from running if content
    // script is disabled or if the source domain is blacklisted (since the
    // source domain for remote XBL must always be the same as the source domain
    // of the bound content).
    //
    // If we just ask the binding document if script is enabled, it will
    // discover that it has no inner window, and return false. So instead, we
    // short-circuit the normal compartment-managed script-disabling machinery,
    // and query the policy for the URI directly.
    bool allow;
    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
    nsresult rv = ssm->PolicyAllowsScript(uri, &allow);
    mScriptAccess = NS_SUCCEEDED(rv) && allow;
  }
}

nsXBLDocumentInfo::~nsXBLDocumentInfo()
{
  mozilla::DropJSObjects(this);
}

nsXBLPrototypeBinding*
nsXBLDocumentInfo::GetPrototypeBinding(const nsACString& aRef)
{
  if (!mBindingTable)
    return nullptr;

  if (aRef.IsEmpty()) {
    // Return our first binding
    return mFirstBinding;
  }

  return mBindingTable->Get(aRef);
}

nsresult
nsXBLDocumentInfo::SetPrototypeBinding(const nsACString& aRef, nsXBLPrototypeBinding* aBinding)
{
  if (!mBindingTable) {
    mBindingTable = new nsClassHashtable<nsCStringHashKey, nsXBLPrototypeBinding>();
    mozilla::HoldJSObjects(this);
  }

  NS_ENSURE_STATE(!mBindingTable->Get(aRef));
  mBindingTable->Put(aRef, aBinding);

  return NS_OK;
}

void
nsXBLDocumentInfo::RemovePrototypeBinding(const nsACString& aRef)
{
  if (mBindingTable) {
    nsAutoPtr<nsXBLPrototypeBinding> bindingToRemove;
    mBindingTable->RemoveAndForget(aRef, bindingToRemove);

    // We do not want to destroy the binding, so just forget it.
    bindingToRemove.forget();
  }
}

// static
nsresult
nsXBLDocumentInfo::ReadPrototypeBindings(nsIURI* aURI, nsXBLDocumentInfo** aDocInfo)
{
  *aDocInfo = nullptr;

  nsAutoCString spec(kXBLCachePrefix);
  nsresult rv = PathifyURI(aURI, spec);
  NS_ENSURE_SUCCESS(rv, rv);

  StartupCache* startupCache = StartupCache::GetSingleton();
  if (!startupCache) {
    return NS_ERROR_FAILURE;
  }

  UniquePtr<char[]> buf;
  uint32_t len;
  rv = startupCache->GetBuffer(spec.get(), &buf, &len);
  // GetBuffer will fail if the binding is not in the cache.
  if (NS_FAILED(rv))
    return rv;

  nsCOMPtr<nsIObjectInputStream> stream;
  rv = NewObjectInputStreamFromBuffer(Move(buf), len, getter_AddRefs(stream));
  NS_ENSURE_SUCCESS(rv, rv);

  // The file compatibility.ini stores the build id. This is checked in
  // nsAppRunner.cpp and will delete the cache if a different build is
  // present. However, we check that the version matches here to be safe. 
  uint32_t version;
  rv = stream->Read32(&version);
  NS_ENSURE_SUCCESS(rv, rv);
  if (version != XBLBinding_Serialize_Version) {
    // The version that exists is different than expected, likely created with a
    // different build, so invalidate the cache.
    startupCache->InvalidateCache();
    return NS_ERROR_NOT_AVAILABLE;
  }

  nsCOMPtr<nsIPrincipal> principal;
  nsContentUtils::GetSecurityManager()->
    GetSystemPrincipal(getter_AddRefs(principal));

  nsCOMPtr<nsIDOMDocument> domdoc;
  rv = NS_NewXBLDocument(getter_AddRefs(domdoc), aURI, nullptr, principal);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
  NS_ASSERTION(doc, "Must have a document!");
  RefPtr<nsXBLDocumentInfo> docInfo = new nsXBLDocumentInfo(doc);

  while (1) {
    uint8_t flags;
    nsresult rv = stream->Read8(&flags);
    NS_ENSURE_SUCCESS(rv, rv);
    if (flags == XBLBinding_Serialize_NoMoreBindings)
      break;

    rv = nsXBLPrototypeBinding::ReadNewBinding(stream, docInfo, doc, flags);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  docInfo.forget(aDocInfo);
  return NS_OK;
}

nsresult
nsXBLDocumentInfo::WritePrototypeBindings()
{
  // Only write out bindings with the system principal
  if (!nsContentUtils::IsSystemPrincipal(mDocument->NodePrincipal()))
    return NS_OK;

  nsAutoCString spec(kXBLCachePrefix);
  nsresult rv = PathifyURI(DocumentURI(), spec);
  NS_ENSURE_SUCCESS(rv, rv);

  StartupCache* startupCache = StartupCache::GetSingleton();
  if (!startupCache) {
    return rv;
  }

  nsCOMPtr<nsIObjectOutputStream> stream;
  nsCOMPtr<nsIStorageStream> storageStream;
  rv = NewObjectOutputWrappedStorageStream(getter_AddRefs(stream),
                                           getter_AddRefs(storageStream),
                                           true);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = stream->Write32(XBLBinding_Serialize_Version);
  NS_ENSURE_SUCCESS(rv, rv);

  if (mBindingTable) {
    for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
      iter.UserData()->Write(stream);
    }
  }

  // write a end marker at the end
  rv = stream->Write8(XBLBinding_Serialize_NoMoreBindings);
  NS_ENSURE_SUCCESS(rv, rv);

  stream->Close();
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t len;
  UniquePtr<char[]> buf;
  rv = NewBufferFromStorageStream(storageStream, &buf, &len);
  NS_ENSURE_SUCCESS(rv, rv);

  return startupCache->PutBuffer(spec.get(), buf.get(), len);
}

void
nsXBLDocumentInfo::SetFirstPrototypeBinding(nsXBLPrototypeBinding* aBinding)
{
  mFirstBinding = aBinding;
}

void
nsXBLDocumentInfo::FlushSkinStylesheets()
{
  if (mBindingTable) {
    for (auto iter = mBindingTable->Iter(); !iter.Done(); iter.Next()) {
      iter.UserData()->FlushSkinSheets();
    }
  }
}

#ifdef DEBUG
void
AssertInCompilationScope()
{
  AutoJSContext cx;
  MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
}
#endif