1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "AntiTrackingLog.h"
8 #include "ContentBlockingNotifier.h"
9 #include "AntiTrackingUtils.h"
10
11 #include "mozilla/EventQueue.h"
12 #include "mozilla/StaticPrefs_privacy.h"
13 #include "mozilla/dom/BrowserChild.h"
14 #include "mozilla/dom/BrowsingContext.h"
15 #include "mozilla/dom/WindowGlobalParent.h"
16 #include "nsIClassifiedChannel.h"
17 #include "nsIRunnable.h"
18 #include "nsIScriptError.h"
19 #include "nsIURI.h"
20 #include "nsIOService.h"
21 #include "nsGlobalWindowInner.h"
22 #include "nsJSUtils.h"
23 #include "mozIThirdPartyUtil.h"
24
25 using namespace mozilla;
26 using mozilla::dom::BrowsingContext;
27 using mozilla::dom::ContentChild;
28 using mozilla::dom::Document;
29
30 static const uint32_t kMaxConsoleOutputDelayMs = 100;
31
32 namespace {
33
RunConsoleReportingRunnable(already_AddRefed<nsIRunnable> && aRunnable)34 void RunConsoleReportingRunnable(already_AddRefed<nsIRunnable>&& aRunnable) {
35 if (StaticPrefs::privacy_restrict3rdpartystorage_console_lazy()) {
36 nsresult rv = NS_DispatchToCurrentThreadQueue(std::move(aRunnable),
37 kMaxConsoleOutputDelayMs,
38 EventQueuePriority::Idle);
39 if (NS_WARN_IF(NS_FAILED(rv))) {
40 return;
41 }
42 } else {
43 nsCOMPtr<nsIRunnable> runnable(std::move(aRunnable));
44 nsresult rv = runnable->Run();
45 if (NS_WARN_IF(NS_FAILED(rv))) {
46 return;
47 }
48 }
49 }
50
ReportUnblockingToConsole(uint64_t aWindowID,nsIPrincipal * aPrincipal,const nsAString & aTrackingOrigin,ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason)51 void ReportUnblockingToConsole(
52 uint64_t aWindowID, nsIPrincipal* aPrincipal,
53 const nsAString& aTrackingOrigin,
54 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason) {
55 MOZ_ASSERT(aWindowID);
56 MOZ_ASSERT(aPrincipal);
57
58 nsAutoString sourceLine;
59 uint32_t lineNumber = 0, columnNumber = 0;
60 JSContext* cx = nsContentUtils::GetCurrentJSContext();
61 if (cx) {
62 nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
63 }
64
65 nsCOMPtr<nsIPrincipal> principal(aPrincipal);
66 nsAutoString trackingOrigin(aTrackingOrigin);
67
68 RefPtr<Runnable> runnable = NS_NewRunnableFunction(
69 "ReportUnblockingToConsoleDelayed",
70 [aWindowID, sourceLine, lineNumber, columnNumber, principal,
71 trackingOrigin, aReason]() {
72 const char* messageWithSameOrigin = nullptr;
73
74 switch (aReason) {
75 case ContentBlockingNotifier::eStorageAccessAPI:
76 case ContentBlockingNotifier::ePrivilegeStorageAccessForOriginAPI:
77 messageWithSameOrigin = "CookieAllowedForOriginByStorageAccessAPI";
78 break;
79
80 case ContentBlockingNotifier::eOpenerAfterUserInteraction:
81 [[fallthrough]];
82 case ContentBlockingNotifier::eOpener:
83 messageWithSameOrigin = "CookieAllowedForOriginByHeuristic";
84 break;
85 }
86
87 nsAutoString origin;
88 nsresult rv = nsContentUtils::GetUTFOrigin(principal, origin);
89 if (NS_WARN_IF(NS_FAILED(rv))) {
90 return;
91 }
92
93 // Not adding grantedOrigin yet because we may not want it later.
94 AutoTArray<nsString, 2> params = {origin, trackingOrigin};
95
96 nsAutoString errorText;
97 rv = nsContentUtils::FormatLocalizedString(
98 nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin, params,
99 errorText);
100 NS_ENSURE_SUCCESS_VOID(rv);
101
102 nsContentUtils::ReportToConsoleByWindowID(
103 errorText, nsIScriptError::warningFlag,
104 ANTITRACKING_CONSOLE_CATEGORY, aWindowID, nullptr, sourceLine,
105 lineNumber, columnNumber);
106 });
107
108 RunConsoleReportingRunnable(runnable.forget());
109 }
110
ReportBlockingToConsole(uint64_t aWindowID,nsIURI * aURI,uint32_t aRejectedReason)111 void ReportBlockingToConsole(uint64_t aWindowID, nsIURI* aURI,
112 uint32_t aRejectedReason) {
113 MOZ_ASSERT(aWindowID);
114 MOZ_ASSERT(aURI);
115 MOZ_ASSERT(
116 aRejectedReason == 0 ||
117 aRejectedReason ==
118 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
119 aRejectedReason ==
120 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
121 aRejectedReason ==
122 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
123 aRejectedReason ==
124 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
125 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
126 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
127
128 if (aURI->SchemeIs("chrome") || aURI->SchemeIs("about")) {
129 return;
130 }
131
132 nsAutoString sourceLine;
133 uint32_t lineNumber = 0, columnNumber = 0;
134 JSContext* cx = nsContentUtils::GetCurrentJSContext();
135 if (cx) {
136 nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
137 }
138
139 nsCOMPtr<nsIURI> uri(aURI);
140
141 RefPtr<Runnable> runnable = NS_NewRunnableFunction(
142 "ReportBlockingToConsoleDelayed", [aWindowID, sourceLine, lineNumber,
143 columnNumber, uri, aRejectedReason]() {
144 const char* message = nullptr;
145 nsAutoCString category;
146 // When changing this list, please make sure to update the corresponding
147 // code in antitracking_head.js (inside _createTask).
148 // XXX: The nsIWebProgressListener constants below are interpreted as
149 // signed integers on Windows and the compiler complains that they can't
150 // be narrowed to uint32_t. To prevent this, we cast them to uint32_t.
151 switch (aRejectedReason) {
152 case uint32_t(
153 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION):
154 message = "CookieBlockedByPermission";
155 category = "cookieBlockedPermission"_ns;
156 break;
157
158 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER):
159 message = "CookieBlockedTracker";
160 category = "cookieBlockedTracker"_ns;
161 break;
162
163 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL):
164 message = "CookieBlockedAll";
165 category = "cookieBlockedAll"_ns;
166 break;
167
168 case uint32_t(nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN):
169 message = "CookieBlockedForeign";
170 category = "cookieBlockedForeign"_ns;
171 break;
172
173 case uint32_t(
174 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN):
175 message = "CookiePartitionedForeign2";
176 category = "cookiePartitionedForeign"_ns;
177 break;
178
179 default:
180 return;
181 }
182
183 MOZ_ASSERT(message);
184
185 // Strip the URL of any possible username/password and make it ready
186 // to be presented in the UI.
187 nsCOMPtr<nsIURI> exposableURI =
188 net::nsIOService::CreateExposableURI(uri);
189 AutoTArray<nsString, 1> params;
190 CopyUTF8toUTF16(exposableURI->GetSpecOrDefault(),
191 *params.AppendElement());
192
193 nsAutoString errorText;
194 nsresult rv = nsContentUtils::FormatLocalizedString(
195 nsContentUtils::eNECKO_PROPERTIES, message, params, errorText);
196 NS_ENSURE_SUCCESS_VOID(rv);
197
198 nsContentUtils::ReportToConsoleByWindowID(
199 errorText, nsIScriptError::warningFlag, category, aWindowID,
200 nullptr, sourceLine, lineNumber, columnNumber);
201 });
202
203 RunConsoleReportingRunnable(runnable.forget());
204 }
205
ReportBlockingToConsole(nsIChannel * aChannel,nsIURI * aURI,uint32_t aRejectedReason)206 void ReportBlockingToConsole(nsIChannel* aChannel, nsIURI* aURI,
207 uint32_t aRejectedReason) {
208 MOZ_ASSERT(aChannel && aURI);
209
210 // Get the top-level window ID from the top-level BrowsingContext
211 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
212
213 RefPtr<dom::BrowsingContext> bc;
214 loadInfo->GetBrowsingContext(getter_AddRefs(bc));
215
216 BrowsingContext* top = bc ? bc->Top() : nullptr;
217 if (!top) {
218 return;
219 }
220
221 uint64_t windowID = top->GetCurrentInnerWindowId();
222
223 ReportBlockingToConsole(windowID, aURI, aRejectedReason);
224 }
225
NotifyBlockingDecision(nsIChannel * aTrackingChannel,ContentBlockingNotifier::BlockingDecision aDecision,uint32_t aRejectedReason,nsIURI * aURI)226 void NotifyBlockingDecision(nsIChannel* aTrackingChannel,
227 ContentBlockingNotifier::BlockingDecision aDecision,
228 uint32_t aRejectedReason, nsIURI* aURI) {
229 MOZ_ASSERT(aTrackingChannel);
230
231 // This can be called in either the parent process or the child processes.
232 // When this is called in the child processes, we must have a window.
233 if (XRE_IsContentProcess()) {
234 nsCOMPtr<nsILoadContext> loadContext;
235 NS_QueryNotificationCallbacks(aTrackingChannel, loadContext);
236 if (!loadContext) {
237 return;
238 }
239
240 nsCOMPtr<mozIDOMWindowProxy> window;
241 loadContext->GetAssociatedWindow(getter_AddRefs(window));
242 if (!window) {
243 return;
244 }
245
246 nsCOMPtr<nsPIDOMWindowOuter> outer = nsPIDOMWindowOuter::From(window);
247 if (!outer) {
248 return;
249 }
250
251 // When this is called in the child processes with system privileges,
252 // the decision should always be ALLOW. We can stop here because both
253 // UI and content blocking log don't care this event.
254 if (nsGlobalWindowOuter::Cast(outer)->GetPrincipal() ==
255 nsContentUtils::GetSystemPrincipal()) {
256 MOZ_DIAGNOSTIC_ASSERT(aDecision ==
257 ContentBlockingNotifier::BlockingDecision::eAllow);
258 return;
259 }
260 }
261
262 nsAutoCString trackingOrigin;
263 if (aURI) {
264 Unused << nsContentUtils::GetASCIIOrigin(aURI, trackingOrigin);
265 }
266
267 if (aDecision == ContentBlockingNotifier::BlockingDecision::eBlock) {
268 ContentBlockingNotifier::OnEvent(aTrackingChannel, true, aRejectedReason,
269 trackingOrigin);
270
271 ReportBlockingToConsole(aTrackingChannel, aURI, aRejectedReason);
272 }
273
274 // Now send the generic "cookies loaded" notifications, from the most generic
275 // to the most specific.
276 ContentBlockingNotifier::OnEvent(aTrackingChannel, false,
277 nsIWebProgressListener::STATE_COOKIES_LOADED,
278 trackingOrigin);
279
280 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
281 do_QueryInterface(aTrackingChannel);
282 if (!classifiedChannel) {
283 return;
284 }
285
286 uint32_t classificationFlags =
287 classifiedChannel->GetThirdPartyClassificationFlags();
288 if (classificationFlags &
289 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING) {
290 ContentBlockingNotifier::OnEvent(
291 aTrackingChannel, false,
292 nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER, trackingOrigin);
293 }
294
295 if (classificationFlags &
296 nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING) {
297 ContentBlockingNotifier::OnEvent(
298 aTrackingChannel, false,
299 nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER,
300 trackingOrigin);
301 }
302 }
303
304 // Send a message to notify OnContentBlockingEvent in the parent, which will
305 // update the ContentBlockingLog in the parent.
NotifyEventInChild(nsIChannel * aTrackingChannel,bool aBlocked,uint32_t aRejectedReason,const nsACString & aTrackingOrigin,const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason> & aReason)306 void NotifyEventInChild(
307 nsIChannel* aTrackingChannel, bool aBlocked, uint32_t aRejectedReason,
308 const nsACString& aTrackingOrigin,
309 const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
310 aReason) {
311 MOZ_ASSERT(XRE_IsContentProcess());
312
313 // We don't need to find the top-level window here because the
314 // parent will do that for us.
315 nsCOMPtr<nsILoadContext> loadContext;
316 NS_QueryNotificationCallbacks(aTrackingChannel, loadContext);
317 if (!loadContext) {
318 return;
319 }
320
321 nsCOMPtr<mozIDOMWindowProxy> window;
322 loadContext->GetAssociatedWindow(getter_AddRefs(window));
323 if (!window) {
324 return;
325 }
326
327 RefPtr<dom::BrowserChild> browserChild = dom::BrowserChild::GetFrom(window);
328 NS_ENSURE_TRUE_VOID(browserChild);
329
330 nsTArray<nsCString> trackingFullHashes;
331 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
332 do_QueryInterface(aTrackingChannel);
333
334 if (classifiedChannel) {
335 Unused << classifiedChannel->GetMatchedTrackingFullHashes(
336 trackingFullHashes);
337 }
338
339 browserChild->NotifyContentBlockingEvent(aRejectedReason, aTrackingChannel,
340 aBlocked, aTrackingOrigin,
341 trackingFullHashes, aReason);
342 }
343
344 // Update the ContentBlockingLog of the top-level WindowGlobalParent of
345 // the tracking channel.
NotifyEventInParent(nsIChannel * aTrackingChannel,bool aBlocked,uint32_t aRejectedReason,const nsACString & aTrackingOrigin,const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason> & aReason)346 void NotifyEventInParent(
347 nsIChannel* aTrackingChannel, bool aBlocked, uint32_t aRejectedReason,
348 const nsACString& aTrackingOrigin,
349 const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
350 aReason) {
351 MOZ_ASSERT(XRE_IsParentProcess());
352
353 nsCOMPtr<nsILoadInfo> loadInfo = aTrackingChannel->LoadInfo();
354 RefPtr<dom::BrowsingContext> bc;
355 loadInfo->GetBrowsingContext(getter_AddRefs(bc));
356
357 if (!bc || bc->IsDiscarded()) {
358 return;
359 }
360
361 bc = bc->Top();
362 RefPtr<dom::WindowGlobalParent> wgp =
363 bc->Canonical()->GetCurrentWindowGlobal();
364 NS_ENSURE_TRUE_VOID(wgp);
365
366 nsTArray<nsCString> trackingFullHashes;
367 nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
368 do_QueryInterface(aTrackingChannel);
369
370 if (classifiedChannel) {
371 Unused << classifiedChannel->GetMatchedTrackingFullHashes(
372 trackingFullHashes);
373 }
374
375 wgp->NotifyContentBlockingEvent(aRejectedReason, aTrackingChannel, aBlocked,
376 aTrackingOrigin, trackingFullHashes, aReason);
377 }
378
379 } // namespace
380
381 /* static */
ReportUnblockingToConsole(BrowsingContext * aBrowsingContext,const nsAString & aTrackingOrigin,ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason)382 void ContentBlockingNotifier::ReportUnblockingToConsole(
383 BrowsingContext* aBrowsingContext, const nsAString& aTrackingOrigin,
384 ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason) {
385 MOZ_ASSERT(aBrowsingContext);
386 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext->Top()->IsInProcess());
387
388 uint64_t windowID = aBrowsingContext->GetCurrentInnerWindowId();
389
390 // The storage permission is granted under the top-level origin.
391 nsCOMPtr<nsIPrincipal> principal =
392 AntiTrackingUtils::GetPrincipal(aBrowsingContext->Top());
393 if (NS_WARN_IF(!principal)) {
394 return;
395 }
396
397 ::ReportUnblockingToConsole(windowID, principal, aTrackingOrigin, aReason);
398 }
399
400 /* static */
OnDecision(nsIChannel * aChannel,BlockingDecision aDecision,uint32_t aRejectedReason)401 void ContentBlockingNotifier::OnDecision(nsIChannel* aChannel,
402 BlockingDecision aDecision,
403 uint32_t aRejectedReason) {
404 MOZ_ASSERT(
405 aRejectedReason == 0 ||
406 aRejectedReason ==
407 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
408 aRejectedReason ==
409 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
410 aRejectedReason ==
411 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
412 aRejectedReason ==
413 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
414 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
415 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
416 MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
417 aDecision == BlockingDecision::eAllow);
418
419 if (!aChannel) {
420 return;
421 }
422
423 nsCOMPtr<nsIURI> uri;
424 aChannel->GetURI(getter_AddRefs(uri));
425
426 // Can be called in EITHER the parent or child process.
427 NotifyBlockingDecision(aChannel, aDecision, aRejectedReason, uri);
428 }
429
430 /* static */
OnDecision(nsPIDOMWindowInner * aWindow,BlockingDecision aDecision,uint32_t aRejectedReason)431 void ContentBlockingNotifier::OnDecision(nsPIDOMWindowInner* aWindow,
432 BlockingDecision aDecision,
433 uint32_t aRejectedReason) {
434 MOZ_ASSERT(aWindow);
435 MOZ_ASSERT(
436 aRejectedReason == 0 ||
437 aRejectedReason ==
438 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
439 aRejectedReason ==
440 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
441 aRejectedReason ==
442 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
443 aRejectedReason ==
444 nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
445 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
446 aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
447 MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
448 aDecision == BlockingDecision::eAllow);
449
450 Document* document = aWindow->GetExtantDoc();
451 if (!document) {
452 return;
453 }
454
455 nsIChannel* channel = document->GetChannel();
456 if (!channel) {
457 return;
458 }
459
460 nsIURI* uri = document->GetDocumentURI();
461
462 NotifyBlockingDecision(channel, aDecision, aRejectedReason, uri);
463 }
464
465 /* static */
OnDecision(BrowsingContext * aBrowsingContext,BlockingDecision aDecision,uint32_t aRejectedReason)466 void ContentBlockingNotifier::OnDecision(BrowsingContext* aBrowsingContext,
467 BlockingDecision aDecision,
468 uint32_t aRejectedReason) {
469 MOZ_ASSERT(aBrowsingContext);
470 MOZ_ASSERT_IF(XRE_IsContentProcess(), aBrowsingContext->IsInProcess());
471
472 if (aBrowsingContext->IsInProcess()) {
473 nsCOMPtr<nsPIDOMWindowOuter> outer = aBrowsingContext->GetDOMWindow();
474 if (NS_WARN_IF(!outer)) {
475 return;
476 }
477
478 nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
479 if (NS_WARN_IF(!inner)) {
480 return;
481 }
482
483 ContentBlockingNotifier::OnDecision(inner, aDecision, aRejectedReason);
484 } else {
485 // we send an IPC to the content process when we don't have an in-process
486 // browsing context. This is not smart because this should be able to be
487 // done directly in the parent. The reason we are doing this is because we
488 // need the channel, which is not accessible in the parent when you only
489 // have a browsing context.
490 MOZ_ASSERT(XRE_IsParentProcess());
491
492 ContentParent* cp = aBrowsingContext->Canonical()->GetContentParent();
493 Unused << cp->SendOnContentBlockingDecision(aBrowsingContext, aDecision,
494 aRejectedReason);
495 }
496 }
497
498 /* static */
OnEvent(nsIChannel * aTrackingChannel,uint32_t aRejectedReason,bool aBlocked)499 void ContentBlockingNotifier::OnEvent(nsIChannel* aTrackingChannel,
500 uint32_t aRejectedReason, bool aBlocked) {
501 MOZ_ASSERT(XRE_IsParentProcess() && aTrackingChannel);
502
503 nsCOMPtr<nsIURI> uri;
504 aTrackingChannel->GetURI(getter_AddRefs(uri));
505
506 nsAutoCString trackingOrigin;
507 if (uri) {
508 Unused << nsContentUtils::GetASCIIOrigin(uri, trackingOrigin);
509 }
510
511 return ContentBlockingNotifier::OnEvent(aTrackingChannel, aBlocked,
512 aRejectedReason, trackingOrigin);
513 }
514
515 /* static */
OnEvent(nsIChannel * aTrackingChannel,bool aBlocked,uint32_t aRejectedReason,const nsACString & aTrackingOrigin,const Maybe<StorageAccessPermissionGrantedReason> & aReason)516 void ContentBlockingNotifier::OnEvent(
517 nsIChannel* aTrackingChannel, bool aBlocked, uint32_t aRejectedReason,
518 const nsACString& aTrackingOrigin,
519 const Maybe<StorageAccessPermissionGrantedReason>& aReason) {
520 if (XRE_IsParentProcess()) {
521 NotifyEventInParent(aTrackingChannel, aBlocked, aRejectedReason,
522 aTrackingOrigin, aReason);
523 } else {
524 NotifyEventInChild(aTrackingChannel, aBlocked, aRejectedReason,
525 aTrackingOrigin, aReason);
526 }
527 }
528