Range constructors should take a Document&.
[WebKit-https.git] / Source / WebKit / mac / WebView / WebHTMLRepresentation.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WebHTMLRepresentation.h"
30
31 #import "DOMElementInternal.h"
32 #import "DOMNodeInternal.h"
33 #import "DOMRangeInternal.h"
34 #import "WebArchive.h"
35 #import "WebBasePluginPackage.h"
36 #import "WebDataSourceInternal.h"
37 #import "WebDocumentPrivate.h"
38 #import "WebFrameInternal.h"
39 #import "WebKitNSStringExtras.h"
40 #import "WebKitStatisticsPrivate.h"
41 #import "WebNSObjectExtras.h"
42 #import "WebTypesInternal.h"
43 #import "WebView.h"
44 #import <Foundation/NSURLResponse.h>
45 #import <WebCore/Document.h>
46 #import <WebCore/DocumentLoader.h>
47 #import <WebCore/Editor.h>
48 #import <WebCore/Frame.h>
49 #import <WebCore/FrameLoader.h>
50 #import <WebCore/FrameLoaderClient.h>
51 #import <WebCore/HTMLConverter.h>
52 #import <WebCore/HTMLFormControlElement.h>
53 #import <WebCore/HTMLFormElement.h>
54 #import <WebCore/HTMLInputElement.h>
55 #import <WebCore/HTMLNames.h>
56 #import <WebCore/HTMLTableCellElement.h>
57 #import <WebCore/MIMETypeRegistry.h>
58 #import <WebCore/NodeTraversal.h>
59 #import <WebCore/Range.h>
60 #import <WebCore/RegularExpression.h>
61 #import <WebCore/RenderElement.h>
62 #import <WebCore/TextResourceDecoder.h>
63 #import <WebKit/DOMHTMLInputElement.h>
64 #import <wtf/Assertions.h>
65 #import <wtf/StdLibExtras.h>
66
67 using namespace WebCore;
68 using namespace HTMLNames;
69
70 @interface WebHTMLRepresentationPrivate : NSObject {
71 @public
72     WebDataSource *dataSource;
73     
74     BOOL hasSentResponseToPlugin;
75     BOOL includedInWebKitStatistics;
76
77     id <WebPluginManualLoader> manualLoader;
78     NSView *pluginView;
79 }
80 @end
81
82 @implementation WebHTMLRepresentationPrivate
83 @end
84
85 @implementation WebHTMLRepresentation
86
87 static NSMutableArray *newArrayWithStrings(const HashSet<String>& set) NS_RETURNS_RETAINED;
88 static NSMutableArray *newArrayWithStrings(const HashSet<String>& set)
89 {
90     NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:set.size()];
91     HashSet<String>::const_iterator end = set.end();
92     for (HashSet<String>::const_iterator it = set.begin(); it != end; ++it)
93         [array addObject:(NSString *)(*it)];
94     return array;
95 }
96
97 static NSMutableArray *newArrayByConcatenatingArrays(NSArray *first, NSArray *second) NS_RETURNS_RETAINED;
98 static NSMutableArray *newArrayByConcatenatingArrays(NSArray *first, NSArray *second)
99 {
100     NSMutableArray *result = [first mutableCopy];
101     [result addObjectsFromArray:second];
102     return result;
103 }
104
105 + (NSArray *)supportedMIMETypes
106 {
107     static __unsafe_unretained NSArray *staticSupportedMIMETypes = newArrayByConcatenatingArrays([self supportedNonImageMIMETypes], [self supportedImageMIMETypes]);
108     return staticSupportedMIMETypes;
109 }
110
111 + (NSArray *)supportedNonImageMIMETypes
112 {
113     static __unsafe_unretained NSArray *staticSupportedNonImageMIMETypes = newArrayWithStrings(MIMETypeRegistry::getSupportedNonImageMIMETypes());
114     return staticSupportedNonImageMIMETypes;
115 }
116
117 + (NSArray *)supportedImageMIMETypes
118 {
119     static __unsafe_unretained NSArray *staticSupportedImageMIMETypes = newArrayWithStrings(MIMETypeRegistry::getSupportedImageMIMETypes());
120     return staticSupportedImageMIMETypes;
121 }
122
123 + (NSArray *)unsupportedTextMIMETypes
124 {
125     static __unsafe_unretained NSArray *staticUnsupportedTextMIMETypes = newArrayWithStrings(MIMETypeRegistry::getUnsupportedTextMIMETypes());
126     return staticUnsupportedTextMIMETypes;
127 }
128
129 - (id)init
130 {
131     self = [super init];
132     if (!self)
133         return nil;
134     
135     _private = [[WebHTMLRepresentationPrivate alloc] init];
136
137     return self;
138 }
139
140 - (void)dealloc
141 {
142     if (_private && _private->includedInWebKitStatistics)
143         --WebHTMLRepresentationCount;
144
145     [_private release];
146
147     [super dealloc];
148 }
149
150 - (void)finalize
151 {
152     if (_private && _private->includedInWebKitStatistics)
153         --WebHTMLRepresentationCount;
154
155     [super finalize];
156 }
157
158 - (void)_redirectDataToManualLoader:(id<WebPluginManualLoader>)manualLoader forPluginView:(NSView *)pluginView
159 {
160     _private->manualLoader = manualLoader;
161     _private->pluginView = pluginView;
162 }
163
164 - (void)setDataSource:(WebDataSource *)dataSource
165 {
166     _private->dataSource = dataSource;
167
168     if (!_private->includedInWebKitStatistics && [[dataSource webFrame] _isIncludedInWebKitStatistics]) {
169         _private->includedInWebKitStatistics = YES;
170         ++WebHTMLRepresentationCount;
171     }
172 }
173
174 - (BOOL)_isDisplayingWebArchive
175 {
176     return [[_private->dataSource _responseMIMEType] _webkit_isCaseInsensitiveEqualToString:@"application/x-webarchive"];
177 }
178
179 - (void)receivedData:(NSData *)data withDataSource:(WebDataSource *)dataSource
180 {
181     WebFrame *webFrame = [dataSource webFrame];
182     if (!webFrame)
183         return;
184
185     if (!_private->pluginView)
186         [webFrame _commitData:data];
187
188     // If the document is a stand-alone media document, now is the right time to cancel the WebKit load
189     Frame* coreFrame = core(webFrame);
190     if (coreFrame->document()->isMediaDocument())
191         coreFrame->loader().documentLoader()->cancelMainResourceLoad(coreFrame->loader().client().pluginWillHandleLoadError(coreFrame->loader().documentLoader()->response()));
192
193     if (_private->pluginView) {
194         if (!_private->hasSentResponseToPlugin) {
195             [_private->manualLoader pluginView:_private->pluginView receivedResponse:[dataSource response]];
196             _private->hasSentResponseToPlugin = YES;
197         }
198         
199         [_private->manualLoader pluginView:_private->pluginView receivedData:data];
200     }
201 }
202
203 - (void)receivedError:(NSError *)error withDataSource:(WebDataSource *)dataSource
204 {
205     if (_private->pluginView) {
206         [_private->manualLoader pluginView:_private->pluginView receivedError:error];
207     }
208 }
209
210 - (void)finishedLoadingWithDataSource:(WebDataSource *)dataSource
211 {
212     WebFrame* webFrame = [dataSource webFrame];
213
214     if (_private->pluginView) {
215         [_private->manualLoader pluginViewFinishedLoading:_private->pluginView];
216         return;
217     }
218
219     if (!webFrame)
220         return;
221     WebView *webView = [webFrame webView];
222     if ([webView mainFrame] == webFrame && [webView isEditable])
223         core(webFrame)->editor().applyEditingStyleToBodyElement();
224 }
225
226 - (BOOL)canProvideDocumentSource
227 {
228     return [[_private->dataSource webFrame] _canProvideDocumentSource];
229 }
230
231 - (BOOL)canSaveAsWebArchive
232 {
233     return [[_private->dataSource webFrame] _canSaveAsWebArchive];
234 }
235
236 - (NSString *)documentSource
237 {
238     if ([self _isDisplayingWebArchive]) {            
239         SharedBuffer *parsedArchiveData = [_private->dataSource _documentLoader]->parsedArchiveData();
240         NSString *result = [[NSString alloc] initWithData:parsedArchiveData ? parsedArchiveData->createNSData().get() : nil encoding:NSUTF8StringEncoding];
241         return [result autorelease];
242     }
243
244     Frame* coreFrame = core([_private->dataSource webFrame]);
245     if (!coreFrame)
246         return nil;
247     Document* document = coreFrame->document();
248     if (!document)
249         return nil;
250     TextResourceDecoder* decoder = document->decoder();
251     if (!decoder)
252         return nil;
253     NSData *data = [_private->dataSource data];
254     if (!data)
255         return nil;
256     return decoder->encoding().decode(reinterpret_cast<const char*>([data bytes]), [data length]);
257 }
258
259 - (NSString *)title
260 {
261     return nsStringNilIfEmpty([_private->dataSource _documentLoader]->title().string());
262 }
263
264 - (DOMDocument *)DOMDocument
265 {
266     return [[_private->dataSource webFrame] DOMDocument];
267 }
268
269 - (NSAttributedString *)attributedText
270 {
271     // FIXME: Implement
272     return nil;
273 }
274
275 - (NSAttributedString *)attributedStringFrom:(DOMNode *)startNode startOffset:(int)startOffset to:(DOMNode *)endNode endOffset:(int)endOffset
276 {
277     return [WebHTMLConverter editingAttributedStringFromRange:Range::create(core(startNode)->document(), core(startNode), startOffset, core(endNode), endOffset).get()];
278 }
279
280 static HTMLFormElement* formElementFromDOMElement(DOMElement *element)
281 {
282     Element* node = core(element);
283     return node && node->hasTagName(formTag) ? static_cast<HTMLFormElement*>(node) : 0;
284 }
285
286 - (DOMElement *)elementWithName:(NSString *)name inForm:(DOMElement *)form
287 {
288     HTMLFormElement* formElement = formElementFromDOMElement(form);
289     if (!formElement)
290         return nil;
291     const Vector<FormAssociatedElement*>& elements = formElement->associatedElements();
292     AtomicString targetName = name;
293     for (unsigned i = 0; i < elements.size(); i++) {
294         FormAssociatedElement* elt = elements[i];
295         if (elt->name() == targetName)
296             return kit(toHTMLElement(elt));
297     }
298     return nil;
299 }
300
301 static HTMLInputElement* inputElementFromDOMElement(DOMElement* element)
302 {
303     Element* node = core(element);
304     return node && isHTMLInputElement(node) ? toHTMLInputElement(node) : 0;
305 }
306
307 - (BOOL)elementDoesAutoComplete:(DOMElement *)element
308 {
309     HTMLInputElement* inputElement = inputElementFromDOMElement(element);
310     return inputElement
311         && inputElement->isTextField()
312         && !inputElement->isPasswordField()
313         && inputElement->shouldAutocomplete();
314 }
315
316 - (BOOL)elementIsPassword:(DOMElement *)element
317 {
318     HTMLInputElement* inputElement = inputElementFromDOMElement(element);
319     return inputElement && inputElement->isPasswordField();
320 }
321
322 - (DOMElement *)formForElement:(DOMElement *)element
323 {
324     HTMLInputElement* inputElement = inputElementFromDOMElement(element);
325     return inputElement ? kit(inputElement->form()) : 0;
326 }
327
328 - (DOMElement *)currentForm
329 {
330     return kit(core([_private->dataSource webFrame])->selection().currentForm());
331 }
332
333 - (NSArray *)controlsInForm:(DOMElement *)form
334 {
335     HTMLFormElement* formElement = formElementFromDOMElement(form);
336     if (!formElement)
337         return nil;
338     NSMutableArray *results = nil;
339     const Vector<FormAssociatedElement*>& elements = formElement->associatedElements();
340     for (unsigned i = 0; i < elements.size(); i++) {
341         if (elements[i]->isEnumeratable()) { // Skip option elements, other duds
342             DOMElement* de = kit(toHTMLElement(elements[i]));
343             if (!results)
344                 results = [NSMutableArray arrayWithObject:de];
345             else
346                 [results addObject:de];
347         }
348     }
349     return results;
350 }
351
352 // Either get cached regexp or build one that matches any of the labels.
353 // The regexp we build is of the form:  (STR1|STR2|STRN)
354 static RegularExpression* regExpForLabels(NSArray *labels)
355 {
356     // All the ObjC calls in this method are simple array and string
357     // calls which we can assume do not raise exceptions
358
359     // Parallel arrays that we use to cache regExps.  In practice the number of expressions
360     // that the app will use is equal to the number of locales is used in searching.
361     static const unsigned int regExpCacheSize = 4;
362     static NSMutableArray* regExpLabels = nil;
363     DEFINE_STATIC_LOCAL(Vector<RegularExpression*>, regExps, ());
364     DEFINE_STATIC_LOCAL(RegularExpression, wordRegExp, ("\\w", TextCaseSensitive));
365
366     RegularExpression* result;
367     if (!regExpLabels)
368         regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
369     CFIndex cacheHit = [regExpLabels indexOfObject:labels];
370     if (cacheHit != NSNotFound)
371         result = regExps.at(cacheHit);
372     else {
373         String pattern("(");
374         unsigned int numLabels = [labels count];
375         unsigned int i;
376         for (i = 0; i < numLabels; i++) {
377             String label = [labels objectAtIndex:i];
378
379             bool startsWithWordChar = false;
380             bool endsWithWordChar = false;
381             if (label.length() != 0) {
382                 startsWithWordChar = wordRegExp.match(label.substring(0, 1)) >= 0;
383                 endsWithWordChar = wordRegExp.match(label.substring(label.length() - 1, 1)) >= 0;
384             }
385             
386             if (i != 0)
387                 pattern.append("|");
388             // Search for word boundaries only if label starts/ends with "word characters".
389             // If we always searched for word boundaries, this wouldn't work for languages
390             // such as Japanese.
391             if (startsWithWordChar)
392                 pattern.append("\\b");
393             pattern.append(label);
394             if (endsWithWordChar)
395                 pattern.append("\\b");
396         }
397         pattern.append(")");
398         result = new RegularExpression(pattern, TextCaseInsensitive);
399     }
400
401     // add regexp to the cache, making sure it is at the front for LRU ordering
402     if (cacheHit != 0) {
403         if (cacheHit != NSNotFound) {
404             // remove from old spot
405             [regExpLabels removeObjectAtIndex:cacheHit];
406             regExps.remove(cacheHit);
407         }
408         // add to start
409         [regExpLabels insertObject:labels atIndex:0];
410         regExps.insert(0, result);
411         // trim if too big
412         if ([regExpLabels count] > regExpCacheSize) {
413             [regExpLabels removeObjectAtIndex:regExpCacheSize];
414             RegularExpression* last = regExps.last();
415             regExps.removeLast();
416             delete last;
417         }
418     }
419     return result;
420 }
421
422 static NSString* searchForLabelsBeforeElement(Frame* frame, NSArray* labels, Element* element, size_t* resultDistance, bool* resultIsInCellAbove)
423 {
424     RegularExpression* regExp = regExpForLabels(labels);
425     // We stop searching after we've seen this many chars
426     const unsigned int charsSearchedThreshold = 500;
427     // This is the absolute max we search.  We allow a little more slop than
428     // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
429     const unsigned int maxCharsSearched = 600;
430     // If the starting element is within a table, the cell that contains it
431     HTMLTableCellElement* startingTableCell = 0;
432     bool searchedCellAbove = false;
433     
434     if (resultDistance)
435         *resultDistance = notFound;
436     if (resultIsInCellAbove)
437         *resultIsInCellAbove = false;
438
439     // walk backwards in the node tree, until another element, or form, or end of tree
440     unsigned lengthSearched = 0;
441     Node* n;
442     for (n = NodeTraversal::previous(element);
443          n && lengthSearched < charsSearchedThreshold;
444          n = NodeTraversal::previous(n))
445     {
446         if (n->hasTagName(formTag)
447             || (n->isHTMLElement() && toElement(n)->isFormControlElement()))
448         {
449             // We hit another form element or the start of the form - bail out
450             break;
451         } else if (n->hasTagName(tdTag) && !startingTableCell) {
452             startingTableCell = static_cast<HTMLTableCellElement*>(n);
453         } else if (n->hasTagName(trTag) && startingTableCell) {
454             NSString* result = frame->searchForLabelsAboveCell(regExp, startingTableCell, resultDistance);
455             if (result && [result length] > 0) {
456                 if (resultIsInCellAbove)
457                     *resultIsInCellAbove = true;
458                 return result;
459             }
460             searchedCellAbove = true;
461         } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) {
462             // For each text chunk, run the regexp
463             String nodeString = n->nodeValue();
464             // add 100 for slop, to make it more likely that we'll search whole nodes
465             if (lengthSearched + nodeString.length() > maxCharsSearched)
466                 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
467             int pos = regExp->searchRev(nodeString);
468             if (pos >= 0) {
469                 if (resultDistance)
470                     *resultDistance = lengthSearched;
471                 return nodeString.substring(pos, regExp->matchedLength());
472             }
473             lengthSearched += nodeString.length();
474         }
475     }
476
477     // If we started in a cell, but bailed because we found the start of the form or the
478     // previous element, we still might need to search the row above us for a label.
479     if (startingTableCell && !searchedCellAbove) {
480         NSString* result = frame->searchForLabelsAboveCell(regExp, startingTableCell, resultDistance);
481         if (result && [result length] > 0) {
482             if (resultIsInCellAbove)
483                 *resultIsInCellAbove = true;
484             return result;
485         }
486     }
487     
488     return nil;
489 }
490
491 static NSString *matchLabelsAgainstString(NSArray *labels, const String& stringToMatch)
492 {
493     if (stringToMatch.isEmpty())
494         return nil;
495     
496     String mutableStringToMatch = stringToMatch;
497     
498     // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
499     replace(mutableStringToMatch, RegularExpression("\\d", TextCaseSensitive), " ");
500     mutableStringToMatch.replace('_', ' ');
501     
502     RegularExpression* regExp = regExpForLabels(labels);
503     // Use the largest match we can find in the whole string
504     int pos;
505     int length;
506     int bestPos = -1;
507     int bestLength = -1;
508     int start = 0;
509     do {
510         pos = regExp->match(mutableStringToMatch, start);
511         if (pos != -1) {
512             length = regExp->matchedLength();
513             if (length >= bestLength) {
514                 bestPos = pos;
515                 bestLength = length;
516             }
517             start = pos + 1;
518         }
519     } while (pos != -1);
520     
521     if (bestPos != -1)
522         return mutableStringToMatch.substring(bestPos, bestLength);
523     return nil;
524 }
525
526 static NSString* matchLabelsAgainstElement(NSArray* labels, Element* element)
527 {
528     // Match against the name element, then against the id element if no match is found for the name element.
529     // See 7538330 for one popular site that benefits from the id element check.
530     String resultFromNameAttribute = matchLabelsAgainstString(labels, element->getAttribute(nameAttr));
531     if (!resultFromNameAttribute.isEmpty())
532         return resultFromNameAttribute;
533     
534     return matchLabelsAgainstString(labels, element->getAttribute(idAttr));
535 }
536
537
538 - (NSString *)searchForLabels:(NSArray *)labels beforeElement:(DOMElement *)element
539 {
540     return [self searchForLabels:labels beforeElement:element resultDistance:0 resultIsInCellAbove:0];
541 }
542
543 - (NSString *)searchForLabels:(NSArray *)labels beforeElement:(DOMElement *)element resultDistance:(NSUInteger*)outDistance resultIsInCellAbove:(BOOL*)outIsInCellAbove
544 {
545     size_t distance;
546     bool isInCellAbove;
547     
548     NSString *result = searchForLabelsBeforeElement(core([_private->dataSource webFrame]), labels, core(element), &distance, &isInCellAbove);
549     
550     if (outDistance) {
551         if (distance == notFound)
552             *outDistance = NSNotFound;
553         else
554             *outDistance = distance;
555     }
556
557     if (outIsInCellAbove)
558         *outIsInCellAbove = isInCellAbove;
559     
560     return result;
561 }
562
563 - (NSString *)matchLabels:(NSArray *)labels againstElement:(DOMElement *)element
564 {
565     return matchLabelsAgainstElement(labels, core(element));
566 }
567
568 @end