1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla SMIL module.
16 : *
17 : * The Initial Developer of the Original Code is Brian Birtles.
18 : * Portions created by the Initial Developer are Copyright (C) 2005
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Brian Birtles <birtles@gmail.com>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either of the GNU General Public License Version 2 or later (the "GPL"),
26 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "nsSMILTimeValueSpec.h"
39 : #include "nsSMILInterval.h"
40 : #include "nsSMILTimeContainer.h"
41 : #include "nsSMILTimeValue.h"
42 : #include "nsSMILTimedElement.h"
43 : #include "nsSMILInstanceTime.h"
44 : #include "nsSMILParserUtils.h"
45 : #include "nsISMILAnimationElement.h"
46 : #include "nsContentUtils.h"
47 : #include "nsEventListenerManager.h"
48 : #include "nsGUIEvent.h"
49 : #include "nsIDOMTimeEvent.h"
50 : #include "nsString.h"
51 : #include <limits>
52 :
53 : using namespace mozilla::dom;
54 :
55 : //----------------------------------------------------------------------
56 : // Nested class: EventListener
57 :
58 0 : NS_IMPL_ISUPPORTS1(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
59 :
60 : NS_IMETHODIMP
61 0 : nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
62 : {
63 0 : if (mSpec) {
64 0 : mSpec->HandleEvent(aEvent);
65 : }
66 0 : return NS_OK;
67 : }
68 :
69 : //----------------------------------------------------------------------
70 : // Implementation
71 :
72 : #ifdef _MSC_VER
73 : // Disable "warning C4355: 'this' : used in base member initializer list".
74 : // We can ignore that warning because we know that mReferencedElement's
75 : // constructor doesn't dereference the pointer passed to it.
76 : #pragma warning(push)
77 : #pragma warning(disable:4355)
78 : #endif
79 0 : nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
80 : bool aIsBegin)
81 : : mOwner(&aOwner),
82 : mIsBegin(aIsBegin),
83 0 : mReferencedElement(this)
84 : #ifdef _MSC_VER
85 : #pragma warning(pop)
86 : #endif
87 : {
88 0 : }
89 :
90 0 : nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
91 : {
92 0 : UnregisterFromReferencedElement(mReferencedElement.get());
93 0 : if (mEventListener) {
94 0 : mEventListener->Disconnect();
95 0 : mEventListener = nsnull;
96 : }
97 0 : }
98 :
99 : nsresult
100 0 : nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
101 : Element* aContextNode)
102 : {
103 0 : nsSMILTimeValueSpecParams params;
104 : nsresult rv =
105 0 : nsSMILParserUtils::ParseTimeValueSpecParams(aStringSpec, params);
106 :
107 0 : if (NS_FAILED(rv))
108 0 : return rv;
109 :
110 0 : mParams = params;
111 :
112 : // According to SMIL 3.0:
113 : // The special value "indefinite" does not yield an instance time in the
114 : // begin list. It will, however yield a single instance with the value
115 : // "indefinite" in an end list. This value is not removed by a reset.
116 0 : if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET ||
117 0 : (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
118 0 : mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
119 : }
120 :
121 : // Fill in the event symbol to simplify handling later
122 0 : if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
123 0 : mParams.mEventSymbol = nsGkAtoms::repeatEvent;
124 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
125 0 : mParams.mEventSymbol = nsGkAtoms::keypress;
126 : }
127 :
128 0 : ResolveReferences(aContextNode);
129 :
130 0 : return rv;
131 : }
132 :
133 : void
134 0 : nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
135 : {
136 0 : if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE && !IsEventBased())
137 0 : return;
138 :
139 0 : NS_ABORT_IF_FALSE(aContextNode,
140 : "null context node for resolving timing references against");
141 :
142 : // If we're not bound to the document yet, don't worry, we'll get called again
143 : // when that happens
144 0 : if (!aContextNode->IsInDoc())
145 0 : return;
146 :
147 : // Hold ref to the old element so that it isn't destroyed in between resetting
148 : // the referenced element and using the pointer to update the referenced
149 : // element.
150 0 : nsRefPtr<Element> oldReferencedElement = mReferencedElement.get();
151 :
152 0 : if (mParams.mDependentElemID) {
153 : mReferencedElement.ResetWithID(aContextNode,
154 0 : nsDependentAtomString(mParams.mDependentElemID));
155 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
156 0 : Element* target = mOwner->GetTargetElement();
157 0 : mReferencedElement.ResetWithElement(target);
158 0 : } else if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
159 0 : nsIDocument* doc = aContextNode->GetCurrentDoc();
160 0 : NS_ABORT_IF_FALSE(doc, "We are in the document but current doc is null");
161 0 : mReferencedElement.ResetWithElement(doc->GetRootElement());
162 : } else {
163 0 : NS_ABORT_IF_FALSE(false, "Syncbase or repeat spec without ID");
164 : }
165 0 : UpdateReferencedElement(oldReferencedElement, mReferencedElement.get());
166 : }
167 :
168 : bool
169 0 : nsSMILTimeValueSpec::IsEventBased() const
170 : {
171 : return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
172 : mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
173 0 : mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
174 : }
175 :
176 : void
177 0 : nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval,
178 : const nsSMILTimeContainer* aSrcContainer)
179 : {
180 : const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin
181 0 : ? *aInterval.Begin() : *aInterval.End();
182 : nsSMILTimeValue newTime =
183 0 : ConvertBetweenTimeContainers(baseInstance.Time(), aSrcContainer);
184 :
185 : // Apply offset
186 0 : if (!ApplyOffset(newTime)) {
187 0 : NS_WARNING("New time overflows nsSMILTime, ignoring");
188 0 : return;
189 : }
190 :
191 : // Create the instance time and register it with the interval
192 : nsRefPtr<nsSMILInstanceTime> newInstance =
193 : new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
194 0 : &aInterval);
195 0 : mOwner->AddInstanceTime(newInstance, mIsBegin);
196 : }
197 :
198 : void
199 0 : nsSMILTimeValueSpec::HandleTargetElementChange(Element* aNewTarget)
200 : {
201 0 : if (!IsEventBased() || mParams.mDependentElemID)
202 0 : return;
203 :
204 0 : mReferencedElement.ResetWithElement(aNewTarget);
205 : }
206 :
207 : void
208 0 : nsSMILTimeValueSpec::HandleChangedInstanceTime(
209 : const nsSMILInstanceTime& aBaseTime,
210 : const nsSMILTimeContainer* aSrcContainer,
211 : nsSMILInstanceTime& aInstanceTimeToUpdate,
212 : bool aObjectChanged)
213 : {
214 : // If the instance time is fixed (e.g. because it's being used as the begin
215 : // time of an active or postactive interval) we just ignore the change.
216 0 : if (aInstanceTimeToUpdate.IsFixedTime())
217 0 : return;
218 :
219 : nsSMILTimeValue updatedTime =
220 0 : ConvertBetweenTimeContainers(aBaseTime.Time(), aSrcContainer);
221 :
222 : // Apply offset
223 0 : if (!ApplyOffset(updatedTime)) {
224 0 : NS_WARNING("Updated time overflows nsSMILTime, ignoring");
225 0 : return;
226 : }
227 :
228 : // The timed element that owns the instance time does the updating so it can
229 : // re-sort its array of instance times more efficiently
230 0 : if (aInstanceTimeToUpdate.Time() != updatedTime || aObjectChanged) {
231 0 : mOwner->UpdateInstanceTime(&aInstanceTimeToUpdate, updatedTime, mIsBegin);
232 : }
233 : }
234 :
235 : void
236 0 : nsSMILTimeValueSpec::HandleDeletedInstanceTime(
237 : nsSMILInstanceTime &aInstanceTime)
238 : {
239 0 : mOwner->RemoveInstanceTime(&aInstanceTime, mIsBegin);
240 0 : }
241 :
242 : bool
243 0 : nsSMILTimeValueSpec::DependsOnBegin() const
244 : {
245 0 : return mParams.mSyncBegin;
246 : }
247 :
248 : void
249 0 : nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
250 : {
251 0 : mReferencedElement.Traverse(aCallback);
252 0 : }
253 :
254 : void
255 0 : nsSMILTimeValueSpec::Unlink()
256 : {
257 0 : UnregisterFromReferencedElement(mReferencedElement.get());
258 0 : mReferencedElement.Unlink();
259 0 : }
260 :
261 : //----------------------------------------------------------------------
262 : // Implementation helpers
263 :
264 : void
265 0 : nsSMILTimeValueSpec::UpdateReferencedElement(Element* aFrom, Element* aTo)
266 : {
267 0 : if (aFrom == aTo)
268 0 : return;
269 :
270 0 : UnregisterFromReferencedElement(aFrom);
271 :
272 0 : switch (mParams.mType)
273 : {
274 : case nsSMILTimeValueSpecParams::SYNCBASE:
275 : {
276 0 : nsSMILTimedElement* to = GetTimedElement(aTo);
277 0 : if (to) {
278 0 : to->AddDependent(*this);
279 : }
280 : }
281 0 : break;
282 :
283 : case nsSMILTimeValueSpecParams::EVENT:
284 : case nsSMILTimeValueSpecParams::REPEAT:
285 : case nsSMILTimeValueSpecParams::ACCESSKEY:
286 0 : RegisterEventListener(aTo);
287 0 : break;
288 :
289 : default:
290 : // not a referencing-type
291 0 : break;
292 : }
293 : }
294 :
295 : void
296 0 : nsSMILTimeValueSpec::UnregisterFromReferencedElement(Element* aElement)
297 : {
298 0 : if (!aElement)
299 0 : return;
300 :
301 0 : if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
302 0 : nsSMILTimedElement* timedElement = GetTimedElement(aElement);
303 0 : if (timedElement) {
304 0 : timedElement->RemoveDependent(*this);
305 : }
306 0 : mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
307 0 : } else if (IsEventBased()) {
308 0 : UnregisterEventListener(aElement);
309 : }
310 : }
311 :
312 : nsSMILTimedElement*
313 0 : nsSMILTimeValueSpec::GetTimedElement(Element* aElement)
314 : {
315 0 : if (!aElement)
316 0 : return nsnull;
317 :
318 0 : nsCOMPtr<nsISMILAnimationElement> animElement = do_QueryInterface(aElement);
319 0 : if (!animElement)
320 0 : return nsnull;
321 :
322 0 : return &animElement->TimedElement();
323 : }
324 :
325 : // Indicates whether we're allowed to register an event-listener
326 : // when scripting is disabled.
327 : bool
328 0 : nsSMILTimeValueSpec::IsWhitelistedEvent()
329 : {
330 : // The category of (SMIL-specific) "repeat(n)" events are allowed.
331 0 : if (mParams.mType == nsSMILTimeValueSpecParams::REPEAT) {
332 0 : return true;
333 : }
334 :
335 : // A specific list of other SMIL-related events are allowed, too.
336 0 : if (mParams.mType == nsSMILTimeValueSpecParams::EVENT &&
337 0 : (mParams.mEventSymbol == nsGkAtoms::repeat ||
338 0 : mParams.mEventSymbol == nsGkAtoms::repeatEvent ||
339 0 : mParams.mEventSymbol == nsGkAtoms::beginEvent ||
340 0 : mParams.mEventSymbol == nsGkAtoms::endEvent)) {
341 0 : return true;
342 : }
343 :
344 0 : return false;
345 : }
346 :
347 : void
348 0 : nsSMILTimeValueSpec::RegisterEventListener(Element* aTarget)
349 : {
350 0 : NS_ABORT_IF_FALSE(IsEventBased(),
351 : "Attempting to register event-listener for unexpected nsSMILTimeValueSpec"
352 : " type");
353 0 : NS_ABORT_IF_FALSE(mParams.mEventSymbol,
354 : "Attempting to register event-listener but there is no event name");
355 :
356 0 : if (!aTarget)
357 0 : return;
358 :
359 : // When script is disabled, only allow registration for whitelisted events.
360 0 : if (!aTarget->GetOwnerDocument()->IsScriptEnabled() &&
361 0 : !IsWhitelistedEvent()) {
362 0 : return;
363 : }
364 :
365 0 : if (!mEventListener) {
366 0 : mEventListener = new EventListener(this);
367 : }
368 :
369 0 : nsEventListenerManager* elm = GetEventListenerManager(aTarget);
370 0 : if (!elm)
371 0 : return;
372 :
373 : elm->AddEventListenerByType(mEventListener,
374 0 : nsDependentAtomString(mParams.mEventSymbol),
375 : NS_EVENT_FLAG_BUBBLE |
376 : NS_PRIV_EVENT_UNTRUSTED_PERMITTED |
377 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
378 : }
379 :
380 : void
381 0 : nsSMILTimeValueSpec::UnregisterEventListener(Element* aTarget)
382 : {
383 0 : if (!aTarget || !mEventListener)
384 0 : return;
385 :
386 0 : nsEventListenerManager* elm = GetEventListenerManager(aTarget);
387 0 : if (!elm)
388 0 : return;
389 :
390 : elm->RemoveEventListenerByType(mEventListener,
391 0 : nsDependentAtomString(mParams.mEventSymbol),
392 : NS_EVENT_FLAG_BUBBLE |
393 : NS_PRIV_EVENT_UNTRUSTED_PERMITTED |
394 0 : NS_EVENT_FLAG_SYSTEM_EVENT);
395 : }
396 :
397 : nsEventListenerManager*
398 0 : nsSMILTimeValueSpec::GetEventListenerManager(Element* aTarget)
399 : {
400 0 : NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager");
401 :
402 0 : nsCOMPtr<nsIDOMEventTarget> target;
403 :
404 0 : if (mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY) {
405 0 : nsIDocument* doc = aTarget->GetCurrentDoc();
406 0 : if (!doc)
407 0 : return nsnull;
408 0 : nsPIDOMWindow* win = doc->GetWindow();
409 0 : if (!win)
410 0 : return nsnull;
411 0 : target = do_QueryInterface(win);
412 : } else {
413 0 : target = aTarget;
414 : }
415 0 : if (!target)
416 0 : return nsnull;
417 :
418 0 : return target->GetListenerManager(true);
419 : }
420 :
421 : void
422 0 : nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent)
423 : {
424 0 : NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener");
425 0 : NS_ABORT_IF_FALSE(IsEventBased(),
426 : "Got event for non-event nsSMILTimeValueSpec");
427 0 : NS_ABORT_IF_FALSE(aEvent, "No event supplied");
428 :
429 : // XXX In the long run we should get the time from the event itself which will
430 : // store the time in global document time which we'll need to convert to our
431 : // time container
432 0 : nsSMILTimeContainer* container = mOwner->GetTimeContainer();
433 0 : if (!container)
434 0 : return;
435 :
436 0 : if (!CheckEventDetail(aEvent))
437 0 : return;
438 :
439 0 : nsSMILTime currentTime = container->GetCurrentTime();
440 0 : nsSMILTimeValue newTime(currentTime);
441 0 : if (!ApplyOffset(newTime)) {
442 0 : NS_WARNING("New time generated from event overflows nsSMILTime, ignoring");
443 0 : return;
444 : }
445 :
446 : nsRefPtr<nsSMILInstanceTime> newInstance =
447 0 : new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
448 0 : mOwner->AddInstanceTime(newInstance, mIsBegin);
449 : }
450 :
451 : bool
452 0 : nsSMILTimeValueSpec::CheckEventDetail(nsIDOMEvent *aEvent)
453 : {
454 0 : switch (mParams.mType)
455 : {
456 : case nsSMILTimeValueSpecParams::REPEAT:
457 0 : return CheckRepeatEventDetail(aEvent);
458 :
459 : case nsSMILTimeValueSpecParams::ACCESSKEY:
460 0 : return CheckAccessKeyEventDetail(aEvent);
461 :
462 : default:
463 : // nothing to check
464 0 : return true;
465 : }
466 : }
467 :
468 : bool
469 0 : nsSMILTimeValueSpec::CheckRepeatEventDetail(nsIDOMEvent *aEvent)
470 : {
471 0 : nsCOMPtr<nsIDOMTimeEvent> timeEvent = do_QueryInterface(aEvent);
472 0 : if (!timeEvent) {
473 0 : NS_WARNING("Received a repeat event that was not a DOMTimeEvent");
474 0 : return false;
475 : }
476 :
477 : PRInt32 detail;
478 0 : timeEvent->GetDetail(&detail);
479 0 : return detail > 0 && (PRUint32)detail == mParams.mRepeatIterationOrAccessKey;
480 : }
481 :
482 : bool
483 0 : nsSMILTimeValueSpec::CheckAccessKeyEventDetail(nsIDOMEvent *aEvent)
484 : {
485 0 : nsCOMPtr<nsIDOMKeyEvent> keyEvent = do_QueryInterface(aEvent);
486 0 : if (!keyEvent) {
487 0 : NS_WARNING("Received an accesskey event that was not a DOMKeyEvent");
488 0 : return false;
489 : }
490 :
491 : // Ignore the key event if any modifier keys are pressed UNLESS we're matching
492 : // on the charCode in which case we ignore the state of the shift and alt keys
493 : // since they might be needed to generate the character in question.
494 : bool isCtrl;
495 : bool isMeta;
496 0 : keyEvent->GetCtrlKey(&isCtrl);
497 0 : keyEvent->GetMetaKey(&isMeta);
498 0 : if (isCtrl || isMeta)
499 0 : return false;
500 :
501 : PRUint32 code;
502 0 : keyEvent->GetCharCode(&code);
503 0 : if (code)
504 0 : return code == mParams.mRepeatIterationOrAccessKey;
505 :
506 : // Only match on the keyCode if it corresponds to some ASCII character that
507 : // does not produce a charCode.
508 : // In this case we can safely bail out if either alt or shift is pressed since
509 : // they won't already be incorporated into the keyCode unlike the charCode.
510 : bool isAlt;
511 : bool isShift;
512 0 : keyEvent->GetAltKey(&isAlt);
513 0 : keyEvent->GetShiftKey(&isShift);
514 0 : if (isAlt || isShift)
515 0 : return false;
516 :
517 0 : keyEvent->GetKeyCode(&code);
518 0 : switch (code)
519 : {
520 : case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
521 0 : return mParams.mRepeatIterationOrAccessKey == 0x08;
522 :
523 : case nsIDOMKeyEvent::DOM_VK_RETURN:
524 : case nsIDOMKeyEvent::DOM_VK_ENTER:
525 : return mParams.mRepeatIterationOrAccessKey == 0x0A ||
526 0 : mParams.mRepeatIterationOrAccessKey == 0x0D;
527 :
528 : case nsIDOMKeyEvent::DOM_VK_ESCAPE:
529 0 : return mParams.mRepeatIterationOrAccessKey == 0x1B;
530 :
531 : case nsIDOMKeyEvent::DOM_VK_DELETE:
532 0 : return mParams.mRepeatIterationOrAccessKey == 0x7F;
533 :
534 : default:
535 0 : return false;
536 : }
537 : }
538 :
539 : nsSMILTimeValue
540 0 : nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
541 : const nsSMILTimeValue& aSrcTime,
542 : const nsSMILTimeContainer* aSrcContainer)
543 : {
544 : // If the source time is either indefinite or unresolved the result is going
545 : // to be the same
546 0 : if (!aSrcTime.IsDefinite())
547 0 : return aSrcTime;
548 :
549 : // Convert from source time container to our parent time container
550 0 : const nsSMILTimeContainer* dstContainer = mOwner->GetTimeContainer();
551 0 : if (dstContainer == aSrcContainer)
552 0 : return aSrcTime;
553 :
554 : // If one of the elements is not attached to a time container then we can't do
555 : // any meaningful conversion
556 0 : if (!aSrcContainer || !dstContainer)
557 0 : return nsSMILTimeValue(); // unresolved
558 :
559 : nsSMILTimeValue docTime =
560 0 : aSrcContainer->ContainerToParentTime(aSrcTime.GetMillis());
561 :
562 0 : if (docTime.IsIndefinite())
563 : // This will happen if the source container is paused and we have a future
564 : // time. Just return the indefinite time.
565 0 : return docTime;
566 :
567 0 : NS_ABORT_IF_FALSE(docTime.IsDefinite(),
568 : "ContainerToParentTime gave us an unresolved or indefinite time");
569 :
570 0 : return dstContainer->ParentToContainerTime(docTime.GetMillis());
571 : }
572 :
573 : bool
574 0 : nsSMILTimeValueSpec::ApplyOffset(nsSMILTimeValue& aTime) const
575 : {
576 : // indefinite + offset = indefinite. Likewise for unresolved times.
577 0 : if (!aTime.IsDefinite()) {
578 0 : return true;
579 : }
580 :
581 : double resultAsDouble =
582 0 : (double)aTime.GetMillis() + mParams.mOffset.GetMillis();
583 0 : if (resultAsDouble > std::numeric_limits<nsSMILTime>::max() ||
584 0 : resultAsDouble < std::numeric_limits<nsSMILTime>::min()) {
585 0 : return false;
586 : }
587 0 : aTime.SetMillis(aTime.GetMillis() + mParams.mOffset.GetMillis());
588 0 : return true;
589 : }
|