|
1 /* -*- Mode: C++; tab-width: 4; 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 "MetroAppShell.h" |
|
7 |
|
8 #include "mozilla/AutoRestore.h" |
|
9 #include "mozilla/TimeStamp.h" |
|
10 #include "mozilla/widget/AudioSession.h" |
|
11 |
|
12 #include "nsIObserverService.h" |
|
13 #include "nsIAppStartup.h" |
|
14 #include "nsToolkitCompsCID.h" |
|
15 #include "nsIPowerManagerService.h" |
|
16 |
|
17 #include "nsXULAppAPI.h" |
|
18 #include "nsServiceManagerUtils.h" |
|
19 #include "WinUtils.h" |
|
20 #include "nsWinMetroUtils.h" |
|
21 #include "MetroUtils.h" |
|
22 #include "MetroApp.h" |
|
23 #include "FrameworkView.h" |
|
24 #include "WakeLockListener.h" |
|
25 |
|
26 #include <shellapi.h> |
|
27 |
|
28 using namespace mozilla; |
|
29 using namespace mozilla::widget; |
|
30 using namespace mozilla::widget::winrt; |
|
31 using namespace Microsoft::WRL; |
|
32 using namespace Microsoft::WRL::Wrappers; |
|
33 using namespace ABI::Windows::UI::Core; |
|
34 using namespace ABI::Windows::Foundation; |
|
35 |
|
36 // ProcessNextNativeEvent message wait timeout, see bug 907410. |
|
37 #define MSG_WAIT_TIMEOUT 250 |
|
38 // MetroInput will occasionally ask us to flush all input so that the dom is |
|
39 // up to date. This is the maximum amount of time we'll agree to spend in |
|
40 // NS_ProcessPendingEvents. |
|
41 #define PURGE_MAX_TIMEOUT 50 |
|
42 |
|
43 namespace mozilla { |
|
44 namespace widget { |
|
45 namespace winrt { |
|
46 extern ComPtr<MetroApp> sMetroApp; |
|
47 } } } |
|
48 |
|
49 namespace mozilla { |
|
50 namespace widget { |
|
51 // pulled from win32 app shell |
|
52 extern UINT sAppShellGeckoMsgId; |
|
53 } } |
|
54 |
|
55 static ComPtr<ICoreWindowStatic> sCoreStatic; |
|
56 static bool sIsDispatching = false; |
|
57 static bool sShouldPurgeThreadQueue = false; |
|
58 static bool sBlockNativeEvents = false; |
|
59 static TimeStamp sPurgeThreadQueueStart; |
|
60 |
|
61 MetroAppShell::~MetroAppShell() |
|
62 { |
|
63 if (mEventWnd) { |
|
64 SendMessage(mEventWnd, WM_CLOSE, 0, 0); |
|
65 } |
|
66 } |
|
67 |
|
68 nsresult |
|
69 MetroAppShell::Init() |
|
70 { |
|
71 LogFunction(); |
|
72 |
|
73 WNDCLASSW wc; |
|
74 HINSTANCE module = GetModuleHandle(nullptr); |
|
75 |
|
76 const char16_t *const kWindowClass = L"nsAppShell:EventWindowClass"; |
|
77 if (!GetClassInfoW(module, kWindowClass, &wc)) { |
|
78 wc.style = 0; |
|
79 wc.lpfnWndProc = EventWindowProc; |
|
80 wc.cbClsExtra = 0; |
|
81 wc.cbWndExtra = 0; |
|
82 wc.hInstance = module; |
|
83 wc.hIcon = nullptr; |
|
84 wc.hCursor = nullptr; |
|
85 wc.hbrBackground = (HBRUSH) nullptr; |
|
86 wc.lpszMenuName = (LPCWSTR) nullptr; |
|
87 wc.lpszClassName = kWindowClass; |
|
88 RegisterClassW(&wc); |
|
89 } |
|
90 |
|
91 mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", |
|
92 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr); |
|
93 NS_ENSURE_STATE(mEventWnd); |
|
94 |
|
95 nsresult rv; |
|
96 nsCOMPtr<nsIObserverService> observerService = |
|
97 do_GetService("@mozilla.org/observer-service;1", &rv); |
|
98 if (NS_SUCCEEDED(rv)) { |
|
99 observerService->AddObserver(this, "dl-start", false); |
|
100 observerService->AddObserver(this, "dl-done", false); |
|
101 observerService->AddObserver(this, "dl-cancel", false); |
|
102 observerService->AddObserver(this, "dl-failed", false); |
|
103 } |
|
104 |
|
105 return nsBaseAppShell::Init(); |
|
106 } |
|
107 |
|
108 HRESULT |
|
109 SHCreateShellItemArrayFromShellItemDynamic(IShellItem *psi, REFIID riid, void **ppv) |
|
110 { |
|
111 HMODULE shell32DLL = LoadLibraryW(L"shell32.dll"); |
|
112 if (!shell32DLL) { |
|
113 return E_FAIL; |
|
114 } |
|
115 |
|
116 typedef BOOL (WINAPI* SHFn)(IShellItem *psi, REFIID riid, void **ppv); |
|
117 |
|
118 HRESULT hr = E_FAIL; |
|
119 SHFn SHCreateShellItemArrayFromShellItemDynamicPtr = |
|
120 (SHFn)GetProcAddress(shell32DLL, "SHCreateShellItemArrayFromShellItem"); |
|
121 FreeLibrary(shell32DLL); |
|
122 if (SHCreateShellItemArrayFromShellItemDynamicPtr) { |
|
123 hr = SHCreateShellItemArrayFromShellItemDynamicPtr(psi, riid, ppv); |
|
124 } |
|
125 |
|
126 FreeLibrary(shell32DLL); |
|
127 return hr; |
|
128 } |
|
129 |
|
130 HRESULT |
|
131 WinLaunchDeferredMetroFirefox() |
|
132 { |
|
133 // Create an instance of the Firefox Metro CEH which is used to launch the browser |
|
134 const CLSID CLSID_FirefoxMetroDEH = {0x5100FEC1,0x212B, 0x4BF5 ,{0x9B,0xF8, 0x3E,0x65, 0x0F,0xD7,0x94,0xA3}}; |
|
135 |
|
136 nsRefPtr<IExecuteCommand> executeCommand; |
|
137 HRESULT hr = CoCreateInstance(CLSID_FirefoxMetroDEH, |
|
138 nullptr, |
|
139 CLSCTX_LOCAL_SERVER, |
|
140 IID_IExecuteCommand, |
|
141 getter_AddRefs(executeCommand)); |
|
142 if (FAILED(hr)) |
|
143 return hr; |
|
144 |
|
145 // Get the currently running exe path |
|
146 WCHAR exePath[MAX_PATH + 1] = { L'\0' }; |
|
147 if (!::GetModuleFileNameW(0, exePath, MAX_PATH)) |
|
148 return hr; |
|
149 |
|
150 // Convert the path to a long path since GetModuleFileNameW returns the path |
|
151 // that was used to launch Firefox which is not necessarily a long path. |
|
152 if (!::GetLongPathNameW(exePath, exePath, MAX_PATH)) |
|
153 return hr; |
|
154 |
|
155 // Create an IShellItem for the current browser path |
|
156 nsRefPtr<IShellItem> shellItem; |
|
157 hr = WinUtils::SHCreateItemFromParsingName(exePath, nullptr, IID_IShellItem, |
|
158 getter_AddRefs(shellItem)); |
|
159 if (FAILED(hr)) |
|
160 return hr; |
|
161 |
|
162 // Convert to an IShellItemArray which is used for the path to launch |
|
163 nsRefPtr<IShellItemArray> shellItemArray; |
|
164 hr = SHCreateShellItemArrayFromShellItemDynamic(shellItem, IID_IShellItemArray, getter_AddRefs(shellItemArray)); |
|
165 if (FAILED(hr)) |
|
166 return hr; |
|
167 |
|
168 // Set the path to launch and parameters needed |
|
169 nsRefPtr<IObjectWithSelection> selection; |
|
170 hr = executeCommand->QueryInterface(IID_IObjectWithSelection, getter_AddRefs(selection)); |
|
171 if (FAILED(hr)) |
|
172 return hr; |
|
173 hr = selection->SetSelection(shellItemArray); |
|
174 if (FAILED(hr)) |
|
175 return hr; |
|
176 |
|
177 if (nsWinMetroUtils::sUpdatePending) { |
|
178 hr = executeCommand->SetParameters(L"--metro-update"); |
|
179 } else { |
|
180 hr = executeCommand->SetParameters(L"--metro-restart"); |
|
181 } |
|
182 if (FAILED(hr)) |
|
183 return hr; |
|
184 |
|
185 // Run the default browser through the CEH |
|
186 return executeCommand->Execute(); |
|
187 } |
|
188 |
|
189 static WakeLockListener* |
|
190 InitWakeLock() |
|
191 { |
|
192 nsCOMPtr<nsIPowerManagerService> powerManagerService = |
|
193 do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
194 if (powerManagerService) { |
|
195 WakeLockListener* pLock = new WakeLockListener(); |
|
196 powerManagerService->AddWakeLockListener(pLock); |
|
197 return pLock; |
|
198 } |
|
199 else { |
|
200 NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); |
|
201 } |
|
202 return nullptr; |
|
203 } |
|
204 |
|
205 static void |
|
206 ShutdownWakeLock(WakeLockListener* aLock) |
|
207 { |
|
208 if (!aLock) { |
|
209 return; |
|
210 } |
|
211 nsCOMPtr<nsIPowerManagerService> powerManagerService = |
|
212 do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
213 if (powerManagerService) { |
|
214 powerManagerService->RemoveWakeLockListener(aLock); |
|
215 } |
|
216 } |
|
217 |
|
218 // Called by appstartup->run in xre, which is initiated by a call to |
|
219 // XRE_metroStartup in MetroApp. This call is on the metro main thread. |
|
220 NS_IMETHODIMP |
|
221 MetroAppShell::Run(void) |
|
222 { |
|
223 LogFunction(); |
|
224 nsresult rv = NS_OK; |
|
225 |
|
226 switch(XRE_GetProcessType()) { |
|
227 case GeckoProcessType_Content: |
|
228 case GeckoProcessType_IPDLUnitTest: |
|
229 mozilla::widget::StartAudioSession(); |
|
230 rv = nsBaseAppShell::Run(); |
|
231 mozilla::widget::StopAudioSession(); |
|
232 break; |
|
233 case GeckoProcessType_Plugin: |
|
234 NS_WARNING("We don't support plugins currently."); |
|
235 // Just exit |
|
236 rv = NS_ERROR_NOT_IMPLEMENTED; |
|
237 break; |
|
238 case GeckoProcessType_Default: { |
|
239 { |
|
240 nsRefPtr<WakeLockListener> wakeLock = InitWakeLock(); |
|
241 mozilla::widget::StartAudioSession(); |
|
242 sMetroApp->ActivateBaseView(); |
|
243 rv = nsBaseAppShell::Run(); |
|
244 mozilla::widget::StopAudioSession(); |
|
245 ShutdownWakeLock(wakeLock); |
|
246 } |
|
247 |
|
248 nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID)); |
|
249 bool restartingInMetro = false, restartingInDesktop = false; |
|
250 |
|
251 if (!appStartup || NS_FAILED(appStartup->GetRestarting(&restartingInDesktop))) { |
|
252 WinUtils::Log("appStartup->GetRestarting() unsuccessful"); |
|
253 } |
|
254 |
|
255 if (appStartup && NS_SUCCEEDED(appStartup->GetRestartingTouchEnvironment(&restartingInMetro)) && |
|
256 restartingInMetro) { |
|
257 restartingInDesktop = false; |
|
258 } |
|
259 |
|
260 // This calls XRE_metroShutdown() in xre. Shuts down gecko, including |
|
261 // releasing the profile, and destroys MessagePump. |
|
262 sMetroApp->Shutdown(); |
|
263 |
|
264 // Handle update restart or browser switch requests |
|
265 if (restartingInDesktop) { |
|
266 WinUtils::Log("Relaunching desktop browser"); |
|
267 // We can't call into the ceh to do this. Microsoft prevents switching to |
|
268 // desktop unless we go through shell execute. |
|
269 SHELLEXECUTEINFOW sinfo; |
|
270 memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); |
|
271 sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); |
|
272 // Per microsoft's metro style enabled desktop browser documentation |
|
273 // SEE_MASK_FLAG_LOG_USAGE is needed if we want to change from immersive |
|
274 // mode to desktop mode. |
|
275 sinfo.fMask = SEE_MASK_FLAG_LOG_USAGE; |
|
276 // The ceh will filter out this fake target. |
|
277 sinfo.lpFile = L"http://-desktop/"; |
|
278 sinfo.lpVerb = L"open"; |
|
279 sinfo.lpParameters = L"--desktop-restart"; |
|
280 sinfo.nShow = SW_SHOWNORMAL; |
|
281 ShellExecuteEx(&sinfo); |
|
282 } else if (restartingInMetro) { |
|
283 HRESULT hresult = WinLaunchDeferredMetroFirefox(); |
|
284 WinUtils::Log("Relaunching metro browser (hr=%X)", hresult); |
|
285 } |
|
286 |
|
287 // This will free the real main thread in CoreApplication::Run() |
|
288 // once winrt cleans up this thread. |
|
289 sMetroApp->CoreExit(); |
|
290 } |
|
291 break; |
|
292 } |
|
293 |
|
294 return rv; |
|
295 } |
|
296 |
|
297 // Called in certain cases where we have async input events in the thread |
|
298 // queue and need to make sure they get dispatched before the stack unwinds. |
|
299 void // static |
|
300 MetroAppShell::MarkEventQueueForPurge() |
|
301 { |
|
302 sShouldPurgeThreadQueue = true; |
|
303 |
|
304 // If we're dispatching native events, wait until the dispatcher is |
|
305 // off the stack. |
|
306 if (sIsDispatching) { |
|
307 return; |
|
308 } |
|
309 |
|
310 // Safe to process pending events now |
|
311 DispatchAllGeckoEvents(); |
|
312 } |
|
313 |
|
314 // Notification from MetroInput that all events it wanted delivered |
|
315 // have been dispatched. It is safe to start processing windowing |
|
316 // events. |
|
317 void // static |
|
318 MetroAppShell::InputEventsDispatched() |
|
319 { |
|
320 sBlockNativeEvents = false; |
|
321 } |
|
322 |
|
323 // static |
|
324 void |
|
325 MetroAppShell::DispatchAllGeckoEvents() |
|
326 { |
|
327 // Only do this if requested and when we're not shutting down |
|
328 if (!sShouldPurgeThreadQueue || MetroApp::sGeckoShuttingDown) { |
|
329 return; |
|
330 } |
|
331 |
|
332 NS_ASSERTION(NS_IsMainThread(), "DispatchAllGeckoEvents should be called on the main thread"); |
|
333 |
|
334 sShouldPurgeThreadQueue = false; |
|
335 sPurgeThreadQueueStart = TimeStamp::Now(); |
|
336 |
|
337 sBlockNativeEvents = true; |
|
338 nsIThread *thread = NS_GetCurrentThread(); |
|
339 NS_ProcessPendingEvents(thread, PURGE_MAX_TIMEOUT); |
|
340 sBlockNativeEvents = false; |
|
341 } |
|
342 |
|
343 static void |
|
344 ProcessNativeEvents(CoreProcessEventsOption eventOption) |
|
345 { |
|
346 HRESULT hr; |
|
347 if (!sCoreStatic) { |
|
348 hr = GetActivationFactory(HStringReference(L"Windows.UI.Core.CoreWindow").Get(), sCoreStatic.GetAddressOf()); |
|
349 NS_ASSERTION(SUCCEEDED(hr), "GetActivationFactory failed?"); |
|
350 AssertHRESULT(hr); |
|
351 } |
|
352 |
|
353 ComPtr<ICoreWindow> window; |
|
354 AssertHRESULT(sCoreStatic->GetForCurrentThread(window.GetAddressOf())); |
|
355 ComPtr<ICoreDispatcher> dispatcher; |
|
356 hr = window->get_Dispatcher(&dispatcher); |
|
357 NS_ASSERTION(SUCCEEDED(hr), "get_Dispatcher failed?"); |
|
358 AssertHRESULT(hr); |
|
359 dispatcher->ProcessEvents(eventOption); |
|
360 } |
|
361 |
|
362 // static |
|
363 bool |
|
364 MetroAppShell::ProcessOneNativeEventIfPresent() |
|
365 { |
|
366 if (sIsDispatching) { |
|
367 // Calling into ProcessNativeEvents is harmless, but won't actually process any |
|
368 // native events. So we log here so we can spot this and get a handle on the |
|
369 // corner cases where this can happen. |
|
370 WinUtils::Log("WARNING: Reentrant call into process events detected, returning early."); |
|
371 return false; |
|
372 } |
|
373 |
|
374 { |
|
375 AutoRestore<bool> dispatching(sIsDispatching); |
|
376 sIsDispatching = true; |
|
377 ProcessNativeEvents(CoreProcessEventsOption::CoreProcessEventsOption_ProcessOneIfPresent); |
|
378 } |
|
379 |
|
380 DispatchAllGeckoEvents(); |
|
381 |
|
382 return !!HIWORD(::GetQueueStatus(MOZ_QS_ALLEVENT)); |
|
383 } |
|
384 |
|
385 bool |
|
386 MetroAppShell::ProcessNextNativeEvent(bool mayWait) |
|
387 { |
|
388 // NS_ProcessPendingEvents will process thread events *and* call |
|
389 // nsBaseAppShell::OnProcessNextEvent to process native events. However |
|
390 // we do not want native events getting dispatched while we are trying |
|
391 // to dispatch pending input in DispatchAllGeckoEvents since a native |
|
392 // event may be a UIA Automation call coming in to check focus. |
|
393 if (sBlockNativeEvents) { |
|
394 if ((TimeStamp::Now() - sPurgeThreadQueueStart).ToMilliseconds() |
|
395 < PURGE_MAX_TIMEOUT) { |
|
396 return false; |
|
397 } |
|
398 sBlockNativeEvents = false; |
|
399 } |
|
400 |
|
401 if (ProcessOneNativeEventIfPresent()) { |
|
402 return true; |
|
403 } |
|
404 if (mayWait) { |
|
405 DWORD result = ::MsgWaitForMultipleObjectsEx(0, nullptr, MSG_WAIT_TIMEOUT, |
|
406 MOZ_QS_ALLEVENT, |
|
407 MWMO_INPUTAVAILABLE|MWMO_ALERTABLE); |
|
408 NS_WARN_IF_FALSE(result != WAIT_FAILED, "Wait failed"); |
|
409 } |
|
410 return ProcessOneNativeEventIfPresent(); |
|
411 } |
|
412 |
|
413 void |
|
414 MetroAppShell::NativeCallback() |
|
415 { |
|
416 NS_ASSERTION(NS_IsMainThread(), "Native callbacks must be on the metro main thread"); |
|
417 |
|
418 // We shouldn't process native events during xpcom shutdown - this can |
|
419 // trigger unexpected xpcom event dispatching for the main thread when |
|
420 // the thread manager is in the process of shutting down non-main threads, |
|
421 // resulting in shutdown hangs. |
|
422 if (MetroApp::sGeckoShuttingDown) { |
|
423 return; |
|
424 } |
|
425 |
|
426 NativeEventCallback(); |
|
427 } |
|
428 |
|
429 // static |
|
430 LRESULT CALLBACK |
|
431 MetroAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
|
432 { |
|
433 if (uMsg == sAppShellGeckoMsgId) { |
|
434 MetroAppShell *as = reinterpret_cast<MetroAppShell *>(lParam); |
|
435 as->NativeCallback(); |
|
436 NS_RELEASE(as); |
|
437 return TRUE; |
|
438 } |
|
439 return DefWindowProc(hwnd, uMsg, wParam, lParam); |
|
440 } |
|
441 |
|
442 void |
|
443 MetroAppShell::ScheduleNativeEventCallback() |
|
444 { |
|
445 NS_ADDREF_THIS(); |
|
446 PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this)); |
|
447 } |
|
448 |
|
449 void |
|
450 MetroAppShell::DoProcessMoreGeckoEvents() |
|
451 { |
|
452 ScheduleNativeEventCallback(); |
|
453 } |
|
454 |
|
455 static HANDLE |
|
456 PowerCreateRequestDyn(REASON_CONTEXT *context) |
|
457 { |
|
458 typedef HANDLE (WINAPI * PowerCreateRequestPtr)(REASON_CONTEXT *context); |
|
459 static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); |
|
460 static PowerCreateRequestPtr powerCreateRequest = |
|
461 (PowerCreateRequestPtr)GetProcAddress(kernel32, "PowerCreateRequest"); |
|
462 if (!powerCreateRequest) |
|
463 return INVALID_HANDLE_VALUE; |
|
464 return powerCreateRequest(context); |
|
465 } |
|
466 |
|
467 static BOOL |
|
468 PowerClearRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType) |
|
469 { |
|
470 typedef BOOL (WINAPI * PowerClearRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType); |
|
471 static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); |
|
472 static PowerClearRequestPtr powerClearRequest = |
|
473 (PowerClearRequestPtr)GetProcAddress(kernel32, "PowerClearRequest"); |
|
474 if (!powerClearRequest) |
|
475 return FALSE; |
|
476 return powerClearRequest(powerRequest, requestType); |
|
477 } |
|
478 |
|
479 static BOOL |
|
480 PowerSetRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType) |
|
481 { |
|
482 typedef BOOL (WINAPI * PowerSetRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType); |
|
483 static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); |
|
484 static PowerSetRequestPtr powerSetRequest = |
|
485 (PowerSetRequestPtr)GetProcAddress(kernel32, "PowerSetRequest"); |
|
486 if (!powerSetRequest) |
|
487 return FALSE; |
|
488 return powerSetRequest(powerRequest, requestType); |
|
489 } |
|
490 |
|
491 NS_IMETHODIMP |
|
492 MetroAppShell::Observe(nsISupports *subject, const char *topic, |
|
493 const char16_t *data) |
|
494 { |
|
495 NS_ENSURE_ARG_POINTER(topic); |
|
496 if (!strcmp(topic, "dl-start")) { |
|
497 if (mPowerRequestCount++ == 0) { |
|
498 WinUtils::Log("Download started - Disallowing suspend"); |
|
499 REASON_CONTEXT context; |
|
500 context.Version = POWER_REQUEST_CONTEXT_VERSION; |
|
501 context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; |
|
502 context.Reason.SimpleReasonString = L"downloading"; |
|
503 mPowerRequest.own(PowerCreateRequestDyn(&context)); |
|
504 PowerSetRequestDyn(mPowerRequest, PowerRequestExecutionRequired); |
|
505 } |
|
506 return NS_OK; |
|
507 } else if (!strcmp(topic, "dl-done") || |
|
508 !strcmp(topic, "dl-cancel") || |
|
509 !strcmp(topic, "dl-failed")) { |
|
510 if (--mPowerRequestCount == 0 && mPowerRequest) { |
|
511 WinUtils::Log("All downloads ended - Allowing suspend"); |
|
512 PowerClearRequestDyn(mPowerRequest, PowerRequestExecutionRequired); |
|
513 mPowerRequest.reset(); |
|
514 } |
|
515 return NS_OK; |
|
516 } |
|
517 |
|
518 return nsBaseAppShell::Observe(subject, topic, data); |
|
519 } |