Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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/. */
6 #include "MetroAppShell.h"
8 #include "mozilla/AutoRestore.h"
9 #include "mozilla/TimeStamp.h"
10 #include "mozilla/widget/AudioSession.h"
12 #include "nsIObserverService.h"
13 #include "nsIAppStartup.h"
14 #include "nsToolkitCompsCID.h"
15 #include "nsIPowerManagerService.h"
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"
26 #include <shellapi.h>
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;
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
43 namespace mozilla {
44 namespace widget {
45 namespace winrt {
46 extern ComPtr<MetroApp> sMetroApp;
47 } } }
49 namespace mozilla {
50 namespace widget {
51 // pulled from win32 app shell
52 extern UINT sAppShellGeckoMsgId;
53 } }
55 static ComPtr<ICoreWindowStatic> sCoreStatic;
56 static bool sIsDispatching = false;
57 static bool sShouldPurgeThreadQueue = false;
58 static bool sBlockNativeEvents = false;
59 static TimeStamp sPurgeThreadQueueStart;
61 MetroAppShell::~MetroAppShell()
62 {
63 if (mEventWnd) {
64 SendMessage(mEventWnd, WM_CLOSE, 0, 0);
65 }
66 }
68 nsresult
69 MetroAppShell::Init()
70 {
71 LogFunction();
73 WNDCLASSW wc;
74 HINSTANCE module = GetModuleHandle(nullptr);
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 }
91 mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow",
92 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr);
93 NS_ENSURE_STATE(mEventWnd);
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 }
105 return nsBaseAppShell::Init();
106 }
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 }
116 typedef BOOL (WINAPI* SHFn)(IShellItem *psi, REFIID riid, void **ppv);
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 }
126 FreeLibrary(shell32DLL);
127 return hr;
128 }
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}};
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;
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;
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;
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;
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;
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;
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;
185 // Run the default browser through the CEH
186 return executeCommand->Execute();
187 }
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 }
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 }
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;
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 }
248 nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID));
249 bool restartingInMetro = false, restartingInDesktop = false;
251 if (!appStartup || NS_FAILED(appStartup->GetRestarting(&restartingInDesktop))) {
252 WinUtils::Log("appStartup->GetRestarting() unsuccessful");
253 }
255 if (appStartup && NS_SUCCEEDED(appStartup->GetRestartingTouchEnvironment(&restartingInMetro)) &&
256 restartingInMetro) {
257 restartingInDesktop = false;
258 }
260 // This calls XRE_metroShutdown() in xre. Shuts down gecko, including
261 // releasing the profile, and destroys MessagePump.
262 sMetroApp->Shutdown();
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 }
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 }
294 return rv;
295 }
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;
304 // If we're dispatching native events, wait until the dispatcher is
305 // off the stack.
306 if (sIsDispatching) {
307 return;
308 }
310 // Safe to process pending events now
311 DispatchAllGeckoEvents();
312 }
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 }
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 }
332 NS_ASSERTION(NS_IsMainThread(), "DispatchAllGeckoEvents should be called on the main thread");
334 sShouldPurgeThreadQueue = false;
335 sPurgeThreadQueueStart = TimeStamp::Now();
337 sBlockNativeEvents = true;
338 nsIThread *thread = NS_GetCurrentThread();
339 NS_ProcessPendingEvents(thread, PURGE_MAX_TIMEOUT);
340 sBlockNativeEvents = false;
341 }
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 }
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 }
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 }
374 {
375 AutoRestore<bool> dispatching(sIsDispatching);
376 sIsDispatching = true;
377 ProcessNativeEvents(CoreProcessEventsOption::CoreProcessEventsOption_ProcessOneIfPresent);
378 }
380 DispatchAllGeckoEvents();
382 return !!HIWORD(::GetQueueStatus(MOZ_QS_ALLEVENT));
383 }
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 }
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 }
413 void
414 MetroAppShell::NativeCallback()
415 {
416 NS_ASSERTION(NS_IsMainThread(), "Native callbacks must be on the metro main thread");
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 }
426 NativeEventCallback();
427 }
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 }
442 void
443 MetroAppShell::ScheduleNativeEventCallback()
444 {
445 NS_ADDREF_THIS();
446 PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this));
447 }
449 void
450 MetroAppShell::DoProcessMoreGeckoEvents()
451 {
452 ScheduleNativeEventCallback();
453 }
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 }
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 }
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 }
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 }
518 return nsBaseAppShell::Observe(subject, topic, data);
519 }