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