diff options
Diffstat (limited to 'dom/power')
-rw-r--r-- | dom/power/PowerManagerService.cpp | 170 | ||||
-rw-r--r-- | dom/power/PowerManagerService.h | 60 | ||||
-rw-r--r-- | dom/power/WakeLock.cpp | 228 | ||||
-rw-r--r-- | dom/power/WakeLock.h | 84 | ||||
-rw-r--r-- | dom/power/moz.build | 33 | ||||
-rw-r--r-- | dom/power/nsIDOMWakeLockListener.idl | 29 | ||||
-rw-r--r-- | dom/power/nsIPowerManagerService.idl | 33 | ||||
-rw-r--r-- | dom/power/nsIWakeLock.idl | 12 |
8 files changed, 649 insertions, 0 deletions
diff --git a/dom/power/PowerManagerService.cpp b/dom/power/PowerManagerService.cpp new file mode 100644 index 0000000000..e957643d00 --- /dev/null +++ b/dom/power/PowerManagerService.cpp @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/dom/ContentParent.h" +#include "mozilla/Hal.h" +#include "mozilla/HalWakeLock.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Preferences.h" +#include "nsIDOMWakeLockListener.h" +#include "PowerManagerService.h" +#include "WakeLock.h" + +// For _exit(). +#ifdef XP_WIN +# include <process.h> +#else +# include <unistd.h> +#endif + +namespace mozilla::dom::power { + +using namespace hal; + +NS_IMPL_ISUPPORTS(PowerManagerService, nsIPowerManagerService) + +/* static */ +StaticRefPtr<PowerManagerService> PowerManagerService::sSingleton; + +/* static */ +already_AddRefed<PowerManagerService> PowerManagerService::GetInstance() { + if (!sSingleton) { + sSingleton = new PowerManagerService(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + + RefPtr<PowerManagerService> service = sSingleton.get(); + return service.forget(); +} + +void PowerManagerService::Init() { RegisterWakeLockObserver(this); } + +PowerManagerService::~PowerManagerService() { + UnregisterWakeLockObserver(this); +} + +void PowerManagerService::ComputeWakeLockState( + const WakeLockInformation& aWakeLockInfo, nsAString& aState) { + WakeLockState state = hal::ComputeWakeLockState(aWakeLockInfo.numLocks(), + aWakeLockInfo.numHidden()); + switch (state) { + case WAKE_LOCK_STATE_UNLOCKED: + aState.AssignLiteral("unlocked"); + break; + case WAKE_LOCK_STATE_HIDDEN: + aState.AssignLiteral("locked-background"); + break; + case WAKE_LOCK_STATE_VISIBLE: + aState.AssignLiteral("locked-foreground"); + break; + } +} + +void PowerManagerService::Notify(const WakeLockInformation& aWakeLockInfo) { + nsAutoString state; + ComputeWakeLockState(aWakeLockInfo, state); + + /** + * Copy the listeners list before we walk through the callbacks + * because the callbacks may install new listeners. We expect no + * more than one listener per window, so it shouldn't be too long. + */ + const CopyableAutoTArray<nsCOMPtr<nsIDOMMozWakeLockListener>, 2> listeners = + mWakeLockListeners; + + for (uint32_t i = 0; i < listeners.Length(); ++i) { + listeners[i]->Callback(aWakeLockInfo.topic(), state); + } +} + +NS_IMETHODIMP +PowerManagerService::AddWakeLockListener(nsIDOMMozWakeLockListener* aListener) { + if (mWakeLockListeners.Contains(aListener)) return NS_OK; + + mWakeLockListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManagerService::RemoveWakeLockListener( + nsIDOMMozWakeLockListener* aListener) { + mWakeLockListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PowerManagerService::GetWakeLockState(const nsAString& aTopic, + nsAString& aState) { + WakeLockInformation info; + GetWakeLockInfo(aTopic, &info); + + ComputeWakeLockState(info, aState); + + return NS_OK; +} + +already_AddRefed<WakeLock> PowerManagerService::NewWakeLock( + const nsAString& aTopic, nsPIDOMWindowInner* aWindow, + mozilla::ErrorResult& aRv) { + RefPtr<WakeLock> wakelock = new WakeLock(); + aRv = wakelock->Init(aTopic, aWindow); + if (aRv.Failed()) { + return nullptr; + } + + return wakelock.forget(); +} + +NS_IMETHODIMP +PowerManagerService::NewWakeLock(const nsAString& aTopic, + mozIDOMWindow* aWindow, + nsIWakeLock** aWakeLock) { + mozilla::ErrorResult rv; + RefPtr<WakeLock> wakelock = + NewWakeLock(aTopic, nsPIDOMWindowInner::From(aWindow), rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + wakelock.forget(aWakeLock); + return NS_OK; +} + +} // namespace mozilla::dom::power + +NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVICE_CID); + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR( + nsIPowerManagerService, + mozilla::dom::power::PowerManagerService::GetInstance) + +static const mozilla::Module::CIDEntry kPowerManagerCIDs[] = { + // clang-format off + { &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor, mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS }, + { nullptr } + // clang-format on +}; + +static const mozilla::Module::ContractIDEntry kPowerManagerContracts[] = { + // clang-format off + { POWERMANAGERSERVICE_CONTRACTID, &kNS_POWERMANAGERSERVICE_CID, mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS }, + { nullptr } + // clang-format on +}; + +// We mark the power module as being available in the GPU process because the +// appshell depends on the power manager service. +extern const mozilla::Module kPowerManagerModule = { + mozilla::Module::kVersion, + kPowerManagerCIDs, + kPowerManagerContracts, + nullptr, + nullptr, + nullptr, + nullptr, + mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS}; diff --git a/dom/power/PowerManagerService.h b/dom/power/PowerManagerService.h new file mode 100644 index 0000000000..dffbf66453 --- /dev/null +++ b/dom/power/PowerManagerService.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ +#ifndef mozilla_dom_power_PowerManagerService_h +#define mozilla_dom_power_PowerManagerService_h + +#include "nsCOMPtr.h" +#include "nsDataHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsIDOMWakeLockListener.h" +#include "nsIPowerManagerService.h" +#include "mozilla/HalWakeLockInformation.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/WakeLock.h" + +namespace mozilla { +namespace dom { + +class ContentParent; + +namespace power { + +class PowerManagerService : public nsIPowerManagerService, + public hal::WakeLockObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPOWERMANAGERSERVICE + + PowerManagerService() = default; + + static already_AddRefed<PowerManagerService> GetInstance(); + + void Init(); + + // Implement WakeLockObserver + void Notify(const hal::WakeLockInformation& aWakeLockInfo) override; + + already_AddRefed<WakeLock> NewWakeLock(const nsAString& aTopic, + nsPIDOMWindowInner* aWindow, + mozilla::ErrorResult& aRv); + + private: + ~PowerManagerService(); + + void ComputeWakeLockState(const hal::WakeLockInformation& aWakeLockInfo, + nsAString& aState); + + static StaticRefPtr<PowerManagerService> sSingleton; + + nsTArray<nsCOMPtr<nsIDOMMozWakeLockListener>> mWakeLockListeners; +}; + +} // namespace power +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_power_PowerManagerService_h diff --git a/dom/power/WakeLock.cpp b/dom/power/WakeLock.cpp new file mode 100644 index 0000000000..bdfcfeaccd --- /dev/null +++ b/dom/power/WakeLock.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WakeLock.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Event.h" // for Event +#include "mozilla/Hal.h" +#include "mozilla/HalWakeLock.h" +#include "nsError.h" +#include "mozilla/dom/Document.h" +#include "nsPIDOMWindow.h" +#include "nsIPropertyBag2.h" + +using namespace mozilla::hal; + +namespace mozilla::dom { + +NS_INTERFACE_MAP_BEGIN(WakeLock) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIWakeLock) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(WakeLock) +NS_IMPL_RELEASE(WakeLock) + +WakeLock::WakeLock() + : mLocked(false), + mHidden(true), + mContentParentID(CONTENT_PROCESS_ID_UNKNOWN) {} + +WakeLock::~WakeLock() { + DoUnlock(); + DetachEventListener(); +} + +nsresult WakeLock::Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow) { + // Don't Init() a WakeLock twice. + MOZ_ASSERT(mTopic.IsEmpty()); + + if (aTopic.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + mTopic.Assign(aTopic); + + mWindow = do_GetWeakReference(aWindow); + + /** + * Null windows are allowed. A wake lock without associated window + * is always considered invisible. + */ + if (aWindow) { + nsCOMPtr<Document> doc = aWindow->GetExtantDoc(); + NS_ENSURE_STATE(doc); + mHidden = IsDocumentInvisible(*doc); + } + + AttachEventListener(); + DoLock(); + + return NS_OK; +} + +NS_IMETHODIMP +WakeLock::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* data) { + // If this wake lock was acquired on behalf of another process, unlock it + // when that process dies. + // + // Note that we do /not/ call DoUnlock() here! The wake lock back-end is + // already listening for ipc:content-shutdown messages and will clear out its + // tally for the process when it dies. All we need to do here is ensure that + // unlock() becomes a nop. + + MOZ_ASSERT(!strcmp(aTopic, "ipc:content-shutdown")); + + nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(aSubject); + if (!props) { + NS_WARNING("ipc:content-shutdown message without property bag as subject"); + return NS_OK; + } + + uint64_t childID = 0; + nsresult rv = props->GetPropertyAsUint64(u"childID"_ns, &childID); + if (NS_SUCCEEDED(rv)) { + if (childID == mContentParentID) { + mLocked = false; + } + } else { + NS_WARNING("ipc:content-shutdown message without childID property"); + } + return NS_OK; +} + +void WakeLock::DoLock() { + if (!mLocked) { + // Change the flag immediately to prevent recursive reentering + mLocked = true; + + hal::ModifyWakeLock( + mTopic, hal::WAKE_LOCK_ADD_ONE, + mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_NO_CHANGE, + mContentParentID); + } +} + +void WakeLock::DoUnlock() { + if (mLocked) { + // Change the flag immediately to prevent recursive reentering + mLocked = false; + + hal::ModifyWakeLock( + mTopic, hal::WAKE_LOCK_REMOVE_ONE, + mHidden ? hal::WAKE_LOCK_REMOVE_ONE : hal::WAKE_LOCK_NO_CHANGE, + mContentParentID); + } +} + +void WakeLock::AttachEventListener() { + if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) { + nsCOMPtr<Document> doc = window->GetExtantDoc(); + if (doc) { + doc->AddSystemEventListener(u"visibilitychange"_ns, this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + + nsCOMPtr<EventTarget> target = do_QueryInterface(window); + target->AddSystemEventListener(u"pagehide"_ns, this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + target->AddSystemEventListener(u"pageshow"_ns, this, + /* useCapture = */ true, + /* wantsUntrusted = */ false); + } + } +} + +void WakeLock::DetachEventListener() { + if (nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(mWindow)) { + nsCOMPtr<Document> doc = window->GetExtantDoc(); + if (doc) { + doc->RemoveSystemEventListener(u"visibilitychange"_ns, this, + /* useCapture = */ true); + nsCOMPtr<EventTarget> target = do_QueryInterface(window); + target->RemoveSystemEventListener(u"pagehide"_ns, this, + /* useCapture = */ true); + target->RemoveSystemEventListener(u"pageshow"_ns, this, + /* useCapture = */ true); + } + } +} + +void WakeLock::Unlock(ErrorResult& aRv) { + /* + * We throw NS_ERROR_DOM_INVALID_STATE_ERR on double unlock. + */ + if (!mLocked) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + DoUnlock(); + DetachEventListener(); +} + +void WakeLock::GetTopic(nsAString& aTopic) { aTopic.Assign(mTopic); } + +bool WakeLock::IsDocumentInvisible(const Document& aDocument) const { + // If document has a child element being used in the picture in picture + // mode, which is always visible to users, then we would consider the + // document as visible as well. + return aDocument.Hidden() && !aDocument.HasPictureInPictureChildElement(); +} + +NS_IMETHODIMP +WakeLock::HandleEvent(Event* aEvent) { + nsAutoString type; + aEvent->GetType(type); + + if (type.EqualsLiteral("visibilitychange")) { + nsCOMPtr<Document> doc = do_QueryInterface(aEvent->GetTarget()); + NS_ENSURE_STATE(doc); + + bool oldHidden = mHidden; + mHidden = IsDocumentInvisible(*doc); + + if (mLocked && oldHidden != mHidden) { + hal::ModifyWakeLock( + mTopic, hal::WAKE_LOCK_NO_CHANGE, + mHidden ? hal::WAKE_LOCK_ADD_ONE : hal::WAKE_LOCK_REMOVE_ONE, + mContentParentID); + } + + return NS_OK; + } + + if (type.EqualsLiteral("pagehide")) { + DoUnlock(); + return NS_OK; + } + + if (type.EqualsLiteral("pageshow")) { + DoLock(); + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +WakeLock::Unlock() { + ErrorResult error; + Unlock(error); + return error.StealNSResult(); +} + +nsPIDOMWindowInner* WakeLock::GetParentObject() const { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mWindow); + return window; +} + +} // namespace mozilla::dom diff --git a/dom/power/WakeLock.h b/dom/power/WakeLock.h new file mode 100644 index 0000000000..0ccf5c81fd --- /dev/null +++ b/dom/power/WakeLock.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef mozilla_dom_power_WakeLock_h +#define mozilla_dom_power_WakeLock_h + +#include "nsCOMPtr.h" +#include "nsIDOMEventListener.h" +#include "nsIObserver.h" +#include "nsIWakeLock.h" +#include "nsString.h" +#include "nsWeakReference.h" +#include "nsWrapperCache.h" + +class nsPIDOMWindowInner; + +namespace mozilla { +class ErrorResult; + +namespace dom { +class Document; + +class WakeLock final : public nsIDOMEventListener, + public nsIObserver, + public nsSupportsWeakReference, + public nsIWakeLock { + public: + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSIWAKELOCK + + NS_DECL_ISUPPORTS + + // Note: WakeLock lives for the lifetime of the document in order to avoid + // exposing GC behavior to pages. This means that + // |var foo = navigator.requestWakeLock('cpu'); foo = null;| + // doesn't unlock the 'cpu' resource. + + WakeLock(); + + // Initialize this wake lock on behalf of the given window. Null windows are + // allowed; a lock without an associated window is always considered + // invisible. + nsresult Init(const nsAString& aTopic, nsPIDOMWindowInner* aWindow); + + // WebIDL methods + + nsPIDOMWindowInner* GetParentObject() const; + + void GetTopic(nsAString& aTopic); + + void Unlock(ErrorResult& aRv); + + private: + virtual ~WakeLock(); + + void DoUnlock(); + void DoLock(); + void AttachEventListener(); + void DetachEventListener(); + + // Return true if all parts of the given document are regarded as invisible. + bool IsDocumentInvisible(const Document& aDocument) const; + + bool mLocked; + bool mHidden; + + // The ID of the ContentParent on behalf of whom we acquired this lock, or + // CONTENT_PROCESS_UNKNOWN_ID if this lock was acquired on behalf of the + // current process. + uint64_t mContentParentID; + nsString mTopic; + + // window that this was created for. Weak reference. + nsWeakPtr mWindow; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_power_WakeLock_h diff --git a/dom/power/moz.build b/dom/power/moz.build new file mode 100644 index 0000000000..c2524fdfa0 --- /dev/null +++ b/dom/power/moz.build @@ -0,0 +1,33 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Core & HTML") + +XPIDL_SOURCES += [ + "nsIDOMWakeLockListener.idl", + "nsIPowerManagerService.idl", + "nsIWakeLock.idl", +] + +XPIDL_MODULE = "dom_power" + +EXPORTS.mozilla.dom += [ + "WakeLock.h", +] + +EXPORTS.mozilla.dom.power += [ + "PowerManagerService.h", +] + +UNIFIED_SOURCES += [ + "PowerManagerService.cpp", + "WakeLock.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/dom/power/nsIDOMWakeLockListener.idl b/dom/power/nsIDOMWakeLockListener.idl new file mode 100644 index 0000000000..366e9cf594 --- /dev/null +++ b/dom/power/nsIDOMWakeLockListener.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 40; 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 "nsISupports.idl" + +[scriptable, function, uuid(4e258af8-cffb-47bc-b16d-e8241243426e)] +interface nsIDOMMozWakeLockListener : nsISupports +{ + /** + * The callback will be called when a lock topic changes its lock + * state. + * + * Possible states are: + * + * - "unlocked" - nobody holds the wake lock. + * + * - "locked-foreground" - at least one window holds the wake lock, + * and it is visible. + * + * - "locked-background" - at least one window holds the wake lock, + * but all of them are hidden. + * + * @param aTopic The resource name related to the wake lock. + * @param aState The wake lock state + */ + void callback(in AString aTopic, in AString aState); +}; diff --git a/dom/power/nsIPowerManagerService.idl b/dom/power/nsIPowerManagerService.idl new file mode 100644 index 0000000000..49d8e3f693 --- /dev/null +++ b/dom/power/nsIPowerManagerService.idl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +%{C++ +#define NS_POWERMANAGERSERVICE_CID { 0x18c2e238, 0x3a0a, 0x4153, {0x89, 0xfc, 0x16, 0x6b, 0x3b, 0x14, 0x65, 0xa1 } } +#define POWERMANAGERSERVICE_CONTRACTID "@mozilla.org/power/powermanagerservice;1" +%} + +interface nsIDOMMozWakeLockListener; +interface mozIDOMWindow; +interface nsIWakeLock; + +/** + * For use with non-content code. + */ +[scriptable, builtinclass, uuid(ba7ca4c1-9d92-4425-a83b-85dd7fa953f7)] +interface nsIPowerManagerService : nsISupports +{ + void addWakeLockListener(in nsIDOMMozWakeLockListener aListener); + void removeWakeLockListener(in nsIDOMMozWakeLockListener aListener); + AString getWakeLockState(in AString aTopic); + + /** + * Return a wake lock (MozWakeLock) object of aTopic associated with aWindow. + * A wake lock without associated window, e.g. used in chrome, is + * always considered invisible. + */ + nsIWakeLock newWakeLock(in AString aTopic, [optional] in mozIDOMWindow aWindow); +}; diff --git a/dom/power/nsIWakeLock.idl b/dom/power/nsIWakeLock.idl new file mode 100644 index 0000000000..5ebc158109 --- /dev/null +++ b/dom/power/nsIWakeLock.idl @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 40; 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 "nsISupports.idl" + +[scriptable, builtinclass, uuid(e27e57ce-fa63-4035-b9ef-27c5dc0cc3ae)] +interface nsIWakeLock : nsISupports +{ + void unlock(); +}; |