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: 2; 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 <stdio.h>
7 #include "nsIDragService.h"
8 #include "nsWidgetsCID.h"
9 #include "nsNativeDragTarget.h"
10 #include "nsDragService.h"
11 #include "nsIServiceManager.h"
12 #include "nsIDOMNode.h"
13 #include "nsCOMPtr.h"
15 #include "nsIWidget.h"
16 #include "nsWindow.h"
17 #include "nsClipboard.h"
18 #include "KeyboardLayout.h"
20 #include "mozilla/MouseEvents.h"
22 using namespace mozilla;
23 using namespace mozilla::widget;
25 /* Define Interface IDs */
26 static NS_DEFINE_IID(kIDragServiceIID, NS_IDRAGSERVICE_IID);
28 // This is cached for Leave notification
29 static POINTL gDragLastPoint;
31 /*
32 * class nsNativeDragTarget
33 */
34 nsNativeDragTarget::nsNativeDragTarget(nsIWidget * aWidget)
35 : m_cRef(0),
36 mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK),
37 mEffectsPreferred(DROPEFFECT_NONE),
38 mTookOwnRef(false), mWidget(aWidget), mDropTargetHelper(nullptr)
39 {
40 static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
42 mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW);
44 /*
45 * Create/Get the DragService that we have implemented
46 */
47 CallGetService(kCDragServiceCID, &mDragService);
48 }
50 nsNativeDragTarget::~nsNativeDragTarget()
51 {
52 NS_RELEASE(mDragService);
54 if (mDropTargetHelper) {
55 mDropTargetHelper->Release();
56 mDropTargetHelper = nullptr;
57 }
58 }
60 // IUnknown methods - see iunknown.h for documentation
61 STDMETHODIMP
62 nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv)
63 {
64 *ppv=nullptr;
66 if (IID_IUnknown == riid || IID_IDropTarget == riid)
67 *ppv=this;
69 if (nullptr!=*ppv) {
70 ((LPUNKNOWN)*ppv)->AddRef();
71 return S_OK;
72 }
74 return E_NOINTERFACE;
75 }
77 STDMETHODIMP_(ULONG)
78 nsNativeDragTarget::AddRef(void)
79 {
80 ++m_cRef;
81 NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this));
82 return m_cRef;
83 }
85 STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void)
86 {
87 --m_cRef;
88 NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget");
89 if (0 != m_cRef)
90 return m_cRef;
92 delete this;
93 return 0;
94 }
96 void
97 nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState, LPDWORD pdwEffect,
98 uint32_t * aGeckoAction)
99 {
100 // If a window is disabled or a modal window is on top of it
101 // (which implies it is disabled), then we should not allow dropping.
102 if (!mWidget->IsEnabled()) {
103 *pdwEffect = DROPEFFECT_NONE;
104 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
105 return;
106 }
108 // If the user explicitly uses a modifier key, they want the associated action
109 // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY
110 DWORD desiredEffect = DROPEFFECT_NONE;
111 if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) {
112 desiredEffect = DROPEFFECT_LINK;
113 } else if (grfKeyState & MK_SHIFT) {
114 desiredEffect = DROPEFFECT_MOVE;
115 } else if (grfKeyState & MK_CONTROL) {
116 desiredEffect = DROPEFFECT_COPY;
117 }
119 // Determine the desired effect from what is allowed and preferred.
120 if (!(desiredEffect &= mEffectsAllowed)) {
121 // No modifier key effect is set which is also allowed, check
122 // the preference of the data.
123 desiredEffect = mEffectsPreferred & mEffectsAllowed;
124 if (!desiredEffect) {
125 // No preference is set, so just fall back to the allowed effect itself
126 desiredEffect = mEffectsAllowed;
127 }
128 }
130 // Otherwise we should specify the first available effect
131 // from MOVE, COPY, or LINK.
132 if (desiredEffect & DROPEFFECT_MOVE) {
133 *pdwEffect = DROPEFFECT_MOVE;
134 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE;
135 } else if (desiredEffect & DROPEFFECT_COPY) {
136 *pdwEffect = DROPEFFECT_COPY;
137 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY;
138 } else if (desiredEffect & DROPEFFECT_LINK) {
139 *pdwEffect = DROPEFFECT_LINK;
140 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK;
141 } else {
142 *pdwEffect = DROPEFFECT_NONE;
143 *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
144 }
145 }
147 inline
148 bool
149 IsKeyDown(char key)
150 {
151 return GetKeyState(key) < 0;
152 }
154 void
155 nsNativeDragTarget::DispatchDragDropEvent(uint32_t aEventType, POINTL aPT)
156 {
157 nsEventStatus status;
158 WidgetDragEvent event(true, aEventType, mWidget);
160 nsWindow * win = static_cast<nsWindow *>(mWidget);
161 win->InitEvent(event);
162 POINT cpos;
164 cpos.x = aPT.x;
165 cpos.y = aPT.y;
167 if (mHWnd != nullptr) {
168 ::ScreenToClient(mHWnd, &cpos);
169 event.refPoint.x = cpos.x;
170 event.refPoint.y = cpos.y;
171 } else {
172 event.refPoint.x = 0;
173 event.refPoint.y = 0;
174 }
176 ModifierKeyState modifierKeyState;
177 modifierKeyState.InitInputEvent(event);
179 event.inputSource = static_cast<nsBaseDragService*>(mDragService)->GetInputSource();
181 mWidget->DispatchEvent(&event, status);
182 }
184 void
185 nsNativeDragTarget::ProcessDrag(uint32_t aEventType,
186 DWORD grfKeyState,
187 POINTL ptl,
188 DWORD* pdwEffect)
189 {
190 // Before dispatching the event make sure we have the correct drop action set
191 uint32_t geckoAction;
192 GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction);
194 // Set the current action into the Gecko specific type
195 nsCOMPtr<nsIDragSession> currSession;
196 mDragService->GetCurrentSession(getter_AddRefs(currSession));
197 if (!currSession) {
198 return;
199 }
201 currSession->SetDragAction(geckoAction);
203 // Dispatch the event into Gecko
204 DispatchDragDropEvent(aEventType, ptl);
206 if (aEventType != NS_DRAGDROP_DROP) {
207 // Get the cached drag effect from the drag service, the data member should
208 // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on
209 // drags.
210 bool canDrop;
211 currSession->GetCanDrop(&canDrop);
212 if (!canDrop) {
213 *pdwEffect = DROPEFFECT_NONE;
214 }
215 }
217 // Clear the cached value
218 currSession->SetCanDrop(false);
219 }
221 // IDropTarget methods
222 STDMETHODIMP
223 nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource,
224 DWORD grfKeyState,
225 POINTL ptl,
226 DWORD* pdwEffect)
227 {
228 if (!mDragService) {
229 return E_FAIL;
230 }
232 mEffectsAllowed = *pdwEffect;
233 AddLinkSupportIfCanBeGenerated(pIDataSource);
235 // Drag and drop image helper
236 if (GetDropTargetHelper()) {
237 POINT pt = { ptl.x, ptl.y };
238 GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect);
239 }
241 // save a ref to this, in case the window is destroyed underneath us
242 NS_ASSERTION(!mTookOwnRef, "own ref already taken!");
243 this->AddRef();
244 mTookOwnRef = true;
246 // tell the drag service about this drag (it may have come from an
247 // outside app).
248 mDragService->StartDragSession();
250 void* tempOutData = nullptr;
251 uint32_t tempDataLen = 0;
252 nsresult loadResult = nsClipboard::GetNativeDataOffClipboard(
253 pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), nullptr, &tempOutData, &tempDataLen);
254 if (NS_SUCCEEDED(loadResult) && tempOutData) {
255 mEffectsPreferred = *((DWORD*)tempOutData);
256 nsMemory::Free(tempOutData);
257 } else {
258 // We have no preference if we can't obtain it
259 mEffectsPreferred = DROPEFFECT_NONE;
260 }
262 // Set the native data object into drag service
263 //
264 // This cast is ok because in the constructor we created a
265 // the actual implementation we wanted, so we know this is
266 // a nsDragService. It should be a private interface, though.
267 nsDragService * winDragService =
268 static_cast<nsDragService *>(mDragService);
269 winDragService->SetIDataObject(pIDataSource);
271 // Now process the native drag state and then dispatch the event
272 ProcessDrag(NS_DRAGDROP_ENTER, grfKeyState, ptl, pdwEffect);
274 return S_OK;
275 }
277 void
278 nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(LPDATAOBJECT aIDataSource)
279 {
280 // If we don't have a link effect, but we can generate one, fix the
281 // drop effect to include it.
282 if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) {
283 if (S_OK == ::OleQueryLinkFromData(aIDataSource)) {
284 mEffectsAllowed |= DROPEFFECT_LINK;
285 }
286 }
287 }
289 STDMETHODIMP
290 nsNativeDragTarget::DragOver(DWORD grfKeyState,
291 POINTL ptl,
292 LPDWORD pdwEffect)
293 {
294 if (!mDragService) {
295 return E_FAIL;
296 }
298 // If a LINK effect could be generated previously from a DragEnter(),
299 // then we should include it as an allowed effect.
300 mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK);
302 nsCOMPtr<nsIDragSession> currentDragSession;
303 mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
304 if (!currentDragSession) {
305 return S_OK; // Drag was canceled.
306 }
308 // without the AddRef() |this| can get destroyed in an event handler
309 this->AddRef();
311 // Drag and drop image helper
312 if (GetDropTargetHelper()) {
313 POINT pt = { ptl.x, ptl.y };
314 GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
315 }
317 mDragService->FireDragEventAtSource(NS_DRAGDROP_DRAG);
318 // Now process the native drag state and then dispatch the event
319 ProcessDrag(NS_DRAGDROP_OVER, grfKeyState, ptl, pdwEffect);
321 this->Release();
323 return S_OK;
324 }
326 STDMETHODIMP
327 nsNativeDragTarget::DragLeave()
328 {
329 if (!mDragService) {
330 return E_FAIL;
331 }
333 // Drag and drop image helper
334 if (GetDropTargetHelper()) {
335 GetDropTargetHelper()->DragLeave();
336 }
338 // dispatch the event into Gecko
339 DispatchDragDropEvent(NS_DRAGDROP_EXIT, gDragLastPoint);
341 nsCOMPtr<nsIDragSession> currentDragSession;
342 mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
344 if (currentDragSession) {
345 nsCOMPtr<nsIDOMNode> sourceNode;
346 currentDragSession->GetSourceNode(getter_AddRefs(sourceNode));
348 if (!sourceNode) {
349 // We're leaving a window while doing a drag that was
350 // initiated in a different app. End the drag session, since
351 // we're done with it for now (until the user drags back into
352 // mozilla).
353 mDragService->EndDragSession(false);
354 }
355 }
357 // release the ref that was taken in DragEnter
358 NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
359 if (mTookOwnRef) {
360 this->Release();
361 mTookOwnRef = false;
362 }
364 return S_OK;
365 }
367 void
368 nsNativeDragTarget::DragCancel()
369 {
370 // Cancel the drag session if we did DragEnter.
371 if (mTookOwnRef) {
372 if (GetDropTargetHelper()) {
373 GetDropTargetHelper()->DragLeave();
374 }
375 if (mDragService) {
376 mDragService->EndDragSession(false);
377 }
378 this->Release(); // matching the AddRef in DragEnter
379 mTookOwnRef = false;
380 }
381 }
383 STDMETHODIMP
384 nsNativeDragTarget::Drop(LPDATAOBJECT pData,
385 DWORD grfKeyState,
386 POINTL aPT,
387 LPDWORD pdwEffect)
388 {
389 if (!mDragService) {
390 return E_FAIL;
391 }
393 mEffectsAllowed = *pdwEffect;
394 AddLinkSupportIfCanBeGenerated(pData);
396 // Drag and drop image helper
397 if (GetDropTargetHelper()) {
398 POINT pt = { aPT.x, aPT.y };
399 GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect);
400 }
402 // Set the native data object into the drag service
403 //
404 // This cast is ok because in the constructor we created a
405 // the actual implementation we wanted, so we know this is
406 // a nsDragService (but it should still be a private interface)
407 nsDragService* winDragService = static_cast<nsDragService*>(mDragService);
408 winDragService->SetIDataObject(pData);
410 // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects.
411 // We use strong refs to prevent it from destroying these:
412 nsRefPtr<nsNativeDragTarget> kungFuDeathGrip = this;
413 nsCOMPtr<nsIDragService> serv = mDragService;
415 // Now process the native drag state and then dispatch the event
416 ProcessDrag(NS_DRAGDROP_DROP, grfKeyState, aPT, pdwEffect);
418 nsCOMPtr<nsIDragSession> currentDragSession;
419 serv->GetCurrentSession(getter_AddRefs(currentDragSession));
420 if (!currentDragSession) {
421 return S_OK; // DragCancel() was called.
422 }
424 // Let the win drag service know whether this session experienced
425 // a drop event within the application. Drop will not oocur if the
426 // drop landed outside the app. (used in tab tear off, bug 455884)
427 winDragService->SetDroppedLocal();
429 // tell the drag service we're done with the session
430 // Use GetMessagePos to get the position of the mouse at the last message
431 // seen by the event loop. (Bug 489729)
432 DWORD pos = ::GetMessagePos();
433 POINT cpos;
434 cpos.x = GET_X_LPARAM(pos);
435 cpos.y = GET_Y_LPARAM(pos);
436 winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y));
437 serv->EndDragSession(true);
439 // release the ref that was taken in DragEnter
440 NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
441 if (mTookOwnRef) {
442 this->Release();
443 mTookOwnRef = false;
444 }
446 return S_OK;
447 }
449 /**
450 * By lazy loading mDropTargetHelper we save 50-70ms of startup time
451 * which is ~5% of startup time.
452 */
453 IDropTargetHelper*
454 nsNativeDragTarget::GetDropTargetHelper()
455 {
456 if (!mDropTargetHelper) {
457 CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
458 IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper);
459 }
461 return mDropTargetHelper;
462 }