|
1 //* -*- Mode: C++; tab-width: 8; 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 "nsCRT.h" |
|
7 #include "nsIHttpChannel.h" |
|
8 #include "nsIObserverService.h" |
|
9 #include "nsIStringStream.h" |
|
10 #include "nsIUploadChannel.h" |
|
11 #include "nsIURI.h" |
|
12 #include "nsIUrlClassifierDBService.h" |
|
13 #include "nsStreamUtils.h" |
|
14 #include "nsStringStream.h" |
|
15 #include "nsToolkitCompsCID.h" |
|
16 #include "nsUrlClassifierStreamUpdater.h" |
|
17 #include "prlog.h" |
|
18 #include "nsIInterfaceRequestor.h" |
|
19 #include "mozilla/LoadContext.h" |
|
20 |
|
21 static const char* gQuitApplicationMessage = "quit-application"; |
|
22 |
|
23 #undef LOG |
|
24 |
|
25 // NSPR_LOG_MODULES=UrlClassifierStreamUpdater:5 |
|
26 #if defined(PR_LOGGING) |
|
27 static const PRLogModuleInfo *gUrlClassifierStreamUpdaterLog = nullptr; |
|
28 #define LOG(args) PR_LOG(gUrlClassifierStreamUpdaterLog, PR_LOG_DEBUG, args) |
|
29 #else |
|
30 #define LOG(args) |
|
31 #endif |
|
32 |
|
33 |
|
34 // This class does absolutely nothing, except pass requests onto the DBService. |
|
35 |
|
36 /////////////////////////////////////////////////////////////////////////////// |
|
37 // nsIUrlClassiferStreamUpdater implementation |
|
38 // Handles creating/running the stream listener |
|
39 |
|
40 nsUrlClassifierStreamUpdater::nsUrlClassifierStreamUpdater() |
|
41 : mIsUpdating(false), mInitialized(false), mDownloadError(false), |
|
42 mBeganStream(false), mUpdateUrl(nullptr), mChannel(nullptr) |
|
43 { |
|
44 #if defined(PR_LOGGING) |
|
45 if (!gUrlClassifierStreamUpdaterLog) |
|
46 gUrlClassifierStreamUpdaterLog = PR_NewLogModule("UrlClassifierStreamUpdater"); |
|
47 #endif |
|
48 |
|
49 } |
|
50 |
|
51 NS_IMPL_ISUPPORTS(nsUrlClassifierStreamUpdater, |
|
52 nsIUrlClassifierStreamUpdater, |
|
53 nsIUrlClassifierUpdateObserver, |
|
54 nsIRequestObserver, |
|
55 nsIStreamListener, |
|
56 nsIObserver, |
|
57 nsIInterfaceRequestor, |
|
58 nsITimerCallback) |
|
59 |
|
60 /** |
|
61 * Clear out the update. |
|
62 */ |
|
63 void |
|
64 nsUrlClassifierStreamUpdater::DownloadDone() |
|
65 { |
|
66 LOG(("nsUrlClassifierStreamUpdater::DownloadDone [this=%p]", this)); |
|
67 mIsUpdating = false; |
|
68 |
|
69 mPendingUpdates.Clear(); |
|
70 mDownloadError = false; |
|
71 mSuccessCallback = nullptr; |
|
72 mUpdateErrorCallback = nullptr; |
|
73 mDownloadErrorCallback = nullptr; |
|
74 } |
|
75 |
|
76 /////////////////////////////////////////////////////////////////////////////// |
|
77 // nsIUrlClassifierStreamUpdater implementation |
|
78 |
|
79 NS_IMETHODIMP |
|
80 nsUrlClassifierStreamUpdater::GetUpdateUrl(nsACString & aUpdateUrl) |
|
81 { |
|
82 if (mUpdateUrl) { |
|
83 mUpdateUrl->GetSpec(aUpdateUrl); |
|
84 } else { |
|
85 aUpdateUrl.Truncate(); |
|
86 } |
|
87 return NS_OK; |
|
88 } |
|
89 |
|
90 NS_IMETHODIMP |
|
91 nsUrlClassifierStreamUpdater::SetUpdateUrl(const nsACString & aUpdateUrl) |
|
92 { |
|
93 LOG(("Update URL is %s\n", PromiseFlatCString(aUpdateUrl).get())); |
|
94 |
|
95 nsresult rv = NS_NewURI(getter_AddRefs(mUpdateUrl), aUpdateUrl); |
|
96 NS_ENSURE_SUCCESS(rv, rv); |
|
97 |
|
98 return NS_OK; |
|
99 } |
|
100 |
|
101 nsresult |
|
102 nsUrlClassifierStreamUpdater::FetchUpdate(nsIURI *aUpdateUrl, |
|
103 const nsACString & aRequestBody, |
|
104 const nsACString & aStreamTable) |
|
105 { |
|
106 nsresult rv; |
|
107 uint32_t loadFlags = nsIChannel::INHIBIT_CACHING | |
|
108 nsIChannel::LOAD_BYPASS_CACHE; |
|
109 rv = NS_NewChannel(getter_AddRefs(mChannel), aUpdateUrl, nullptr, nullptr, this, |
|
110 loadFlags); |
|
111 NS_ENSURE_SUCCESS(rv, rv); |
|
112 |
|
113 mBeganStream = false; |
|
114 |
|
115 // If aRequestBody is empty, construct it for the test. |
|
116 if (!aRequestBody.IsEmpty()) { |
|
117 rv = AddRequestBody(aRequestBody); |
|
118 NS_ENSURE_SUCCESS(rv, rv); |
|
119 } |
|
120 |
|
121 // Set the appropriate content type for file/data URIs, for unit testing |
|
122 // purposes. |
|
123 // This is only used for testing and should be deleted. |
|
124 bool match; |
|
125 if ((NS_SUCCEEDED(aUpdateUrl->SchemeIs("file", &match)) && match) || |
|
126 (NS_SUCCEEDED(aUpdateUrl->SchemeIs("data", &match)) && match)) { |
|
127 mChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.google.safebrowsing-update")); |
|
128 } |
|
129 |
|
130 // Create a custom LoadContext for SafeBrowsing, so we can use callbacks on |
|
131 // the channel to query the appId which allows separation of safebrowsing |
|
132 // cookies in a separate jar. |
|
133 nsCOMPtr<nsIInterfaceRequestor> sbContext = |
|
134 new mozilla::LoadContext(NECKO_SAFEBROWSING_APP_ID); |
|
135 rv = mChannel->SetNotificationCallbacks(sbContext); |
|
136 NS_ENSURE_SUCCESS(rv, rv); |
|
137 |
|
138 // Make the request |
|
139 rv = mChannel->AsyncOpen(this, nullptr); |
|
140 NS_ENSURE_SUCCESS(rv, rv); |
|
141 |
|
142 mStreamTable = aStreamTable; |
|
143 |
|
144 return NS_OK; |
|
145 } |
|
146 |
|
147 nsresult |
|
148 nsUrlClassifierStreamUpdater::FetchUpdate(const nsACString & aUpdateUrl, |
|
149 const nsACString & aRequestBody, |
|
150 const nsACString & aStreamTable) |
|
151 { |
|
152 LOG(("(pre) Fetching update from %s\n", PromiseFlatCString(aUpdateUrl).get())); |
|
153 |
|
154 nsCOMPtr<nsIURI> uri; |
|
155 nsresult rv = NS_NewURI(getter_AddRefs(uri), aUpdateUrl); |
|
156 NS_ENSURE_SUCCESS(rv, rv); |
|
157 |
|
158 nsAutoCString urlSpec; |
|
159 uri->GetAsciiSpec(urlSpec); |
|
160 |
|
161 LOG(("(post) Fetching update from %s\n", urlSpec.get())); |
|
162 |
|
163 return FetchUpdate(uri, aRequestBody, aStreamTable); |
|
164 } |
|
165 |
|
166 NS_IMETHODIMP |
|
167 nsUrlClassifierStreamUpdater::DownloadUpdates( |
|
168 const nsACString &aRequestTables, |
|
169 const nsACString &aRequestBody, |
|
170 nsIUrlClassifierCallback *aSuccessCallback, |
|
171 nsIUrlClassifierCallback *aUpdateErrorCallback, |
|
172 nsIUrlClassifierCallback *aDownloadErrorCallback, |
|
173 bool *_retval) |
|
174 { |
|
175 NS_ENSURE_ARG(aSuccessCallback); |
|
176 NS_ENSURE_ARG(aUpdateErrorCallback); |
|
177 NS_ENSURE_ARG(aDownloadErrorCallback); |
|
178 |
|
179 if (mIsUpdating) { |
|
180 LOG(("already updating, skipping update")); |
|
181 *_retval = false; |
|
182 return NS_OK; |
|
183 } |
|
184 |
|
185 if (!mUpdateUrl) { |
|
186 NS_ERROR("updateUrl not set"); |
|
187 return NS_ERROR_NOT_INITIALIZED; |
|
188 } |
|
189 |
|
190 nsresult rv; |
|
191 |
|
192 if (!mInitialized) { |
|
193 // Add an observer for shutdown so we can cancel any pending list |
|
194 // downloads. quit-application is the same event that the download |
|
195 // manager listens for and uses to cancel pending downloads. |
|
196 nsCOMPtr<nsIObserverService> observerService = |
|
197 mozilla::services::GetObserverService(); |
|
198 if (!observerService) |
|
199 return NS_ERROR_FAILURE; |
|
200 |
|
201 observerService->AddObserver(this, gQuitApplicationMessage, false); |
|
202 |
|
203 mDBService = do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv); |
|
204 NS_ENSURE_SUCCESS(rv, rv); |
|
205 |
|
206 mInitialized = true; |
|
207 } |
|
208 |
|
209 rv = mDBService->BeginUpdate(this, aRequestTables); |
|
210 if (rv == NS_ERROR_NOT_AVAILABLE) { |
|
211 LOG(("already updating, skipping update")); |
|
212 *_retval = false; |
|
213 return NS_OK; |
|
214 } else if (NS_FAILED(rv)) { |
|
215 return rv; |
|
216 } |
|
217 |
|
218 mSuccessCallback = aSuccessCallback; |
|
219 mUpdateErrorCallback = aUpdateErrorCallback; |
|
220 mDownloadErrorCallback = aDownloadErrorCallback; |
|
221 |
|
222 mIsUpdating = true; |
|
223 *_retval = true; |
|
224 |
|
225 nsAutoCString urlSpec; |
|
226 mUpdateUrl->GetAsciiSpec(urlSpec); |
|
227 |
|
228 LOG(("FetchUpdate: %s", urlSpec.get())); |
|
229 //LOG(("requestBody: %s", aRequestBody.Data())); |
|
230 |
|
231 LOG(("Calling into FetchUpdate")); |
|
232 return FetchUpdate(mUpdateUrl, aRequestBody, EmptyCString()); |
|
233 } |
|
234 |
|
235 /////////////////////////////////////////////////////////////////////////////// |
|
236 // nsIUrlClassifierUpdateObserver implementation |
|
237 |
|
238 NS_IMETHODIMP |
|
239 nsUrlClassifierStreamUpdater::UpdateUrlRequested(const nsACString &aUrl, |
|
240 const nsACString &aTable) |
|
241 { |
|
242 LOG(("Queuing requested update from %s\n", PromiseFlatCString(aUrl).get())); |
|
243 |
|
244 PendingUpdate *update = mPendingUpdates.AppendElement(); |
|
245 if (!update) |
|
246 return NS_ERROR_OUT_OF_MEMORY; |
|
247 |
|
248 // Allow data: and file: urls for unit testing purposes, otherwise assume http |
|
249 if (StringBeginsWith(aUrl, NS_LITERAL_CSTRING("data:")) || |
|
250 StringBeginsWith(aUrl, NS_LITERAL_CSTRING("file:"))) { |
|
251 update->mUrl = aUrl; |
|
252 } else { |
|
253 // For unittesting update urls to localhost should use http, not https |
|
254 // (otherwise the connection will fail silently, since there will be no |
|
255 // cert available). |
|
256 if (!StringBeginsWith(aUrl, NS_LITERAL_CSTRING("localhost"))) { |
|
257 update->mUrl = NS_LITERAL_CSTRING("https://") + aUrl; |
|
258 } else { |
|
259 update->mUrl = NS_LITERAL_CSTRING("http://") + aUrl; |
|
260 } |
|
261 } |
|
262 update->mTable = aTable; |
|
263 |
|
264 return NS_OK; |
|
265 } |
|
266 |
|
267 nsresult |
|
268 nsUrlClassifierStreamUpdater::FetchNext() |
|
269 { |
|
270 if (mPendingUpdates.Length() == 0) { |
|
271 return NS_OK; |
|
272 } |
|
273 |
|
274 PendingUpdate &update = mPendingUpdates[0]; |
|
275 LOG(("Fetching update url: %s\n", update.mUrl.get())); |
|
276 nsresult rv = FetchUpdate(update.mUrl, EmptyCString(), |
|
277 update.mTable); |
|
278 if (NS_FAILED(rv)) { |
|
279 LOG(("Error fetching update url: %s\n", update.mUrl.get())); |
|
280 // We can commit the urls that we've applied so far. This is |
|
281 // probably a transient server problem, so trigger backoff. |
|
282 mDownloadErrorCallback->HandleEvent(EmptyCString()); |
|
283 mDownloadError = true; |
|
284 mDBService->FinishUpdate(); |
|
285 return rv; |
|
286 } |
|
287 |
|
288 mPendingUpdates.RemoveElementAt(0); |
|
289 |
|
290 return NS_OK; |
|
291 } |
|
292 |
|
293 NS_IMETHODIMP |
|
294 nsUrlClassifierStreamUpdater::StreamFinished(nsresult status, |
|
295 uint32_t requestedDelay) |
|
296 { |
|
297 LOG(("nsUrlClassifierStreamUpdater::StreamFinished [%x, %d]", status, requestedDelay)); |
|
298 if (NS_FAILED(status) || mPendingUpdates.Length() == 0) { |
|
299 // We're done. |
|
300 mDBService->FinishUpdate(); |
|
301 return NS_OK; |
|
302 } |
|
303 |
|
304 // Wait the requested amount of time before starting a new stream. |
|
305 nsresult rv; |
|
306 mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); |
|
307 if (NS_SUCCEEDED(rv)) { |
|
308 rv = mTimer->InitWithCallback(this, requestedDelay, |
|
309 nsITimer::TYPE_ONE_SHOT); |
|
310 } |
|
311 |
|
312 if (NS_FAILED(rv)) { |
|
313 NS_WARNING("Unable to initialize timer, fetching next safebrowsing item immediately"); |
|
314 return FetchNext(); |
|
315 } |
|
316 |
|
317 return NS_OK; |
|
318 } |
|
319 |
|
320 NS_IMETHODIMP |
|
321 nsUrlClassifierStreamUpdater::UpdateSuccess(uint32_t requestedTimeout) |
|
322 { |
|
323 LOG(("nsUrlClassifierStreamUpdater::UpdateSuccess [this=%p]", this)); |
|
324 if (mPendingUpdates.Length() != 0) { |
|
325 NS_WARNING("Didn't fetch all safebrowsing update redirects"); |
|
326 } |
|
327 |
|
328 // DownloadDone() clears mSuccessCallback, so we save it off here. |
|
329 nsCOMPtr<nsIUrlClassifierCallback> successCallback = mDownloadError ? nullptr : mSuccessCallback.get(); |
|
330 DownloadDone(); |
|
331 |
|
332 nsAutoCString strTimeout; |
|
333 strTimeout.AppendInt(requestedTimeout); |
|
334 if (successCallback) { |
|
335 successCallback->HandleEvent(strTimeout); |
|
336 } |
|
337 |
|
338 return NS_OK; |
|
339 } |
|
340 |
|
341 NS_IMETHODIMP |
|
342 nsUrlClassifierStreamUpdater::UpdateError(nsresult result) |
|
343 { |
|
344 LOG(("nsUrlClassifierStreamUpdater::UpdateError [this=%p]", this)); |
|
345 |
|
346 // DownloadDone() clears mUpdateErrorCallback, so we save it off here. |
|
347 nsCOMPtr<nsIUrlClassifierCallback> errorCallback = mDownloadError ? nullptr : mUpdateErrorCallback.get(); |
|
348 |
|
349 DownloadDone(); |
|
350 |
|
351 nsAutoCString strResult; |
|
352 strResult.AppendInt(static_cast<uint32_t>(result)); |
|
353 if (errorCallback) { |
|
354 errorCallback->HandleEvent(strResult); |
|
355 } |
|
356 |
|
357 return NS_OK; |
|
358 } |
|
359 |
|
360 nsresult |
|
361 nsUrlClassifierStreamUpdater::AddRequestBody(const nsACString &aRequestBody) |
|
362 { |
|
363 nsresult rv; |
|
364 nsCOMPtr<nsIStringInputStream> strStream = |
|
365 do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); |
|
366 NS_ENSURE_SUCCESS(rv, rv); |
|
367 |
|
368 rv = strStream->SetData(aRequestBody.BeginReading(), |
|
369 aRequestBody.Length()); |
|
370 NS_ENSURE_SUCCESS(rv, rv); |
|
371 |
|
372 nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(mChannel, &rv); |
|
373 NS_ENSURE_SUCCESS(rv, rv); |
|
374 |
|
375 rv = uploadChannel->SetUploadStream(strStream, |
|
376 NS_LITERAL_CSTRING("text/plain"), |
|
377 -1); |
|
378 NS_ENSURE_SUCCESS(rv, rv); |
|
379 |
|
380 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel, &rv); |
|
381 NS_ENSURE_SUCCESS(rv, rv); |
|
382 |
|
383 rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST")); |
|
384 NS_ENSURE_SUCCESS(rv, rv); |
|
385 |
|
386 return NS_OK; |
|
387 } |
|
388 |
|
389 |
|
390 /////////////////////////////////////////////////////////////////////////////// |
|
391 // nsIStreamListenerObserver implementation |
|
392 |
|
393 NS_IMETHODIMP |
|
394 nsUrlClassifierStreamUpdater::OnStartRequest(nsIRequest *request, |
|
395 nsISupports* context) |
|
396 { |
|
397 nsresult rv; |
|
398 bool downloadError = false; |
|
399 nsAutoCString strStatus; |
|
400 nsresult status = NS_OK; |
|
401 |
|
402 // Only update if we got http success header |
|
403 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); |
|
404 if (httpChannel) { |
|
405 rv = httpChannel->GetStatus(&status); |
|
406 NS_ENSURE_SUCCESS(rv, rv); |
|
407 |
|
408 if (NS_ERROR_CONNECTION_REFUSED == status || |
|
409 NS_ERROR_NET_TIMEOUT == status) { |
|
410 // Assume we're overloading the server and trigger backoff. |
|
411 downloadError = true; |
|
412 } |
|
413 |
|
414 if (NS_SUCCEEDED(status)) { |
|
415 bool succeeded = false; |
|
416 rv = httpChannel->GetRequestSucceeded(&succeeded); |
|
417 NS_ENSURE_SUCCESS(rv, rv); |
|
418 |
|
419 if (!succeeded) { |
|
420 // 404 or other error, pass error status back |
|
421 LOG(("HTTP request returned failure code.")); |
|
422 |
|
423 uint32_t requestStatus; |
|
424 rv = httpChannel->GetResponseStatus(&requestStatus); |
|
425 LOG(("HTTP request returned failure code: %d.", requestStatus)); |
|
426 NS_ENSURE_SUCCESS(rv, rv); |
|
427 |
|
428 strStatus.AppendInt(requestStatus); |
|
429 downloadError = true; |
|
430 } |
|
431 } |
|
432 } |
|
433 |
|
434 if (downloadError) { |
|
435 mDownloadErrorCallback->HandleEvent(strStatus); |
|
436 mDownloadError = true; |
|
437 status = NS_ERROR_ABORT; |
|
438 } else if (NS_SUCCEEDED(status)) { |
|
439 mBeganStream = true; |
|
440 rv = mDBService->BeginStream(mStreamTable); |
|
441 NS_ENSURE_SUCCESS(rv, rv); |
|
442 } |
|
443 |
|
444 mStreamTable.Truncate(); |
|
445 |
|
446 return status; |
|
447 } |
|
448 |
|
449 NS_IMETHODIMP |
|
450 nsUrlClassifierStreamUpdater::OnDataAvailable(nsIRequest *request, |
|
451 nsISupports* context, |
|
452 nsIInputStream *aIStream, |
|
453 uint64_t aSourceOffset, |
|
454 uint32_t aLength) |
|
455 { |
|
456 if (!mDBService) |
|
457 return NS_ERROR_NOT_INITIALIZED; |
|
458 |
|
459 LOG(("OnDataAvailable (%d bytes)", aLength)); |
|
460 |
|
461 nsresult rv; |
|
462 |
|
463 // Copy the data into a nsCString |
|
464 nsCString chunk; |
|
465 rv = NS_ConsumeStream(aIStream, aLength, chunk); |
|
466 NS_ENSURE_SUCCESS(rv, rv); |
|
467 |
|
468 //LOG(("Chunk (%d): %s\n\n", chunk.Length(), chunk.get())); |
|
469 rv = mDBService->UpdateStream(chunk); |
|
470 NS_ENSURE_SUCCESS(rv, rv); |
|
471 |
|
472 return NS_OK; |
|
473 } |
|
474 |
|
475 NS_IMETHODIMP |
|
476 nsUrlClassifierStreamUpdater::OnStopRequest(nsIRequest *request, nsISupports* context, |
|
477 nsresult aStatus) |
|
478 { |
|
479 if (!mDBService) |
|
480 return NS_ERROR_NOT_INITIALIZED; |
|
481 |
|
482 LOG(("OnStopRequest (status %x)", aStatus)); |
|
483 |
|
484 nsresult rv; |
|
485 |
|
486 if (NS_SUCCEEDED(aStatus)) { |
|
487 // Success, finish this stream and move on to the next. |
|
488 rv = mDBService->FinishStream(); |
|
489 } else if (mBeganStream) { |
|
490 // We began this stream and couldn't finish it. We have to cancel the |
|
491 // update, it's not in a consistent state. |
|
492 rv = mDBService->CancelUpdate(); |
|
493 } else { |
|
494 // The fetch failed, but we didn't start the stream (probably a |
|
495 // server or connection error). We can commit what we've applied |
|
496 // so far, and request again later. |
|
497 rv = mDBService->FinishUpdate(); |
|
498 } |
|
499 |
|
500 mChannel = nullptr; |
|
501 |
|
502 return rv; |
|
503 } |
|
504 |
|
505 /////////////////////////////////////////////////////////////////////////////// |
|
506 // nsIObserver implementation |
|
507 |
|
508 NS_IMETHODIMP |
|
509 nsUrlClassifierStreamUpdater::Observe(nsISupports *aSubject, const char *aTopic, |
|
510 const char16_t *aData) |
|
511 { |
|
512 if (nsCRT::strcmp(aTopic, gQuitApplicationMessage) == 0) { |
|
513 if (mIsUpdating && mChannel) { |
|
514 LOG(("Cancel download")); |
|
515 nsresult rv; |
|
516 rv = mChannel->Cancel(NS_ERROR_ABORT); |
|
517 NS_ENSURE_SUCCESS(rv, rv); |
|
518 mIsUpdating = false; |
|
519 mChannel = nullptr; |
|
520 } |
|
521 if (mTimer) { |
|
522 mTimer->Cancel(); |
|
523 mTimer = nullptr; |
|
524 } |
|
525 } |
|
526 return NS_OK; |
|
527 } |
|
528 |
|
529 /////////////////////////////////////////////////////////////////////////////// |
|
530 // nsIInterfaceRequestor implementation |
|
531 |
|
532 NS_IMETHODIMP |
|
533 nsUrlClassifierStreamUpdater::GetInterface(const nsIID & eventSinkIID, void* *_retval) |
|
534 { |
|
535 return QueryInterface(eventSinkIID, _retval); |
|
536 } |
|
537 |
|
538 |
|
539 /////////////////////////////////////////////////////////////////////////////// |
|
540 // nsITimerCallback implementation |
|
541 NS_IMETHODIMP |
|
542 nsUrlClassifierStreamUpdater::Notify(nsITimer *timer) |
|
543 { |
|
544 LOG(("nsUrlClassifierStreamUpdater::Notify [%p]", this)); |
|
545 |
|
546 mTimer = nullptr; |
|
547 |
|
548 // Start the update process up again. |
|
549 FetchNext(); |
|
550 |
|
551 return NS_OK; |
|
552 } |
|
553 |