Reviewed by Tim Omernick
[WebKit-https.git] / WebCore / bridge / mac / FrameMac.mm
1 /*
2  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2006 Alexey Proskuryakov (ap@nypop.com)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #import "config.h"
28 #import "FrameMac.h"
29
30 #import "AXObjectCache.h"
31 #import "BeforeUnloadEvent.h"
32 #import "BlockExceptions.h"
33 #import "Chrome.h"
34 #import "Cache.h"
35 #import "ClipboardEvent.h"
36 #import "ClipboardMac.h"
37 #import "Cursor.h"
38 #import "DOMInternal.h"
39 #import "DocumentLoader.h"
40 #import "EditCommand.h"
41 #import "EditorClient.h"
42 #import "Event.h"
43 #import "EventNames.h"
44 #import "FloatRect.h"
45 #import "FontData.h"
46 #import "FoundationExtras.h"
47 #import "FrameLoadRequest.h"
48 #import "FrameLoader.h"
49 #import "FrameLoaderClient.h"
50 #import "FrameLoaderTypes.h"
51 #import "FramePrivate.h"
52 #import "FrameView.h"
53 #import "GraphicsContext.h"
54 #import "HTMLDocument.h"
55 #import "HTMLFormElement.h"
56 #import "HTMLGenericFormElement.h"
57 #import "HTMLInputElement.h"
58 #import "HTMLNames.h"
59 #import "HTMLTableCellElement.h"
60 #import "HitTestRequest.h"
61 #import "HitTestResult.h"
62 #import "KeyboardEvent.h"
63 #import "Logging.h"
64 #import "MouseEventWithHitTestResults.h"
65 #import "Page.h"
66 #import "PlatformKeyboardEvent.h"
67 #import "PlatformScrollBar.h"
68 #import "PlatformWheelEvent.h"
69 #import "Plugin.h"
70 #import "RegularExpression.h"
71 #import "RenderImage.h"
72 #import "RenderListItem.h"
73 #import "RenderPart.h"
74 #import "RenderTableCell.h"
75 #import "RenderTheme.h"
76 #import "RenderView.h"
77 #import "ResourceHandle.h"
78 #import "SystemTime.h"
79 #import "TextIterator.h"
80 #import "TextResourceDecoder.h"
81 #import "WebCoreFrameBridge.h"
82 #import "WebCoreSystemInterface.h"
83 #import "WebCoreViewFactory.h"
84 #import "WebDashboardRegion.h"
85 #import "WebScriptObjectPrivate.h"
86 #import "csshelper.h"
87 #import "htmlediting.h"
88 #import "kjs_proxy.h"
89 #import "kjs_window.h"
90 #import "visible_units.h"
91 #import <Carbon/Carbon.h>
92 #import <JavaScriptCore/NP_jsobject.h>
93 #import <JavaScriptCore/npruntime_impl.h>
94
95 #undef _webcore_TIMING
96
97 @interface NSObject (WebPlugIn)
98 - (id)objectForWebScript;
99 - (NPObject *)createPluginScriptableObject;
100 @end
101
102 #ifndef BUILDING_ON_TIGER
103 @interface NSSpellChecker (UpcomingAPI)
104 - (void)updateSpellingPanelWithGrammarString:(NSString *)grammarString detail:(NSDictionary *)grammarDetail;
105 @end
106 #endif
107
108 @interface NSSpellChecker (CurrentlyPrivateForTextView)
109 - (void)learnWord:(NSString *)word;
110 @end
111
112 using namespace std;
113 using namespace KJS::Bindings;
114
115 using KJS::JSLock;
116
117 namespace WebCore {
118
119 using namespace EventNames;
120 using namespace HTMLNames;
121
122 static SEL selectorForKeyEvent(KeyboardEvent* event)
123 {
124     // FIXME: This helper function is for the auto-fill code so the bridge can pass a selector to the form delegate.  
125     // Eventually, we should move all of the auto-fill code down to WebKit and remove the need for this function by
126     // not relying on the selector in the new implementation.
127     String key = event->keyIdentifier();
128     if (key == "Up")
129         return @selector(moveUp:);
130     if (key == "Down")
131         return @selector(moveDown:);
132     if (key == "U+00001B")
133         return @selector(cancel:);
134     if (key == "U+000009") {
135         if (event->shiftKey())
136             return @selector(insertBacktab:);
137         return @selector(insertTab:);
138     }
139     if (key == "Enter")
140         return @selector(insertNewline:);
141     return 0;
142 }
143
144 FrameMac::FrameMac(Page* page, Element* ownerElement, FrameLoaderClient* frameLoaderClient)
145     : Frame(page, ownerElement, frameLoaderClient)
146     , _bridge(nil)
147     , _bindingRoot(0)
148     , _windowScriptObject(0)
149     , _windowScriptNPObject(0)
150 {
151 }
152
153 FrameMac::~FrameMac()
154 {
155     setView(0);
156     loader()->clearRecordedFormValues();    
157     
158     [_bridge clearFrame];
159     HardRelease(_bridge);
160     _bridge = nil;
161
162     loader()->cancelAndClear();
163 }
164
165 // Either get cached regexp or build one that matches any of the labels.
166 // The regexp we build is of the form:  (STR1|STR2|STRN)
167 RegularExpression *regExpForLabels(NSArray *labels)
168 {
169     // All the ObjC calls in this method are simple array and string
170     // calls which we can assume do not raise exceptions
171
172
173     // Parallel arrays that we use to cache regExps.  In practice the number of expressions
174     // that the app will use is equal to the number of locales is used in searching.
175     static const unsigned int regExpCacheSize = 4;
176     static NSMutableArray *regExpLabels = nil;
177     static Vector<RegularExpression*> regExps;
178     static RegularExpression wordRegExp = RegularExpression("\\w");
179
180     RegularExpression *result;
181     if (!regExpLabels)
182         regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
183     CFIndex cacheHit = [regExpLabels indexOfObject:labels];
184     if (cacheHit != NSNotFound)
185         result = regExps.at(cacheHit);
186     else {
187         DeprecatedString pattern("(");
188         unsigned int numLabels = [labels count];
189         unsigned int i;
190         for (i = 0; i < numLabels; i++) {
191             DeprecatedString label = DeprecatedString::fromNSString((NSString *)[labels objectAtIndex:i]);
192
193             bool startsWithWordChar = false;
194             bool endsWithWordChar = false;
195             if (label.length() != 0) {
196                 startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
197                 endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0;
198             }
199             
200             if (i != 0)
201                 pattern.append("|");
202             // Search for word boundaries only if label starts/ends with "word characters".
203             // If we always searched for word boundaries, this wouldn't work for languages
204             // such as Japanese.
205             if (startsWithWordChar) {
206                 pattern.append("\\b");
207             }
208             pattern.append(label);
209             if (endsWithWordChar) {
210                 pattern.append("\\b");
211             }
212         }
213         pattern.append(")");
214         result = new RegularExpression(pattern, false);
215     }
216
217     // add regexp to the cache, making sure it is at the front for LRU ordering
218     if (cacheHit != 0) {
219         if (cacheHit != NSNotFound) {
220             // remove from old spot
221             [regExpLabels removeObjectAtIndex:cacheHit];
222             regExps.remove(cacheHit);
223         }
224         // add to start
225         [regExpLabels insertObject:labels atIndex:0];
226         regExps.insert(0, result);
227         // trim if too big
228         if ([regExpLabels count] > regExpCacheSize) {
229             [regExpLabels removeObjectAtIndex:regExpCacheSize];
230             RegularExpression *last = regExps.last();
231             regExps.removeLast();
232             delete last;
233         }
234     }
235     return result;
236 }
237
238 NSString* FrameMac::searchForLabelsAboveCell(RegularExpression* regExp, HTMLTableCellElement* cell)
239 {
240     RenderTableCell* cellRenderer = static_cast<RenderTableCell*>(cell->renderer());
241
242     if (cellRenderer && cellRenderer->isTableCell()) {
243         RenderTableCell* cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
244
245         if (cellAboveRenderer) {
246             HTMLTableCellElement *aboveCell =
247                 static_cast<HTMLTableCellElement*>(cellAboveRenderer->element());
248
249             if (aboveCell) {
250                 // search within the above cell we found for a match
251                 for (Node *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
252                     if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) {
253                         // For each text chunk, run the regexp
254                         DeprecatedString nodeString = n->nodeValue().deprecatedString();
255                         int pos = regExp->searchRev(nodeString);
256                         if (pos >= 0)
257                             return nodeString.mid(pos, regExp->matchedLength()).getNSString();
258                     }
259                 }
260             }
261         }
262     }
263     // Any reason in practice to search all cells in that are above cell?
264     return nil;
265 }
266
267 NSString *FrameMac::searchForLabelsBeforeElement(NSArray *labels, Element *element)
268 {
269     RegularExpression *regExp = regExpForLabels(labels);
270     // We stop searching after we've seen this many chars
271     const unsigned int charsSearchedThreshold = 500;
272     // This is the absolute max we search.  We allow a little more slop than
273     // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
274     const unsigned int maxCharsSearched = 600;
275     // If the starting element is within a table, the cell that contains it
276     HTMLTableCellElement *startingTableCell = 0;
277     bool searchedCellAbove = false;
278
279     // walk backwards in the node tree, until another element, or form, or end of tree
280     int unsigned lengthSearched = 0;
281     Node *n;
282     for (n = element->traversePreviousNode();
283          n && lengthSearched < charsSearchedThreshold;
284          n = n->traversePreviousNode())
285     {
286         if (n->hasTagName(formTag)
287             || (n->isHTMLElement()
288                 && static_cast<HTMLElement*>(n)->isGenericFormElement()))
289         {
290             // We hit another form element or the start of the form - bail out
291             break;
292         } else if (n->hasTagName(tdTag) && !startingTableCell) {
293             startingTableCell = static_cast<HTMLTableCellElement*>(n);
294         } else if (n->hasTagName(trTag) && startingTableCell) {
295             NSString *result = searchForLabelsAboveCell(regExp, startingTableCell);
296             if (result) {
297                 return result;
298             }
299             searchedCellAbove = true;
300         } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) {
301             // For each text chunk, run the regexp
302             DeprecatedString nodeString = n->nodeValue().deprecatedString();
303             // add 100 for slop, to make it more likely that we'll search whole nodes
304             if (lengthSearched + nodeString.length() > maxCharsSearched)
305                 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
306             int pos = regExp->searchRev(nodeString);
307             if (pos >= 0)
308                 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
309             else
310                 lengthSearched += nodeString.length();
311         }
312     }
313
314     // If we started in a cell, but bailed because we found the start of the form or the
315     // previous element, we still might need to search the row above us for a label.
316     if (startingTableCell && !searchedCellAbove) {
317          return searchForLabelsAboveCell(regExp, startingTableCell);
318     } else {
319         return nil;
320     }
321 }
322
323 NSString *FrameMac::matchLabelsAgainstElement(NSArray *labels, Element *element)
324 {
325     DeprecatedString name = element->getAttribute(nameAttr).deprecatedString();
326     // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
327     name.replace(RegularExpression("[[:digit:]]"), " ");
328     name.replace('_', ' ');
329     
330     RegularExpression *regExp = regExpForLabels(labels);
331     // Use the largest match we can find in the whole name string
332     int pos;
333     int length;
334     int bestPos = -1;
335     int bestLength = -1;
336     int start = 0;
337     do {
338         pos = regExp->search(name, start);
339         if (pos != -1) {
340             length = regExp->matchedLength();
341             if (length >= bestLength) {
342                 bestPos = pos;
343                 bestLength = length;
344             }
345             start = pos+1;
346         }
347     } while (pos != -1);
348
349     if (bestPos != -1)
350         return name.mid(bestPos, bestLength).getNSString();
351     return nil;
352 }
353
354 void FrameMac::setView(FrameView *view)
355 {
356     Frame::setView(view);
357     
358     // Only one form submission is allowed per view of a part.
359     // Since this part may be getting reused as a result of being
360     // pulled from the back/forward cache, reset this flag.
361     loader()->resetMultipleFormSubmissionProtection();
362 }
363
364 void FrameMac::setStatusBarText(const String& status)
365 {
366     String text = status;
367     text.replace('\\', backslashAsCurrencySymbol());
368     
369     // We want the temporaries allocated here to be released even before returning to the 
370     // event loop; see <http://bugs.webkit.org/show_bug.cgi?id=9880>.
371     NSAutoreleasePool* localPool = [[NSAutoreleasePool alloc] init];
372
373     BEGIN_BLOCK_OBJC_EXCEPTIONS;
374     [_bridge setStatusText:text];
375     END_BLOCK_OBJC_EXCEPTIONS;
376
377     [localPool release];
378 }
379
380 void FrameMac::scheduleClose()
381 {
382     if (!shouldClose())
383         return;
384     BEGIN_BLOCK_OBJC_EXCEPTIONS;
385     [_bridge closeWindowSoon];
386     END_BLOCK_OBJC_EXCEPTIONS;
387 }
388
389 void FrameMac::focusWindow()
390 {
391     BEGIN_BLOCK_OBJC_EXCEPTIONS;
392
393     // If we're a top level window, bring the window to the front.
394     if (!tree()->parent())
395         page()->chrome()->focus();
396
397     // Might not have a view yet: this could be a child frame that has not yet received its first byte of data.
398     // FIXME: Should remember that the frame needs focus.  See <rdar://problem/4645685>.
399     if (d->m_view) {
400         NSView *view = d->m_view->getDocumentView();
401         if ([_bridge firstResponder] != view)
402             [_bridge makeFirstResponder:view];
403     }
404
405     END_BLOCK_OBJC_EXCEPTIONS;
406 }
407
408 void FrameMac::unfocusWindow()
409 {
410     // Might not have a view yet: this could be a child frame that has not yet received its first byte of data.
411     // FIXME: Should remember that the frame needs to unfocus.  See <rdar://problem/4645685>.
412     if (!d->m_view)
413         return;
414
415     BEGIN_BLOCK_OBJC_EXCEPTIONS;
416     NSView *view = d->m_view->getDocumentView();
417     if ([_bridge firstResponder] == view) {
418         // If we're a top level window, deactivate the window.
419         if (!tree()->parent())
420             page()->chrome()->unfocus();
421         else {
422             // We want to shift focus to our parent.
423             FrameMac* parentFrame = static_cast<FrameMac*>(tree()->parent());
424             NSView* parentView = parentFrame->d->m_view->getDocumentView();
425             [parentFrame->_bridge makeFirstResponder:parentView];
426         }
427     }
428     END_BLOCK_OBJC_EXCEPTIONS;
429 }
430     
431 static NSString *findFirstMisspellingInRange(NSSpellChecker *checker, int tag, Range* searchRange, int& firstMisspellingOffset, bool markAll)
432 {
433     ASSERT_ARG(checker, checker);
434     ASSERT_ARG(searchRange, searchRange);
435     
436     WordAwareIterator it(searchRange);
437     firstMisspellingOffset = 0;
438     
439     NSString *firstMisspelling = nil;
440     int currentChunkOffset = 0;
441
442     while (!it.atEnd()) {
443         const UChar* chars = it.characters();
444         int len = it.length();
445         
446         // Skip some work for one-space-char hunks
447         if (!(len == 1 && chars[0] == ' ')) {
448             
449             NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:const_cast<UChar*>(chars) length:len freeWhenDone:NO];
450             NSRange misspellingNSRange = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:tag wordCount:NULL];
451             NSString *misspelledWord = (misspellingNSRange.length > 0) ? [chunk substringWithRange:misspellingNSRange] : nil;
452             [chunk release];
453             
454             if (misspelledWord) {
455                 
456                 // Remember first-encountered misspelling and its offset
457                 if (!firstMisspelling) {
458                     firstMisspellingOffset = currentChunkOffset + misspellingNSRange.location;
459                     firstMisspelling = misspelledWord;
460                 }
461                 
462                 // Mark this instance if we're marking all instances. Otherwise bail out because we found the first one.
463                 if (!markAll)
464                     break;
465                 
466                 // Compute range of misspelled word
467                 RefPtr<Range> misspellingRange = TextIterator::subrange(searchRange, currentChunkOffset + misspellingNSRange.location, [misspelledWord length]);
468                 
469                 // Store marker for misspelled word
470                 ExceptionCode ec = 0;
471                 misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
472                 ASSERT(ec == 0);
473             }
474         }
475         
476         currentChunkOffset += len;
477         it.advance();
478     }
479     
480     return firstMisspelling;
481 }
482     
483 #ifndef BUILDING_ON_TIGER
484
485 static PassRefPtr<Range> paragraphAlignedRangeForRange(Range* arbitraryRange, int& offsetIntoParagraphAlignedRange, NSString*& paragraphNSString)
486 {
487     ASSERT_ARG(arbitraryRange, arbitraryRange);
488     
489     ExceptionCode ec = 0;
490     
491     // Expand range to paragraph boundaries
492     RefPtr<Range> paragraphRange = arbitraryRange->cloneRange(ec);
493     setStart(paragraphRange.get(), startOfParagraph(arbitraryRange->startPosition()));
494     setEnd(paragraphRange.get(), endOfParagraph(arbitraryRange->endPosition()));
495     
496     // Compute offset from start of expanded range to start of original range
497     RefPtr<Range> offsetAsRange = new Range(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), arbitraryRange->startPosition());
498     offsetIntoParagraphAlignedRange = TextIterator::rangeLength(offsetAsRange.get());
499     
500     // Fill in out parameter with autoreleased string representing entire paragraph range.
501     // Someday we might have a caller that doesn't use this, but for now all callers do.
502     paragraphNSString = plainText(paragraphRange.get()).getNSString();
503
504     return paragraphRange;
505 }
506
507 static NSDictionary *findFirstGrammarDetailInRange(NSArray *grammarDetails, NSRange badGrammarPhraseNSRange, Range *searchRange, int startOffset, int endOffset, bool markAll)
508 {
509     // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
510     // Optionally add a DocumentMarker for each detail in the range.
511     NSRange earliestDetailNSRangeSoFar = NSMakeRange(NSNotFound, 0);
512     NSDictionary *earliestDetail = nil;
513     for (NSDictionary *detail in grammarDetails) {
514         NSValue *detailRangeAsNSValue = [detail objectForKey:NSGrammarRange];
515         NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
516         ASSERT(detailNSRange.length > 0 && detailNSRange.location != NSNotFound);
517         
518         int detailStartOffsetInParagraph = badGrammarPhraseNSRange.location + detailNSRange.location;
519         
520         // Skip this detail if it starts before the original search range
521         if (detailStartOffsetInParagraph < startOffset)
522             continue;
523         
524         // Skip this detail if it starts after the original search range
525         if (detailStartOffsetInParagraph >= endOffset)
526             continue;
527         
528         if (markAll) {
529             RefPtr<Range> badGrammarRange = TextIterator::subrange(searchRange, badGrammarPhraseNSRange.location - startOffset + detailNSRange.location, detailNSRange.length);
530             ExceptionCode ec = 0;
531             badGrammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, [detail objectForKey:NSGrammarUserDescription]);
532             ASSERT(ec == 0);
533         }
534         
535         // Remember this detail only if it's earlier than our current candidate (the details aren't in a guaranteed order)
536         if (!earliestDetail || earliestDetailNSRangeSoFar.location > detailNSRange.location) {
537             earliestDetail = detail;
538             earliestDetailNSRangeSoFar = detailNSRange;
539         }
540     }
541     
542     return earliestDetail;
543 }
544     
545 static NSString *findFirstBadGrammarInRange(NSSpellChecker *checker, int tag, Range* searchRange, NSDictionary*& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll)
546 {
547     ASSERT_ARG(checker, checker);
548     ASSERT_ARG(searchRange, searchRange);
549     
550     // Initialize out parameters; these will be updated if we find something to return.
551     outGrammarDetail = nil;
552     outGrammarPhraseOffset = 0;
553     
554     NSString *firstBadGrammarPhrase = nil;
555
556     // Expand the search range to encompass entire paragraphs, since grammar checking needs that much context.
557     // Determine the character offset from the start of the paragraph to the start of the original search range,
558     // since we will want to ignore results in this area.
559     int searchRangeStartOffset;
560     NSString *paragraphNSString;
561     RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(searchRange, searchRangeStartOffset, paragraphNSString);
562         
563     // Determine the character offset from the start of the paragraph to the end of the original search range, 
564     // since we will want to ignore results in this area also.
565     int searchRangeEndOffset = searchRangeStartOffset + TextIterator::rangeLength(searchRange);
566         
567     // Start checking from beginning of paragraph, but skip past results that occur before the start of the original search range.
568     NSInteger startOffset = 0;
569     while (startOffset < searchRangeEndOffset) {
570         NSArray *grammarDetails;
571         NSRange badGrammarPhraseNSRange = [checker checkGrammarOfString:paragraphNSString startingAt:startOffset language:nil wrap:NO inSpellDocumentWithTag:tag details:&grammarDetails];
572         
573         if (badGrammarPhraseNSRange.location == NSNotFound) {
574             ASSERT(badGrammarPhraseNSRange.length == 0);
575             return nil;
576         }
577         
578         // Found some bad grammar. Find the earliest detail range that starts in our search range (if any).
579         outGrammarDetail = findFirstGrammarDetailInRange(grammarDetails, badGrammarPhraseNSRange, searchRange, searchRangeStartOffset, searchRangeEndOffset, markAll);
580         
581         // If we found a detail in range, then we have found the first bad phrase (unless we found one earlier but
582         // kept going so we could mark all instances).
583         if (outGrammarDetail && !firstBadGrammarPhrase) {
584             outGrammarPhraseOffset = badGrammarPhraseNSRange.location - searchRangeStartOffset;
585             firstBadGrammarPhrase = [paragraphNSString substringWithRange:badGrammarPhraseNSRange];
586             
587             // Found one. We're done now, unless we're marking each instance.
588             if (!markAll)
589                 break;
590         }
591
592         // These results were all between the start of the paragraph and the start of the search range; look
593         // beyond this phrase.
594         startOffset = NSMaxRange(badGrammarPhraseNSRange);
595     }
596     
597     return firstBadGrammarPhrase;
598 }
599     
600 #endif /* not BUILDING_ON_TIGER */
601
602 void FrameMac::advanceToNextMisspelling(bool startBeforeSelection)
603 {
604     ExceptionCode ec = 0;
605
606     // The basic approach is to search in two phases - from the selection end to the end of the doc, and
607     // then we wrap and search from the doc start to (approximately) where we started.
608     
609     // Start at the end of the selection, search to edge of document.  Starting at the selection end makes
610     // repeated "check spelling" commands work.
611     Selection selection(selectionController()->selection());
612     RefPtr<Range> spellingSearchRange(rangeOfContents(document()));
613     bool startedWithSelection = false;
614     if (selection.start().node()) {
615         startedWithSelection = true;
616         if (startBeforeSelection) {
617             VisiblePosition start(selection.visibleStart());
618             // We match AppKit's rule: Start 1 character before the selection.
619             VisiblePosition oneBeforeStart = start.previous();
620             setStart(spellingSearchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start);
621         } else
622             setStart(spellingSearchRange.get(), selection.visibleEnd());
623     }
624
625     // If we're not in an editable node, try to find one, make that our range to work in
626     Node *editableNode = spellingSearchRange->startContainer(ec);
627     if (!editableNode->isContentEditable()) {
628         editableNode = editableNode->nextEditable();
629         if (!editableNode)
630             return;
631
632         spellingSearchRange->setStartBefore(editableNode, ec);
633         startedWithSelection = false;   // won't need to wrap
634     }
635     
636     // topNode defines the whole range we want to operate on 
637     Node *topNode = editableNode->rootEditableElement();
638     spellingSearchRange->setEnd(topNode, maxDeepOffset(topNode), ec);
639
640     // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking
641     // at a word boundary. Going back by one char and then forward by a word does the trick.
642     if (startedWithSelection) {
643         VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.get(), DOWNSTREAM).previous();
644         if (oneBeforeStart.isNotNull()) {
645             setStart(spellingSearchRange.get(), endOfWord(oneBeforeStart));
646         } // else we were already at the start of the editable node
647     }
648     
649     if (spellingSearchRange->collapsed(ec))
650         return;       // nothing to search in
651     
652     // Get the spell checker if it is available
653     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
654     if (!checker)
655         return;
656         
657     // We go to the end of our first range instead of the start of it, just to be sure
658     // we don't get foiled by any word boundary problems at the start.  It means we might
659     // do a tiny bit more searching.
660     Node *searchEndNodeAfterWrap = spellingSearchRange->endContainer(ec);
661     int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec);
662     
663     int misspellingOffset;
664     NSString *misspelledWord = findFirstMisspellingInRange(checker, editor()->client()->spellCheckerDocumentTag(), spellingSearchRange.get(), misspellingOffset, false);
665     
666     NSString *badGrammarPhrase = nil;
667
668 #ifndef BUILDING_ON_TIGER
669     int grammarPhraseOffset;
670     NSDictionary *grammarDetail = nil;
671
672     // Search for bad grammar that occurs prior to the next misspelled word (if any)
673     RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec);
674     if (misspelledWord) {
675         // Stop looking at start of next misspelled word
676         CharacterIterator chars(grammarSearchRange.get());
677         chars.advance(misspellingOffset);
678         grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
679     }
680     
681     if (editor()->client()->isGrammarCheckingEnabled())
682         badGrammarPhrase = findFirstBadGrammarInRange(checker, editor()->client()->spellCheckerDocumentTag(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
683 #endif
684     
685     // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the
686     // block rather than at a selection).
687     if (startedWithSelection && !misspelledWord && !badGrammarPhrase) {
688         spellingSearchRange->setStart(topNode, 0, ec);
689         // going until the end of the very first chunk we tested is far enough
690         spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, ec);
691         
692         misspelledWord = findFirstMisspellingInRange(checker, editor()->client()->spellCheckerDocumentTag(), spellingSearchRange.get(), misspellingOffset, false);
693
694 #ifndef BUILDING_ON_TIGER
695         grammarSearchRange = spellingSearchRange->cloneRange(ec);
696         if (misspelledWord) {
697             // Stop looking at start of next misspelled word
698             CharacterIterator chars(grammarSearchRange.get());
699             chars.advance(misspellingOffset);
700             grammarSearchRange->setEnd(chars.range()->startContainer(ec), chars.range()->startOffset(ec), ec);
701         }
702         if (editor()->client()->isGrammarCheckingEnabled())
703             badGrammarPhrase = findFirstBadGrammarInRange(checker, editor()->client()->spellCheckerDocumentTag(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false);
704 #endif
705     }
706     
707     if (badGrammarPhrase) {
708 #ifdef BUILDING_ON_TIGER
709         ASSERT_NOT_REACHED();
710 #else
711         // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar
712         // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling
713         // panel, and store a marker so we draw the green squiggle later.
714         
715         ASSERT([badGrammarPhrase length] > 0);
716         ASSERT(grammarDetail);
717         NSValue *detailRangeAsNSValue = [grammarDetail objectForKey:NSGrammarRange];
718         ASSERT(detailRangeAsNSValue);
719         NSRange detailNSRange = [detailRangeAsNSValue rangeValue];
720         ASSERT(detailNSRange.location != NSNotFound && detailNSRange.length > 0);
721         
722         // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph
723         RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + detailNSRange.location, detailNSRange.length);
724         selectionController()->setSelection(Selection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY));
725         revealSelection();
726         
727         [checker updateSpellingPanelWithGrammarString:badGrammarPhrase detail:grammarDetail];
728         document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, [grammarDetail objectForKey:NSGrammarUserDescription]);
729 #endif        
730     } else if (misspelledWord) {
731         // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store
732         // a marker so we draw the red squiggle later.
733         
734         RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, [misspelledWord length]);
735         selectionController()->setSelection(Selection(misspellingRange.get(), DOWNSTREAM));
736         revealSelection();
737         
738         [checker updateSpellingPanelWithMisspelledWord:misspelledWord];
739         document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling);
740     }
741 }
742
743 String FrameMac::mimeTypeForFileName(const String& fileName) const
744 {
745     BEGIN_BLOCK_OBJC_EXCEPTIONS;
746     return [_bridge MIMETypeForPath:fileName];
747     END_BLOCK_OBJC_EXCEPTIONS;
748
749     return String();
750 }
751
752 KJS::Bindings::RootObject *FrameMac::executionContextForDOM()
753 {
754     if (!javaScriptEnabled())
755         return 0;
756
757     return bindingRootObject();
758 }
759
760 KJS::Bindings::RootObject *FrameMac::bindingRootObject()
761 {
762     assert(javaScriptEnabled());
763     if (!_bindingRoot) {
764         JSLock lock;
765         _bindingRoot = new KJS::Bindings::RootObject(0);    // The root gets deleted by JavaScriptCore.
766         KJS::JSObject *win = KJS::Window::retrieveWindow(this);
767         _bindingRoot->setRootObjectImp (win);
768         _bindingRoot->setInterpreter(scriptProxy()->interpreter());
769         addPluginRootObject (_bindingRoot);
770     }
771     return _bindingRoot;
772 }
773
774 WebScriptObject *FrameMac::windowScriptObject()
775 {
776     if (!javaScriptEnabled())
777         return 0;
778
779     if (!_windowScriptObject) {
780         KJS::JSLock lock;
781         KJS::JSObject *win = KJS::Window::retrieveWindow(this);
782         _windowScriptObject = HardRetainWithNSRelease([[WebScriptObject alloc] _initWithJSObject:win originExecutionContext:bindingRootObject() executionContext:bindingRootObject()]);
783     }
784
785     return _windowScriptObject;
786 }
787
788 NPObject *FrameMac::windowScriptNPObject()
789 {
790     if (!_windowScriptNPObject) {
791         if (javaScriptEnabled()) {
792             // JavaScript is enabled, so there is a JavaScript window object.  Return an NPObject bound to the window
793             // object.
794             KJS::JSObject *win = KJS::Window::retrieveWindow(this);
795             assert(win);
796             _windowScriptNPObject = _NPN_CreateScriptObject(0, win, bindingRootObject(), bindingRootObject());
797         } else {
798             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
799             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
800             _windowScriptNPObject = _NPN_CreateNoScriptObject();
801         }
802     }
803
804     return _windowScriptNPObject;
805 }
806
807 WebCoreFrameBridge *FrameMac::bridgeForWidget(const Widget* widget)
808 {
809     ASSERT_ARG(widget, widget);
810     
811     FrameMac* frame = Mac(frameForWidget(widget));
812     ASSERT(frame);
813     return frame->_bridge;
814 }
815
816 void FrameMac::runJavaScriptAlert(const String& message)
817 {
818     String text = message;
819     text.replace('\\', backslashAsCurrencySymbol());
820     BEGIN_BLOCK_OBJC_EXCEPTIONS;
821     [_bridge runJavaScriptAlertPanelWithMessage:text];
822     END_BLOCK_OBJC_EXCEPTIONS;
823 }
824
825 bool FrameMac::runJavaScriptConfirm(const String& message)
826 {
827     String text = message;
828     text.replace('\\', backslashAsCurrencySymbol());
829
830     BEGIN_BLOCK_OBJC_EXCEPTIONS;
831     return [_bridge runJavaScriptConfirmPanelWithMessage:text];
832     END_BLOCK_OBJC_EXCEPTIONS;
833
834     return false;
835 }
836
837 bool FrameMac::runJavaScriptPrompt(const String& prompt, const String& defaultValue, String& result)
838 {
839     String promptText = prompt;
840     promptText.replace('\\', backslashAsCurrencySymbol());
841     String defaultValueText = defaultValue;
842     defaultValueText.replace('\\', backslashAsCurrencySymbol());
843
844     bool ok;
845     BEGIN_BLOCK_OBJC_EXCEPTIONS;
846     NSString *returnedText = nil;
847
848     ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt
849         defaultText:defaultValue returningText:&returnedText];
850
851     if (ok) {
852         result = String(returnedText);
853         result.replace(backslashAsCurrencySymbol(), '\\');
854     }
855
856     return ok;
857     END_BLOCK_OBJC_EXCEPTIONS;
858     
859     return false;
860 }
861
862 bool FrameMac::shouldInterruptJavaScript()
863 {
864     BEGIN_BLOCK_OBJC_EXCEPTIONS;
865     return [_bridge shouldInterruptJavaScript];
866     END_BLOCK_OBJC_EXCEPTIONS;
867     
868     return false;
869 }
870
871 NSImage *FrameMac::imageFromRect(NSRect rect) const
872 {
873     NSView *view = d->m_view->getDocumentView();
874     if (!view)
875         return nil;
876     
877     NSImage *resultImage;
878     BEGIN_BLOCK_OBJC_EXCEPTIONS;
879     
880     NSRect bounds = [view bounds];
881     
882     // Round image rect size in window coordinate space to avoid pixel cracks at HiDPI (4622794)
883     rect = [view convertRect:rect toView:nil];
884     rect.size.height = roundf(rect.size.height);
885     rect.size.width = roundf(rect.size.width);
886     rect = [view convertRect:rect fromView:nil];
887     
888     resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
889
890     if (rect.size.width != 0 && rect.size.height != 0) {
891         [resultImage setFlipped:YES];
892         [resultImage lockFocus];
893
894         CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
895
896         CGContextSaveGState(context);
897         CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y);
898         [view drawRect:rect];
899         CGContextRestoreGState(context);
900         [resultImage unlockFocus];
901         [resultImage setFlipped:NO];
902     }
903
904     return resultImage;
905
906     END_BLOCK_OBJC_EXCEPTIONS;
907     
908     return nil;
909 }
910
911 NSImage* FrameMac::selectionImage(bool forceWhiteText) const
912 {
913     d->m_paintRestriction = forceWhiteText ? PaintRestrictionSelectionOnlyWhiteText : PaintRestrictionSelectionOnly;
914     NSImage *result = imageFromRect(visibleSelectionRect());
915     d->m_paintRestriction = PaintRestrictionNone;
916     return result;
917 }
918
919 NSImage *FrameMac::snapshotDragImage(Node *node, NSRect *imageRect, NSRect *elementRect) const
920 {
921     RenderObject *renderer = node->renderer();
922     if (!renderer)
923         return nil;
924     
925     renderer->updateDragState(true);    // mark dragged nodes (so they pick up the right CSS)
926     d->m_doc->updateLayout();        // forces style recalc - needed since changing the drag state might
927                                         // imply new styles, plus JS could have changed other things
928     IntRect topLevelRect;
929     NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
930
931     d->m_elementToDraw = node;              // invoke special sub-tree drawing mode
932     NSImage *result = imageFromRect(paintingRect);
933     renderer->updateDragState(false);
934     d->m_doc->updateLayout();
935     d->m_elementToDraw = 0;
936
937     if (elementRect)
938         *elementRect = topLevelRect;
939     if (imageRect)
940         *imageRect = paintingRect;
941     return result;
942 }
943
944 NSFont *FrameMac::fontForSelection(bool *hasMultipleFonts) const
945 {
946     if (hasMultipleFonts)
947         *hasMultipleFonts = false;
948
949     if (!selectionController()->isRange()) {
950         Node *nodeToRemove;
951         RenderStyle *style = styleForSelectionStart(nodeToRemove); // sets nodeToRemove
952
953         NSFont *result = 0;
954         if (style)
955             result = style->font().primaryFont()->getNSFont();
956         
957         if (nodeToRemove) {
958             ExceptionCode ec;
959             nodeToRemove->remove(ec);
960             ASSERT(ec == 0);
961         }
962
963         return result;
964     }
965
966     NSFont *font = nil;
967
968     RefPtr<Range> range = selectionController()->toRange();
969     Node *startNode = range->editingStartPosition().node();
970     if (startNode != nil) {
971         Node *pastEnd = range->pastEndNode();
972         // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
973         // unreproducible case where this didn't happen, so check for nil also.
974         for (Node *n = startNode; n && n != pastEnd; n = n->traverseNextNode()) {
975             RenderObject *renderer = n->renderer();
976             if (!renderer)
977                 continue;
978             // FIXME: Are there any node types that have renderers, but that we should be skipping?
979             NSFont *f = renderer->style()->font().primaryFont()->getNSFont();
980             if (!font) {
981                 font = f;
982                 if (!hasMultipleFonts)
983                     break;
984             } else if (font != f) {
985                 *hasMultipleFonts = true;
986                 break;
987             }
988         }
989     }
990
991     return font;
992 }
993
994 NSDictionary *FrameMac::fontAttributesForSelectionStart() const
995 {
996     Node *nodeToRemove;
997     RenderStyle *style = styleForSelectionStart(nodeToRemove);
998     if (!style)
999         return nil;
1000
1001     NSMutableDictionary *result = [NSMutableDictionary dictionary];
1002
1003     if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0)
1004         [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName];
1005
1006     if (style->font().primaryFont()->getNSFont())
1007         [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName];
1008
1009     if (style->color().isValid() && style->color() != Color::black)
1010         [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName];
1011
1012     ShadowData *shadow = style->textShadow();
1013     if (shadow) {
1014         NSShadow *s = [[NSShadow alloc] init];
1015         [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)];
1016         [s setShadowBlurRadius:shadow->blur];
1017         [s setShadowColor:nsColor(shadow->color)];
1018         [result setObject:s forKey:NSShadowAttributeName];
1019     }
1020
1021     int decoration = style->textDecorationsInEffect();
1022     if (decoration & LINE_THROUGH)
1023         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
1024
1025     int superscriptInt = 0;
1026     switch (style->verticalAlign()) {
1027         case BASELINE:
1028         case BOTTOM:
1029         case BASELINE_MIDDLE:
1030         case LENGTH:
1031         case MIDDLE:
1032         case TEXT_BOTTOM:
1033         case TEXT_TOP:
1034         case TOP:
1035             break;
1036         case SUB:
1037             superscriptInt = -1;
1038             break;
1039         case SUPER:
1040             superscriptInt = 1;
1041             break;
1042     }
1043     if (superscriptInt)
1044         [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
1045
1046     if (decoration & UNDERLINE)
1047         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
1048
1049     if (nodeToRemove) {
1050         ExceptionCode ec = 0;
1051         nodeToRemove->remove(ec);
1052         ASSERT(ec == 0);
1053     }
1054
1055     return result;
1056 }
1057
1058 NSWritingDirection FrameMac::baseWritingDirectionForSelectionStart() const
1059 {
1060     NSWritingDirection result = NSWritingDirectionLeftToRight;
1061
1062     Position pos = selectionController()->selection().visibleStart().deepEquivalent();
1063     Node *node = pos.node();
1064     if (!node || !node->renderer() || !node->renderer()->containingBlock())
1065         return result;
1066     RenderStyle *style = node->renderer()->containingBlock()->style();
1067     if (!style)
1068         return result;
1069         
1070     switch (style->direction()) {
1071         case LTR:
1072             result = NSWritingDirectionLeftToRight;
1073             break;
1074         case RTL:
1075             result = NSWritingDirectionRightToLeft;
1076             break;
1077     }
1078
1079     return result;
1080 }
1081
1082 void FrameMac::setBridge(WebCoreFrameBridge *bridge)
1083
1084     if (_bridge == bridge)
1085         return;
1086
1087     HardRetain(bridge);
1088     HardRelease(_bridge);
1089     _bridge = bridge;
1090 }
1091
1092 void FrameMac::print()
1093 {
1094     [Mac(this)->_bridge print];
1095 }
1096
1097 KJS::Bindings::Instance *FrameMac::getAppletInstanceForWidget(Widget *widget)
1098 {
1099     NSView *aView = widget->getView();
1100     if (!aView)
1101         return 0;
1102     jobject applet;
1103     
1104     // Get a pointer to the actual Java applet instance.
1105     if ([_bridge respondsToSelector:@selector(getAppletInView:)])
1106         applet = [_bridge getAppletInView:aView];
1107     else
1108         applet = [_bridge pollForAppletInView:aView];
1109     
1110     if (applet) {
1111         // Wrap the Java instance in a language neutral binding and hand
1112         // off ownership to the APPLET element.
1113         KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView);
1114         KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::JavaLanguage, applet, executionContext);        
1115         return instance;
1116     }
1117     
1118     return 0;
1119 }
1120
1121 static KJS::Bindings::Instance *getInstanceForView(NSView *aView)
1122 {
1123     if ([aView respondsToSelector:@selector(objectForWebScript)]){
1124         id object = [aView objectForWebScript];
1125         if (object) {
1126             KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView);
1127             return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::ObjectiveCLanguage, object, executionContext);
1128         }
1129     }
1130     else if ([aView respondsToSelector:@selector(createPluginScriptableObject)]) {
1131         NPObject *object = [aView createPluginScriptableObject];
1132         if (object) {
1133             KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction()(aView);
1134             KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance(KJS::Bindings::Instance::CLanguage, object, executionContext);
1135             
1136             // -createPluginScriptableObject returns a retained NPObject.  The caller is expected to release it.
1137             _NPN_ReleaseObject(object);
1138             
1139             return instance;
1140         }
1141     }
1142     return 0;
1143 }
1144
1145 KJS::Bindings::Instance *FrameMac::getEmbedInstanceForWidget(Widget *widget)
1146 {
1147     return getInstanceForView(widget->getView());
1148 }
1149
1150 KJS::Bindings::Instance *FrameMac::getObjectInstanceForWidget(Widget *widget)
1151 {
1152     return getInstanceForView(widget->getView());
1153 }
1154
1155 void FrameMac::addPluginRootObject(KJS::Bindings::RootObject *root)
1156 {
1157     m_rootObjects.append(root);
1158 }
1159
1160 void FrameMac::cleanupPluginObjects()
1161 {
1162     // Delete old plug-in data structures
1163     JSLock lock;
1164     
1165     unsigned count = m_rootObjects.size();
1166     for (unsigned i = 0; i < count; i++)
1167         m_rootObjects[i]->removeAllNativeReferences();
1168     m_rootObjects.clear();
1169     
1170     _bindingRoot = 0;
1171     HardRelease(_windowScriptObject);
1172     _windowScriptObject = 0;
1173     
1174     if (_windowScriptNPObject) {
1175         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
1176         // script object properly.
1177         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
1178         _NPN_DeallocateObject(_windowScriptNPObject);
1179         _windowScriptNPObject = 0;
1180     }
1181 }
1182
1183 void FrameMac::issueCutCommand()
1184 {
1185     [_bridge issueCutCommand];
1186 }
1187
1188 void FrameMac::issueCopyCommand()
1189 {
1190     [_bridge issueCopyCommand];
1191 }
1192
1193 void FrameMac::issuePasteCommand()
1194 {
1195     [_bridge issuePasteCommand];
1196 }
1197
1198 void FrameMac::issuePasteAndMatchStyleCommand()
1199 {
1200     [_bridge issuePasteAndMatchStyleCommand];
1201 }
1202
1203 void FrameMac::issueTransposeCommand()
1204 {
1205     [_bridge issueTransposeCommand];
1206 }
1207
1208 void FrameMac::ignoreSpelling()
1209 {
1210     String text = selectedText();
1211     ASSERT(text.length() != 0);
1212     [[NSSpellChecker sharedSpellChecker] ignoreWord:text 
1213         inSpellDocumentWithTag:editor()->spellCheckerDocumentTag()];
1214 }
1215
1216 void FrameMac::learnSpelling()
1217 {
1218     String text = selectedText();
1219     ASSERT(text.length() != 0);
1220     [[NSSpellChecker sharedSpellChecker] learnWord:text];
1221 }
1222
1223 bool FrameMac::isSelectionMisspelled()
1224 {
1225     String selectedString = selectedText();
1226     unsigned length = selectedString.length();
1227     if (length == 0)
1228         return false;
1229     NSRange range = [[NSSpellChecker sharedSpellChecker] checkSpellingOfString:selectedString
1230                                                                     startingAt:0
1231                                                                       language:nil
1232                                                                           wrap:NO 
1233                                                         inSpellDocumentWithTag:editor()->spellCheckerDocumentTag() 
1234                                                                      wordCount:NULL];
1235     return range.length == length;
1236 }
1237
1238 static Vector<String> core(NSArray* stringsArray)
1239 {
1240     Vector<String> stringsVector = Vector<String>();
1241     unsigned count = [stringsArray count];
1242     if (count > 0) {
1243         NSEnumerator* enumerator = [stringsArray objectEnumerator];
1244         NSString* string;
1245         while ((string = [enumerator nextObject]) != nil)
1246             stringsVector.append(string);
1247     }
1248     return stringsVector;
1249 }
1250
1251 Vector<String> FrameMac::guessesForMisspelledSelection()
1252 {
1253     String selectedString = selectedText();
1254     ASSERT(selectedString.length() != 0);
1255     return core([[NSSpellChecker sharedSpellChecker] guessesForWord:selectedString]);
1256 }
1257
1258 void FrameMac::markMisspellingsInAdjacentWords(const VisiblePosition &p)
1259 {
1260     if (!editor()->isContinuousSpellCheckingEnabled())
1261         return;
1262     markMisspellings(Selection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)));
1263 }
1264     
1265 static void markAllMisspellingsInRange(NSSpellChecker *checker, int tag, Range* searchRange)
1266 {
1267     // Use the "markAll" feature of findFirstMisspellingInRange. Ignore the return value and the "out parameter";
1268     // all we need to do is mark every instance.
1269     int ignoredOffset;
1270     findFirstMisspellingInRange(checker, tag, searchRange, ignoredOffset, true);
1271 }
1272
1273 #ifndef BUILDING_ON_TIGER
1274 static void markAllBadGrammarInRange(NSSpellChecker *checker, int tag, Range* searchRange)
1275 {
1276     // Use the "markAll" feature of findFirstBadGrammarInRange. Ignore the return value and "out parameters"; all we need to
1277     // do is mark every instance.
1278     NSDictionary *ignoredGrammarDetail;
1279     int ignoredOffset;
1280     findFirstBadGrammarInRange(checker, tag, searchRange, ignoredGrammarDetail, ignoredOffset, true);
1281 }
1282 #endif
1283
1284 void FrameMac::markMisspellings(const Selection& selection)
1285 {
1286     // This function is called with a selection already expanded to word boundaries.
1287     // Might be nice to assert that here.
1288
1289     if (!editor()->isContinuousSpellCheckingEnabled())
1290         return;
1291
1292     RefPtr<Range> searchRange(selection.toRange());
1293     if (!searchRange || searchRange->isDetached())
1294         return;
1295     
1296     // If we're not in an editable node, bail.
1297     int exception = 0;
1298     Node *editableNode = searchRange->startContainer(exception);
1299     if (!editableNode->isContentEditable())
1300         return;
1301     
1302     // Get the spell checker if it is available
1303     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
1304     if (checker == nil)
1305         return;
1306     
1307     markAllMisspellingsInRange(checker, editor()->spellCheckerDocumentTag(), searchRange.get());
1308     
1309 #ifndef BUILDING_ON_TIGER
1310     if (editor()->client()->isGrammarCheckingEnabled())
1311         markAllBadGrammarInRange(checker, editor()->spellCheckerDocumentTag(), searchRange.get());
1312 #endif
1313 }
1314
1315 void FrameMac::respondToChangedSelection(const Selection &oldSelection, bool closeTyping)
1316 {
1317     if (document()) {
1318         if (editor()->isContinuousSpellCheckingEnabled()) {
1319             Selection oldAdjacentWords;
1320             
1321             // If this is a change in selection resulting from a delete operation, oldSelection may no longer
1322             // be in the document.
1323             if (oldSelection.start().node() && oldSelection.start().node()->inDocument()) {
1324                 VisiblePosition oldStart(oldSelection.visibleStart());
1325                 oldAdjacentWords = Selection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));   
1326             }
1327
1328             VisiblePosition newStart(selectionController()->selection().visibleStart());
1329             Selection newAdjacentWords(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
1330
1331             // When typing we check spelling elsewhere, so don't redo it here.
1332             if (closeTyping && oldAdjacentWords != newAdjacentWords)
1333                 markMisspellings(oldAdjacentWords);
1334
1335             // This only erases a marker in the first word of the selection.
1336             // Perhaps peculiar, but it matches AppKit.
1337             document()->removeMarkers(newAdjacentWords.toRange().get(), DocumentMarker::Spelling);
1338             document()->removeMarkers(newAdjacentWords.toRange().get(), DocumentMarker::Grammar);
1339         } else {
1340             // When continuous spell checking is off, existing markers disappear after the selection changes.
1341             document()->removeMarkers(DocumentMarker::Spelling);
1342             document()->removeMarkers(DocumentMarker::Grammar);
1343         }
1344     }
1345
1346     [_bridge respondToChangedSelection];
1347 }
1348
1349 bool FrameMac::shouldChangeSelection(const Selection& oldSelection, const Selection& newSelection, EAffinity affinity, bool stillSelecting) const
1350 {
1351     return [_bridge shouldChangeSelectedDOMRange:[DOMRange _rangeWith:oldSelection.toRange().get()]
1352                                       toDOMRange:[DOMRange _rangeWith:newSelection.toRange().get()]
1353                                         affinity:affinity
1354                                   stillSelecting:stillSelecting];
1355 }
1356
1357 bool FrameMac::shouldDeleteSelection(const Selection& selection) const
1358 {
1359     return [_bridge shouldDeleteSelectedDOMRange:[DOMRange _rangeWith:selection.toRange().get()]];
1360 }
1361
1362 bool FrameMac::isContentEditable() const
1363 {
1364     return Frame::isContentEditable() || [_bridge isEditable];
1365 }
1366
1367 void FrameMac::textFieldDidBeginEditing(Element* input)
1368 {
1369     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1370     [_bridge textFieldDidBeginEditing:(DOMHTMLInputElement *)[DOMElement _elementWith:input]];
1371     END_BLOCK_OBJC_EXCEPTIONS;
1372 }
1373
1374 void FrameMac::textFieldDidEndEditing(Element* input)
1375 {
1376     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1377     [_bridge textFieldDidEndEditing:(DOMHTMLInputElement *)[DOMElement _elementWith:input]];
1378     END_BLOCK_OBJC_EXCEPTIONS;
1379 }
1380
1381 void FrameMac::textDidChangeInTextField(Element* input)
1382 {
1383     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1384     [_bridge textDidChangeInTextField:(DOMHTMLInputElement *)[DOMElement _elementWith:input]];
1385     END_BLOCK_OBJC_EXCEPTIONS;
1386 }
1387
1388 void FrameMac::textDidChangeInTextArea(Element* textarea)
1389 {
1390     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1391     [_bridge textDidChangeInTextArea:(DOMHTMLTextAreaElement *)[DOMElement _elementWith:textarea]];
1392     END_BLOCK_OBJC_EXCEPTIONS;
1393 }
1394
1395 bool FrameMac::doTextFieldCommandFromEvent(Element* input, KeyboardEvent* event)
1396 {
1397     bool result = false;
1398     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1399     SEL selector = selectorForKeyEvent(event);
1400     if (selector)
1401         result = [_bridge textField:(DOMHTMLInputElement *)[DOMElement _elementWith:input] doCommandBySelector:selector];
1402     END_BLOCK_OBJC_EXCEPTIONS;
1403     return result;
1404 }
1405
1406 void FrameMac::textWillBeDeletedInTextField(Element* input)
1407 {
1408     // We're using the deleteBackward selector for all deletion operations since the autofill code treats all deletions the same way.
1409     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1410     [_bridge textField:(DOMHTMLInputElement *)[DOMElement _elementWith:input] doCommandBySelector:@selector(deleteBackward:)];
1411     END_BLOCK_OBJC_EXCEPTIONS;
1412 }
1413
1414 const short enableRomanKeyboardsOnly = -23;
1415 void FrameMac::setSecureKeyboardEntry(bool enable)
1416 {
1417     if (enable) {
1418         EnableSecureEventInput();
1419 // FIXME: KeyScript is deprecated in Leopard, we need a new solution for this <rdar://problem/4727607>
1420 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
1421         KeyScript(enableRomanKeyboardsOnly);
1422 #endif
1423     } else {
1424         DisableSecureEventInput();
1425 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
1426         KeyScript(smKeyEnableKybds);
1427 #endif
1428     }
1429 }
1430
1431 bool FrameMac::isSecureKeyboardEntry()
1432 {
1433     return IsSecureEventInputEnabled();
1434 }
1435
1436 static void convertAttributesToUnderlines(Vector<MarkedTextUnderline>& result, const Range *markedTextRange, NSArray *attributes, NSArray *ranges)
1437 {
1438     int exception = 0;
1439     int baseOffset = markedTextRange->startOffset(exception);
1440
1441     unsigned length = [attributes count];
1442     ASSERT([ranges count] == length);
1443
1444     for (unsigned i = 0; i < length; i++) {
1445         NSNumber *style = [[attributes objectAtIndex:i] objectForKey:NSUnderlineStyleAttributeName];
1446         if (!style)
1447             continue;
1448         NSRange range = [[ranges objectAtIndex:i] rangeValue];
1449         NSColor *color = [[attributes objectAtIndex:i] objectForKey:NSUnderlineColorAttributeName];
1450         Color qColor = Color::black;
1451         if (color) {
1452             NSColor* deviceColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace];
1453             qColor = Color(makeRGBA((int)(255 * [deviceColor redComponent]),
1454                                     (int)(255 * [deviceColor blueComponent]),
1455                                     (int)(255 * [deviceColor greenComponent]),
1456                                     (int)(255 * [deviceColor alphaComponent])));
1457         }
1458
1459         result.append(MarkedTextUnderline(range.location + baseOffset, 
1460                                           range.location + baseOffset + range.length, 
1461                                           qColor,
1462                                           [style intValue] > 1));
1463     }
1464 }
1465
1466 void FrameMac::setMarkedTextRange(const Range *range, NSArray *attributes, NSArray *ranges)
1467 {
1468     int exception = 0;
1469
1470     ASSERT(!range || range->startContainer(exception) == range->endContainer(exception));
1471     ASSERT(!range || range->collapsed(exception) || range->startContainer(exception)->isTextNode());
1472
1473     d->m_markedTextUnderlines.clear();
1474     if (attributes == nil)
1475         d->m_markedTextUsesUnderlines = false;
1476     else {
1477         d->m_markedTextUsesUnderlines = true;
1478         convertAttributesToUnderlines(d->m_markedTextUnderlines, range, attributes, ranges);
1479     }
1480
1481     if (m_markedTextRange.get() && document() && m_markedTextRange->startContainer(exception)->renderer())
1482         m_markedTextRange->startContainer(exception)->renderer()->repaint();
1483
1484     if (range && range->collapsed(exception))
1485         m_markedTextRange = 0;
1486     else
1487         m_markedTextRange = const_cast<Range*>(range);
1488
1489     if (m_markedTextRange.get() && document() && m_markedTextRange->startContainer(exception)->renderer())
1490         m_markedTextRange->startContainer(exception)->renderer()->repaint();
1491 }
1492
1493 NSMutableDictionary *FrameMac::dashboardRegionsDictionary()
1494 {
1495     Document *doc = document();
1496     if (!doc)
1497         return nil;
1498
1499     const Vector<DashboardRegionValue>& regions = doc->dashboardRegions();
1500     size_t n = regions.size();
1501
1502     // Convert the Vector<DashboardRegionValue> into a NSDictionary of WebDashboardRegions
1503     NSMutableDictionary *webRegions = [NSMutableDictionary dictionaryWithCapacity:n];
1504     for (size_t i = 0; i < n; i++) {
1505         const DashboardRegionValue& region = regions[i];
1506
1507         if (region.type == StyleDashboardRegion::None)
1508             continue;
1509         
1510         NSString *label = region.label;
1511         WebDashboardRegionType type = WebDashboardRegionTypeNone;
1512         if (region.type == StyleDashboardRegion::Circle)
1513             type = WebDashboardRegionTypeCircle;
1514         else if (region.type == StyleDashboardRegion::Rectangle)
1515             type = WebDashboardRegionTypeRectangle;
1516         NSMutableArray *regionValues = [webRegions objectForKey:label];
1517         if (!regionValues) {
1518             regionValues = [[NSMutableArray alloc] initWithCapacity:1];
1519             [webRegions setObject:regionValues forKey:label];
1520             [regionValues release];
1521         }
1522         
1523         WebDashboardRegion *webRegion = [[WebDashboardRegion alloc] initWithRect:region.bounds clip:region.clip type:type];
1524         [regionValues addObject:webRegion];
1525         [webRegion release];
1526     }
1527     
1528     return webRegions;
1529 }
1530
1531 void FrameMac::dashboardRegionsChanged()
1532 {
1533     NSMutableDictionary *webRegions = dashboardRegionsDictionary();
1534     [_bridge dashboardRegionsChanged:webRegions];
1535 }
1536
1537 void FrameMac::willPopupMenu(NSMenu * menu)
1538 {
1539     [_bridge willPopupMenu:menu];
1540 }
1541
1542 bool FrameMac::isCharacterSmartReplaceExempt(UChar c, bool isPreviousChar)
1543 {
1544     return [_bridge isCharacterSmartReplaceExempt:c isPreviousCharacter:isPreviousChar];
1545 }
1546
1547 bool FrameMac::shouldClose()
1548 {
1549     BEGIN_BLOCK_OBJC_EXCEPTIONS;
1550
1551     if (![_bridge canRunBeforeUnloadConfirmPanel])
1552         return true;
1553
1554     RefPtr<Document> doc = document();
1555     if (!doc)
1556         return true;
1557     HTMLElement* body = doc->body();
1558     if (!body)
1559         return true;
1560
1561     RefPtr<BeforeUnloadEvent> event = new BeforeUnloadEvent;
1562     event->setTarget(doc);
1563     doc->handleWindowEvent(event.get(), false);
1564
1565     if (!event->defaultPrevented() && doc)
1566         doc->defaultEventHandler(event.get());
1567     if (event->result().isNull())
1568         return true;
1569
1570     String text = event->result();
1571     text.replace('\\', backslashAsCurrencySymbol());
1572
1573     return [_bridge runBeforeUnloadConfirmPanelWithMessage:text];
1574
1575     END_BLOCK_OBJC_EXCEPTIONS;
1576
1577     return true;
1578 }
1579
1580 void Frame::setNeedsReapplyStyles()
1581 {
1582     [Mac(this)->_bridge setNeedsReapplyStyles];
1583 }
1584
1585 FloatRect FrameMac::customHighlightLineRect(const AtomicString& type, const FloatRect& lineRect)
1586 {
1587     return [_bridge customHighlightRect:type forLine:lineRect];
1588 }
1589
1590 void FrameMac::paintCustomHighlight(const AtomicString& type, const FloatRect& boxRect, const FloatRect& lineRect, bool text, bool line)
1591 {
1592     [_bridge paintCustomHighlight:type forBox:boxRect onLine:lineRect behindText:text entireLine:line];
1593 }
1594
1595 } // namespace WebCore