editor/libeditor/text/nsPlaintextDataTransfer.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "mozilla/ArrayUtils.h"
michael@0 7 #include "mozilla/MouseEvents.h"
michael@0 8 #include "nsAString.h"
michael@0 9 #include "nsCOMPtr.h"
michael@0 10 #include "nsCRT.h"
michael@0 11 #include "nsComponentManagerUtils.h"
michael@0 12 #include "nsContentUtils.h"
michael@0 13 #include "nsDebug.h"
michael@0 14 #include "nsEditor.h"
michael@0 15 #include "nsEditorUtils.h"
michael@0 16 #include "nsError.h"
michael@0 17 #include "nsIClipboard.h"
michael@0 18 #include "nsIContent.h"
michael@0 19 #include "nsIDOMDataTransfer.h"
michael@0 20 #include "nsIDOMDocument.h"
michael@0 21 #include "nsIDOMDragEvent.h"
michael@0 22 #include "nsIDOMEvent.h"
michael@0 23 #include "nsIDOMNode.h"
michael@0 24 #include "nsIDOMRange.h"
michael@0 25 #include "nsIDOMUIEvent.h"
michael@0 26 #include "nsIDocument.h"
michael@0 27 #include "nsIDragService.h"
michael@0 28 #include "nsIDragSession.h"
michael@0 29 #include "nsIEditor.h"
michael@0 30 #include "nsIEditorIMESupport.h"
michael@0 31 #include "nsIDocShell.h"
michael@0 32 #include "nsIDocShellTreeItem.h"
michael@0 33 #include "nsIPrincipal.h"
michael@0 34 #include "nsIFormControl.h"
michael@0 35 #include "nsIPlaintextEditor.h"
michael@0 36 #include "nsISelection.h"
michael@0 37 #include "nsISupportsPrimitives.h"
michael@0 38 #include "nsITransferable.h"
michael@0 39 #include "nsIVariant.h"
michael@0 40 #include "nsLiteralString.h"
michael@0 41 #include "nsPlaintextEditor.h"
michael@0 42 #include "nsSelectionState.h"
michael@0 43 #include "nsServiceManagerUtils.h"
michael@0 44 #include "nsString.h"
michael@0 45 #include "nsXPCOM.h"
michael@0 46 #include "nscore.h"
michael@0 47
michael@0 48 class nsILoadContext;
michael@0 49 class nsISupports;
michael@0 50
michael@0 51 using namespace mozilla;
michael@0 52 using namespace mozilla::dom;
michael@0 53
michael@0 54 NS_IMETHODIMP nsPlaintextEditor::PrepareTransferable(nsITransferable **transferable)
michael@0 55 {
michael@0 56 // Create generic Transferable for getting the data
michael@0 57 nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", transferable);
michael@0 58 NS_ENSURE_SUCCESS(rv, rv);
michael@0 59
michael@0 60 // Get the nsITransferable interface for getting the data from the clipboard
michael@0 61 if (transferable) {
michael@0 62 nsCOMPtr<nsIDocument> destdoc = GetDocument();
michael@0 63 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
michael@0 64 (*transferable)->Init(loadContext);
michael@0 65
michael@0 66 (*transferable)->AddDataFlavor(kUnicodeMime);
michael@0 67 (*transferable)->AddDataFlavor(kMozTextInternal);
michael@0 68 };
michael@0 69 return NS_OK;
michael@0 70 }
michael@0 71
michael@0 72 nsresult nsPlaintextEditor::InsertTextAt(const nsAString &aStringToInsert,
michael@0 73 nsIDOMNode *aDestinationNode,
michael@0 74 int32_t aDestOffset,
michael@0 75 bool aDoDeleteSelection)
michael@0 76 {
michael@0 77 if (aDestinationNode)
michael@0 78 {
michael@0 79 nsresult res;
michael@0 80 nsCOMPtr<nsISelection>selection;
michael@0 81 res = GetSelection(getter_AddRefs(selection));
michael@0 82 NS_ENSURE_SUCCESS(res, res);
michael@0 83
michael@0 84 nsCOMPtr<nsIDOMNode> targetNode = aDestinationNode;
michael@0 85 int32_t targetOffset = aDestOffset;
michael@0 86
michael@0 87 if (aDoDeleteSelection)
michael@0 88 {
michael@0 89 // Use an auto tracker so that our drop point is correctly
michael@0 90 // positioned after the delete.
michael@0 91 nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
michael@0 92 res = DeleteSelection(eNone, eStrip);
michael@0 93 NS_ENSURE_SUCCESS(res, res);
michael@0 94 }
michael@0 95
michael@0 96 res = selection->Collapse(targetNode, targetOffset);
michael@0 97 NS_ENSURE_SUCCESS(res, res);
michael@0 98 }
michael@0 99
michael@0 100 return InsertText(aStringToInsert);
michael@0 101 }
michael@0 102
michael@0 103 NS_IMETHODIMP nsPlaintextEditor::InsertTextFromTransferable(nsITransferable *aTransferable,
michael@0 104 nsIDOMNode *aDestinationNode,
michael@0 105 int32_t aDestOffset,
michael@0 106 bool aDoDeleteSelection)
michael@0 107 {
michael@0 108 nsresult rv = NS_OK;
michael@0 109 char* bestFlavor = nullptr;
michael@0 110 nsCOMPtr<nsISupports> genericDataObj;
michael@0 111 uint32_t len = 0;
michael@0 112 if (NS_SUCCEEDED(aTransferable->GetAnyTransferData(&bestFlavor, getter_AddRefs(genericDataObj), &len))
michael@0 113 && bestFlavor && (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
michael@0 114 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)))
michael@0 115 {
michael@0 116 nsAutoTxnsConserveSelection dontSpazMySelection(this);
michael@0 117 nsCOMPtr<nsISupportsString> textDataObj ( do_QueryInterface(genericDataObj) );
michael@0 118 if (textDataObj && len > 0)
michael@0 119 {
michael@0 120 nsAutoString stuffToPaste;
michael@0 121 textDataObj->GetData(stuffToPaste);
michael@0 122 NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
michael@0 123
michael@0 124 // Sanitize possible carriage returns in the string to be inserted
michael@0 125 nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
michael@0 126
michael@0 127 nsAutoEditBatch beginBatching(this);
michael@0 128 rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 129 }
michael@0 130 }
michael@0 131 NS_Free(bestFlavor);
michael@0 132
michael@0 133 // Try to scroll the selection into view if the paste/drop succeeded
michael@0 134
michael@0 135 if (NS_SUCCEEDED(rv))
michael@0 136 ScrollSelectionIntoView(false);
michael@0 137
michael@0 138 return rv;
michael@0 139 }
michael@0 140
michael@0 141 nsresult nsPlaintextEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer,
michael@0 142 int32_t aIndex,
michael@0 143 nsIDOMDocument *aSourceDoc,
michael@0 144 nsIDOMNode *aDestinationNode,
michael@0 145 int32_t aDestOffset,
michael@0 146 bool aDoDeleteSelection)
michael@0 147 {
michael@0 148 nsCOMPtr<nsIVariant> data;
michael@0 149 aDataTransfer->MozGetDataAt(NS_LITERAL_STRING("text/plain"), aIndex,
michael@0 150 getter_AddRefs(data));
michael@0 151 if (data) {
michael@0 152 nsAutoString insertText;
michael@0 153 data->GetAsAString(insertText);
michael@0 154 nsContentUtils::PlatformToDOMLineBreaks(insertText);
michael@0 155
michael@0 156 nsAutoEditBatch beginBatching(this);
michael@0 157 return InsertTextAt(insertText, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 158 }
michael@0 159
michael@0 160 return NS_OK;
michael@0 161 }
michael@0 162
michael@0 163 nsresult nsPlaintextEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
michael@0 164 {
michael@0 165 ForceCompositionEnd();
michael@0 166
michael@0 167 nsCOMPtr<nsIDOMDragEvent> dragEvent(do_QueryInterface(aDropEvent));
michael@0 168 NS_ENSURE_TRUE(dragEvent, NS_ERROR_FAILURE);
michael@0 169
michael@0 170 nsCOMPtr<nsIDOMDataTransfer> domDataTransfer;
michael@0 171 dragEvent->GetDataTransfer(getter_AddRefs(domDataTransfer));
michael@0 172 nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(domDataTransfer);
michael@0 173 NS_ENSURE_TRUE(dataTransfer, NS_ERROR_FAILURE);
michael@0 174
michael@0 175 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
michael@0 176 NS_ASSERTION(dragSession, "No drag session");
michael@0 177
michael@0 178 nsCOMPtr<nsIDOMNode> sourceNode;
michael@0 179 dataTransfer->GetMozSourceNode(getter_AddRefs(sourceNode));
michael@0 180
michael@0 181 nsCOMPtr<nsIDOMDocument> srcdomdoc;
michael@0 182 if (sourceNode) {
michael@0 183 sourceNode->GetOwnerDocument(getter_AddRefs(srcdomdoc));
michael@0 184 NS_ENSURE_TRUE(sourceNode, NS_ERROR_FAILURE);
michael@0 185 }
michael@0 186
michael@0 187 if (nsContentUtils::CheckForSubFrameDrop(dragSession,
michael@0 188 aDropEvent->GetInternalNSEvent()->AsDragEvent())) {
michael@0 189 // Don't allow drags from subframe documents with different origins than
michael@0 190 // the drop destination.
michael@0 191 if (srcdomdoc && !IsSafeToInsertData(srcdomdoc))
michael@0 192 return NS_OK;
michael@0 193 }
michael@0 194
michael@0 195 // Current doc is destination
michael@0 196 nsCOMPtr<nsIDOMDocument> destdomdoc = GetDOMDocument();
michael@0 197 NS_ENSURE_TRUE(destdomdoc, NS_ERROR_NOT_INITIALIZED);
michael@0 198
michael@0 199 uint32_t numItems = 0;
michael@0 200 nsresult rv = dataTransfer->GetMozItemCount(&numItems);
michael@0 201 NS_ENSURE_SUCCESS(rv, rv);
michael@0 202 if (numItems < 1) return NS_ERROR_FAILURE; // nothing to drop?
michael@0 203
michael@0 204 // Combine any deletion and drop insertion into one transaction
michael@0 205 nsAutoEditBatch beginBatching(this);
michael@0 206
michael@0 207 bool deleteSelection = false;
michael@0 208
michael@0 209 // We have to figure out whether to delete and relocate caret only once
michael@0 210 // Parent and offset are under the mouse cursor
michael@0 211 nsCOMPtr<nsIDOMUIEvent> uiEvent = do_QueryInterface(aDropEvent);
michael@0 212 NS_ENSURE_TRUE(uiEvent, NS_ERROR_FAILURE);
michael@0 213
michael@0 214 nsCOMPtr<nsIDOMNode> newSelectionParent;
michael@0 215 rv = uiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
michael@0 216 NS_ENSURE_SUCCESS(rv, rv);
michael@0 217 NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
michael@0 218
michael@0 219 int32_t newSelectionOffset;
michael@0 220 rv = uiEvent->GetRangeOffset(&newSelectionOffset);
michael@0 221 NS_ENSURE_SUCCESS(rv, rv);
michael@0 222
michael@0 223 nsCOMPtr<nsISelection> selection;
michael@0 224 rv = GetSelection(getter_AddRefs(selection));
michael@0 225 NS_ENSURE_SUCCESS(rv, rv);
michael@0 226 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
michael@0 227
michael@0 228 bool isCollapsed = selection->Collapsed();
michael@0 229
michael@0 230 // Only the nsHTMLEditor::FindUserSelectAllNode returns a node.
michael@0 231 nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
michael@0 232 if (userSelectNode)
michael@0 233 {
michael@0 234 // The drop is happening over a "-moz-user-select: all"
michael@0 235 // subtree so make sure the content we insert goes before
michael@0 236 // the root of the subtree.
michael@0 237 //
michael@0 238 // XXX: Note that inserting before the subtree matches the
michael@0 239 // current behavior when dropping on top of an image.
michael@0 240 // The decision for dropping before or after the
michael@0 241 // subtree should really be done based on coordinates.
michael@0 242
michael@0 243 newSelectionParent = GetNodeLocation(userSelectNode, &newSelectionOffset);
michael@0 244
michael@0 245 NS_ENSURE_TRUE(newSelectionParent, NS_ERROR_FAILURE);
michael@0 246 }
michael@0 247
michael@0 248 // Check if mouse is in the selection
michael@0 249 // if so, jump through some hoops to determine if mouse is over selection (bail)
michael@0 250 // and whether user wants to copy selection or delete it
michael@0 251 if (!isCollapsed)
michael@0 252 {
michael@0 253 // We never have to delete if selection is already collapsed
michael@0 254 bool cursorIsInSelection = false;
michael@0 255
michael@0 256 int32_t rangeCount;
michael@0 257 rv = selection->GetRangeCount(&rangeCount);
michael@0 258 NS_ENSURE_SUCCESS(rv, rv);
michael@0 259
michael@0 260 for (int32_t j = 0; j < rangeCount; j++)
michael@0 261 {
michael@0 262 nsCOMPtr<nsIDOMRange> range;
michael@0 263 rv = selection->GetRangeAt(j, getter_AddRefs(range));
michael@0 264 if (NS_FAILED(rv) || !range)
michael@0 265 continue; // don't bail yet, iterate through them all
michael@0 266
michael@0 267 rv = range->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
michael@0 268 if (cursorIsInSelection)
michael@0 269 break;
michael@0 270 }
michael@0 271
michael@0 272 if (cursorIsInSelection)
michael@0 273 {
michael@0 274 // Dragging within same doc can't drop on itself -- leave!
michael@0 275 if (srcdomdoc == destdomdoc)
michael@0 276 return NS_OK;
michael@0 277
michael@0 278 // Dragging from another window onto a selection
michael@0 279 // XXX Decision made to NOT do this,
michael@0 280 // note that 4.x does replace if dropped on
michael@0 281 //deleteSelection = true;
michael@0 282 }
michael@0 283 else
michael@0 284 {
michael@0 285 // We are NOT over the selection
michael@0 286 if (srcdomdoc == destdomdoc)
michael@0 287 {
michael@0 288 // Within the same doc: delete if user doesn't want to copy
michael@0 289 uint32_t dropEffect;
michael@0 290 dataTransfer->GetDropEffectInt(&dropEffect);
michael@0 291 deleteSelection = !(dropEffect & nsIDragService::DRAGDROP_ACTION_COPY);
michael@0 292 }
michael@0 293 else
michael@0 294 {
michael@0 295 // Different source doc: Don't delete
michael@0 296 deleteSelection = false;
michael@0 297 }
michael@0 298 }
michael@0 299 }
michael@0 300
michael@0 301 if (IsPlaintextEditor()) {
michael@0 302 nsCOMPtr<nsIContent> content = do_QueryInterface(newSelectionParent);
michael@0 303 while (content) {
michael@0 304 nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content));
michael@0 305 if (formControl && !formControl->AllowDrop()) {
michael@0 306 // Don't allow dropping into a form control that doesn't allow being
michael@0 307 // dropped into.
michael@0 308 return NS_OK;
michael@0 309 }
michael@0 310 content = content->GetParent();
michael@0 311 }
michael@0 312 }
michael@0 313
michael@0 314 for (uint32_t i = 0; i < numItems; ++i) {
michael@0 315 InsertFromDataTransfer(dataTransfer, i, srcdomdoc, newSelectionParent,
michael@0 316 newSelectionOffset, deleteSelection);
michael@0 317 }
michael@0 318
michael@0 319 if (NS_SUCCEEDED(rv))
michael@0 320 ScrollSelectionIntoView(false);
michael@0 321
michael@0 322 return rv;
michael@0 323 }
michael@0 324
michael@0 325 NS_IMETHODIMP nsPlaintextEditor::Paste(int32_t aSelectionType)
michael@0 326 {
michael@0 327 if (!FireClipboardEvent(NS_PASTE, aSelectionType))
michael@0 328 return NS_OK;
michael@0 329
michael@0 330 // Get Clipboard Service
michael@0 331 nsresult rv;
michael@0 332 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 333 if ( NS_FAILED(rv) )
michael@0 334 return rv;
michael@0 335
michael@0 336 // Get the nsITransferable interface for getting the data from the clipboard
michael@0 337 nsCOMPtr<nsITransferable> trans;
michael@0 338 rv = PrepareTransferable(getter_AddRefs(trans));
michael@0 339 if (NS_SUCCEEDED(rv) && trans)
michael@0 340 {
michael@0 341 // Get the Data from the clipboard
michael@0 342 if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
michael@0 343 {
michael@0 344 // handle transferable hooks
michael@0 345 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
michael@0 346 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans))
michael@0 347 return NS_OK;
michael@0 348
michael@0 349 rv = InsertTextFromTransferable(trans, nullptr, 0, true);
michael@0 350 }
michael@0 351 }
michael@0 352
michael@0 353 return rv;
michael@0 354 }
michael@0 355
michael@0 356 NS_IMETHODIMP nsPlaintextEditor::PasteTransferable(nsITransferable *aTransferable)
michael@0 357 {
michael@0 358 // Use an invalid value for the clipboard type as data comes from aTransferable
michael@0 359 // and we don't currently implement a way to put that in the data transfer yet.
michael@0 360 if (!FireClipboardEvent(NS_PASTE, -1))
michael@0 361 return NS_OK;
michael@0 362
michael@0 363 if (!IsModifiable())
michael@0 364 return NS_OK;
michael@0 365
michael@0 366 // handle transferable hooks
michael@0 367 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
michael@0 368 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable))
michael@0 369 return NS_OK;
michael@0 370
michael@0 371 return InsertTextFromTransferable(aTransferable, nullptr, 0, true);
michael@0 372 }
michael@0 373
michael@0 374 NS_IMETHODIMP nsPlaintextEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste)
michael@0 375 {
michael@0 376 NS_ENSURE_ARG_POINTER(aCanPaste);
michael@0 377 *aCanPaste = false;
michael@0 378
michael@0 379 // can't paste if readonly
michael@0 380 if (!IsModifiable())
michael@0 381 return NS_OK;
michael@0 382
michael@0 383 nsresult rv;
michael@0 384 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 385 NS_ENSURE_SUCCESS(rv, rv);
michael@0 386
michael@0 387 // the flavors that we can deal with
michael@0 388 const char* textEditorFlavors[] = { kUnicodeMime };
michael@0 389
michael@0 390 bool haveFlavors;
michael@0 391 rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
michael@0 392 ArrayLength(textEditorFlavors),
michael@0 393 aSelectionType, &haveFlavors);
michael@0 394 NS_ENSURE_SUCCESS(rv, rv);
michael@0 395
michael@0 396 *aCanPaste = haveFlavors;
michael@0 397 return NS_OK;
michael@0 398 }
michael@0 399
michael@0 400
michael@0 401 NS_IMETHODIMP nsPlaintextEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
michael@0 402 {
michael@0 403 NS_ENSURE_ARG_POINTER(aCanPaste);
michael@0 404
michael@0 405 // can't paste if readonly
michael@0 406 if (!IsModifiable()) {
michael@0 407 *aCanPaste = false;
michael@0 408 return NS_OK;
michael@0 409 }
michael@0 410
michael@0 411 // If |aTransferable| is null, assume that a paste will succeed.
michael@0 412 if (!aTransferable) {
michael@0 413 *aCanPaste = true;
michael@0 414 return NS_OK;
michael@0 415 }
michael@0 416
michael@0 417 nsCOMPtr<nsISupports> data;
michael@0 418 uint32_t dataLen;
michael@0 419 nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
michael@0 420 getter_AddRefs(data),
michael@0 421 &dataLen);
michael@0 422 if (NS_SUCCEEDED(rv) && data)
michael@0 423 *aCanPaste = true;
michael@0 424 else
michael@0 425 *aCanPaste = false;
michael@0 426
michael@0 427 return NS_OK;
michael@0 428 }
michael@0 429
michael@0 430 bool nsPlaintextEditor::IsSafeToInsertData(nsIDOMDocument* aSourceDoc)
michael@0 431 {
michael@0 432 // Try to determine whether we should use a sanitizing fragment sink
michael@0 433 bool isSafe = false;
michael@0 434
michael@0 435 nsCOMPtr<nsIDocument> destdoc = GetDocument();
michael@0 436 NS_ASSERTION(destdoc, "Where is our destination doc?");
michael@0 437 nsCOMPtr<nsIDocShellTreeItem> dsti = destdoc->GetDocShell();
michael@0 438 nsCOMPtr<nsIDocShellTreeItem> root;
michael@0 439 if (dsti)
michael@0 440 dsti->GetRootTreeItem(getter_AddRefs(root));
michael@0 441 nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(root);
michael@0 442 uint32_t appType;
michael@0 443 if (docShell && NS_SUCCEEDED(docShell->GetAppType(&appType)))
michael@0 444 isSafe = appType == nsIDocShell::APP_TYPE_EDITOR;
michael@0 445 if (!isSafe && aSourceDoc) {
michael@0 446 nsCOMPtr<nsIDocument> srcdoc = do_QueryInterface(aSourceDoc);
michael@0 447 NS_ASSERTION(srcdoc, "Where is our source doc?");
michael@0 448
michael@0 449 nsIPrincipal* srcPrincipal = srcdoc->NodePrincipal();
michael@0 450 nsIPrincipal* destPrincipal = destdoc->NodePrincipal();
michael@0 451 NS_ASSERTION(srcPrincipal && destPrincipal, "How come we don't have a principal?");
michael@0 452 srcPrincipal->Subsumes(destPrincipal, &isSafe);
michael@0 453 }
michael@0 454
michael@0 455 return isSafe;
michael@0 456 }
michael@0 457

mercurial