WebCore:
[WebKit-https.git] / WebCore / kwq / KWQKHTMLPartImpl.mm
1 /*
2  * Copyright (C) 2001, 2002 Apple Computer, 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  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import <KWQKHTMLPartImpl.h>
27
28 #import <html/htmltokenizer.h>
29
30 #import <html/html_documentimpl.h>
31
32 #import <rendering/render_frames.h>
33
34 #import <khtmlview.h>
35
36 #import <khtmlpart_p.h>
37
38 #import <WebCoreViewFactory.h>
39 #import <WebCoreBridge.h>
40 #import <WCWebDataSource.h>
41
42 #import <kwqdebug.h>
43
44 #undef _KWQ_TIMING
45
46 static void recursive(const DOM::Node &pNode, const DOM::Node &node)
47 {
48     DOM::Node cur_child = node.lastChild();
49
50     KWQDEBUG("cur_child: %s = %s", cur_child.nodeName().string().latin1(), cur_child.nodeValue().string().latin1());
51
52     while(!cur_child.isNull())
53     {
54         recursive(node, cur_child);
55         cur_child = cur_child.previousSibling();
56     }
57 }
58
59 KWQKHTMLPartImpl::KWQKHTMLPartImpl(KHTMLPart *p)
60     : part(p)
61     , d(part->d)
62     , m_redirectionTimer(0)
63     , m_decodingStarted(false)
64 {
65 }
66
67 KWQKHTMLPartImpl::~KWQKHTMLPartImpl()
68 {
69     killTimer(m_redirectionTimer);
70 }
71
72 bool KWQKHTMLPartImpl::openURLInFrame( const KURL &url, const KParts::URLArgs &urlArgs )
73 {
74     WebCoreBridge *frame;
75
76     if (!urlArgs.frameName.isEmpty()) {
77         frame = [bridge frameNamed:urlArgs.frameName.getNSString()];
78         if (frame == nil) {
79             frame = [bridge mainFrame];
80         }
81     } else {
82         frame = bridge;
83     }
84
85     [frame loadURL:url.getNSURL()];
86
87     return true;
88 }
89
90 void KWQKHTMLPartImpl::openURL(const KURL &url)
91 {
92     d->m_workingURL = url;
93     part->m_url = url;
94
95     m_documentSource = "";
96     m_decodingStarted = false;
97 }
98
99 void KWQKHTMLPartImpl::slotData(NSString *encoding, const char *bytes, int length, bool complete)
100 {
101 // NOTE: This code emulates the interface used by the original khtml part  
102     QString enc;
103
104     // This flag is used to tell when a load has completed so we can be sure
105     // to process the data even if we have not yet determined the proper
106     // encoding.
107     if (complete) {
108         d->m_bComplete = true;    
109     }
110
111     if (!d->m_workingURL.isEmpty()) {
112         //begin(d->m_workingURL, d->m_extension->urlArgs().xOffset, d->m_extension->urlArgs().yOffset);
113         part->begin(d->m_workingURL, 0, 0);
114
115         //d->m_doc->docLoader()->setReloading(d->m_bReloading);
116         d->m_workingURL = KURL();
117     }
118
119     if (encoding != NULL) {
120         enc = QString::fromCFString((CFStringRef) encoding);
121         part->setEncoding(enc, true);
122     }
123     
124     // FIXME [rjw]:  Remove this log eventually.  Should never happen.  For debugging
125     // purposes only.
126     if (d->m_doc == 0){
127         fprintf (stderr, "ERROR:  KHTMLPart::slotData m_doc == 0 IGNORING DATA, url = %s\n", part->m_url.url().latin1());
128         return;
129     }
130
131     part->write(bytes, length);
132 }
133
134 // FIXME: Need to remerge this with code in khtml_part.cpp?
135 void KWQKHTMLPartImpl::begin( const KURL &url, int xOffset, int yOffset )
136 {
137   KParts::URLArgs args;
138   args.xOffset = xOffset;
139   args.yOffset = yOffset;
140   d->m_extension->setURLArgs( args );
141
142   // d->m_referrer = url.url();
143   part->m_url = url;
144   KURL baseurl;
145
146   // ### not sure if XHTML documents served as text/xml should use DocumentImpl or HTMLDocumentImpl
147   if (args.serviceType == "text/xml")
148     d->m_doc = DOM::DOMImplementationImpl::instance()->createDocument( d->m_view );
149   else
150     d->m_doc = DOM::DOMImplementationImpl::instance()->createHTMLDocument( d->m_view );
151
152     //DomShared::instanceToCheck = (void *)((DomShared *)d->m_doc);
153     d->m_doc->ref();
154
155     if (m_baseURL.isEmpty()) {
156         m_baseURL = KURL();
157     }
158     else {
159         // If we get here, this means the part has received a redirect before
160         // m_doc was created. Update the document with the base URL.
161         d->m_doc->setBaseURL(m_baseURL.url());
162     }
163     d->m_workingURL = url;
164
165     /* FIXME: this is a pretty gross way to make sure the decoder gets reinitialized for each page. */
166     if (d->m_decoder != NULL) {
167         delete d->m_decoder;
168         d->m_decoder = NULL;
169     }
170
171     //FIXME: do we need this? 
172     if (!d->m_doc->attached())
173         d->m_doc->attach();
174     d->m_doc->setURL( url.url() );
175     
176     // do not set base URL if it has already been set
177     if (!d->m_workingURL.isEmpty() && m_baseURL.isEmpty())
178     {
179         // We're not planning to support the KDE chained URL feature, AFAIK
180 #if KDE_CHAINED_URIS
181         KURL::List lst = KURL::split( d->m_workingURL );
182         KURL baseurl;
183         if ( !lst.isEmpty() )
184             baseurl = *lst.begin();
185         // Use this for relative links.
186         // We prefer m_baseURL over m_url because m_url changes when we are
187         // about to load a new page.
188         setBaseURL(baseurl);
189 #else
190         setBaseURL(d->m_workingURL);
191 #endif
192     }
193
194     /* FIXME: we'll need to make this work....
195     QString userStyleSheet = KHTMLFactory::defaultHTMLSettings()->userStyleSheet();
196     if ( !userStyleSheet.isEmpty() ) {
197         setUserStyleSheet( KURL( userStyleSheet ) );
198     }
199     */
200     
201     d->m_doc->open();    
202
203     d->m_doc->setParsing(true);
204
205 #ifdef _KWQ_TIMING        
206     d->totalWriteTime = 0;
207 #endif
208 }
209
210 // FIXME: Need to remerge this with code in khtml_part.cpp?
211 void KWQKHTMLPartImpl::write( const char *str, int len )
212 {
213     /* FIXME: hook this code back when we have decoders completely working */
214 #if 0
215   if(d->m_bFirstData) {
216       // determine the parse mode
217       d->m_doc->determineParseMode( decoded );
218       d->m_bFirstData = false;
219     
220   //kdDebug(6050) << "KHTMLPart::write haveEnc = " << d->m_haveEncoding << endl;
221       // ### this is still quite hacky, but should work a lot better than the old solution
222       if(d->m_decoder->visuallyOrdered()) d->m_doc->setVisuallyOrdered();
223
224       if (!d->m_haveCharset)
225         {
226             const QTextCodec *c = d->m_decoder->codec();
227             //kdDebug(6005) << "setting up charset to " << (int) KGlobal::charsets()->charsetForEncoding(c->name()) << endl;
228             d->m_charset = KGlobal::charsets()->charsetForEncoding(c->name());
229             d->m_settings->setCharset( d->m_charset );
230             d->m_settings->setScript( KGlobal::charsets()->charsetForEncoding(c->name(), true ));
231             //kdDebug(6005) << "charset is " << (int)d->m_settings->charset() << endl;
232         }
233         d->m_doc->applyChanges(true, true);
234     }
235 #endif
236
237     // begin lines added in lieu of big fixme    
238     if ( !d->m_decoder ) {
239         d->m_decoder = new khtml::Decoder();
240         if(!d->m_encoding.isNull())
241             d->m_decoder->setEncoding(d->m_encoding.latin1(), d->m_haveEncoding);
242         else {
243             //FIXME: d->m_decoder->setEncoding(settings()->encoding().latin1(), d->m_haveEncoding);
244         }
245     }
246     if ( len == 0 )
247         return;
248     
249     if ( len == -1 )
250         len = strlen( str );
251     
252 #ifdef _KWQ_TIMING        
253     double start = CFAbsoluteTimeGetCurrent();
254 #endif
255     
256     // FIX ME:  This is very expensive.  We should using the IFMutableData
257     // that represents the document, and only constructing the complete
258     // string when requested.
259     m_documentSource += QString(str, len);
260
261     QString decoded;
262     if (m_decodingStarted)
263         decoded = d->m_decoder->decode( str, len );
264     else    
265         decoded = d->m_decoder->decode( m_documentSource, m_documentSource.length() );
266
267     if(decoded.isEmpty()){
268         // Check flag to tell whether the load has completed.
269         // If we get here, it means that no text encoding was available.
270         // Try to process what we have with the default encoding.
271         if (d->m_bComplete) {
272             decoded = m_documentSource;
273         }
274         else {
275             fprintf (stderr, "WARNING:  DECODER unable to decode string, length = %d, total length = %d\n", len, m_documentSource.length());
276             return;
277         }
278     }
279
280     m_decodingStarted = true;
281         
282     // Transition from provisional to committed data source at this point.
283     
284 #if FIGURE_OUT_WHAT_APPLY_CHANGES_DOES
285     d->m_doc->applyChanges();
286 #endif    
287
288     // end lines added in lieu of big fixme
289     
290     if (part->jScript())
291         part->jScript()->appendSourceFile(part->m_url.url(),decoded);
292     Tokenizer* t = d->m_doc->tokenizer();
293     if(t)
294         t->write( decoded, true );
295
296 #ifdef _KWQ_TIMING        
297     double thisTime = CFAbsoluteTimeGetCurrent() - start;
298     d->totalWriteTime += thisTime;
299     KWQDEBUGLEVEL (0x200, "%s bytes = %d, seconds = %f, total = %f\n", part->m_url.url().latin1(), len, thisTime, d->totalWriteTime);
300 #endif
301 }
302
303 // FIXME: Need to remerge this with code in khtml_part.cpp?
304 void KWQKHTMLPartImpl::end()
305 {
306     // FIXME [rjw]:  Remove this log eventually.  Should never happen.  For debugging
307     // purposes only.
308     if (d->m_doc == 0){
309         fprintf (stderr, "ERROR:  KHTMLPart::end  m_doc == 0\n");
310         return;
311     }
312
313     d->m_doc->setParsing(false);
314
315     d->m_doc->close();
316     KURL::clearCaches();
317     
318     d->m_view->complete();
319 }
320  
321 bool KWQKHTMLPartImpl::gotoBaseAnchor()
322 {
323     if ( !part->m_url.ref().isEmpty() )
324         return part->gotoAnchor( part->m_url.ref() );
325     return false;
326 }
327
328 // FIXME: Need to remerge this with code in khtml_part.cpp?
329 void KWQKHTMLPartImpl::scheduleRedirection(int delay, const QString &url)
330 {
331     if( d->m_redirectURL.isEmpty() || delay < d->m_delayRedirect )
332     {
333         if (delay < 1) {
334             delay = 1;
335         }
336         d->m_delayRedirect = delay;
337         d->m_redirectURL = url;
338         killTimer(m_redirectionTimer);
339         m_redirectionTimer = startTimer(1000 * d->m_delayRedirect);
340     }
341 }
342
343 void KWQKHTMLPartImpl::timerEvent(QTimerEvent *e)
344 {
345     if (e->timerId()==m_redirectionTimer)
346     {
347         redirectURL();
348         return;
349     }
350 }
351
352 void KWQKHTMLPartImpl::redirectURL()
353 {
354   QString u = d->m_redirectURL;
355   d->m_delayRedirect = 0;
356   d->m_redirectURL = QString::null;
357   if ( u.find( QString::fromLatin1( "javascript:" ), 0, false ) == 0 )
358   {
359     QString script = KURL::decode_string( u.right( u.length() - 11 ) );
360     //kdDebug( 6050 ) << "KHTMLPart::slotRedirect script=" << script << endl;
361     QVariant res = part->executeScript( script );
362     if ( res.type() == QVariant::String ) {
363       part->begin( part->url() );
364       part->write( res.asString() );
365       part->end();
366     }
367     return;
368   }
369
370   KParts::URLArgs args;
371 #if 0
372   if ( urlcmp( u, m_url.url(), true, true ) )
373     args.reload = true;
374
375   args.setLockHistory( true );
376 #endif
377   part->urlSelected( u, 0, 0, QString::null, args );
378 }
379
380
381
382 void KWQKHTMLPartImpl::urlSelected( const QString &url, int button, int state, const QString &_target, KParts::URLArgs )
383 {
384     KURL clickedURL(part->completeURL( url));
385     KURL refLess(clickedURL);
386     WebCoreBridge *frame;
387         
388     if ( url.find( QString::fromLatin1( "javascript:" ), 0, false ) == 0 )
389     {
390         part->executeScript( url.right( url.length() - 11) );
391         return;
392     }
393
394     // Open new window on command-click
395     if (state & MetaButton) {
396         [bridge openNewWindowWithURL:clickedURL.getNSURL()];
397         return;
398     }
399
400     part->m_url.setRef ("");
401     refLess.setRef ("");
402     if (refLess.url() == part->m_url.url()){
403         part->m_url = clickedURL;
404         part->gotoAnchor (clickedURL.ref());
405         return;
406     }
407     
408     if (_target.isEmpty()) {
409         // If we're the only frame in a frameset then pop the frame.
410         if ([[[bridge parent] children] count] == 1) {
411             frame = [bridge parent];
412         } else {
413             frame = bridge;
414         }
415     }
416     else {
417         frame = [bridge frameNamed:_target.getNSString()];
418         if (frame == nil) {
419             // FIXME: What is the correct behavior here? Other browsers seem to open new windows.
420             NSLog (@"ERROR: unable to find frame named %@\n", _target.getNSString());
421             return;
422         }
423     }
424     
425     [frame loadURL:clickedURL.getNSURL()];
426 }
427
428 bool KWQKHTMLPartImpl::requestFrame( khtml::RenderPart *frame, const QString &url, const QString &frameName,
429                                      const QStringList &params, bool isIFrame )
430 {
431     NSString *name = frameName.getNSString();
432
433     KWQDEBUGLEVEL(KWQ_LOG_FRAMES, "name %s\n", DEBUG_OBJECT(name));
434     WebCoreBridge *framePart = [bridge frameNamed:name];
435     if (framePart) {
436         KWQDEBUGLEVEL(KWQ_LOG_FRAMES, "found %s\n", DEBUG_OBJECT(name));
437         frame->setWidget([framePart widget]);
438     }
439     else {        
440         KWQDEBUGLEVEL(KWQ_LOG_FRAMES, "creating %s\n", DEBUG_OBJECT(name));
441         
442         NSURL *childURL = part->completeURL(url).getNSURL();
443         if (childURL == nil || [childURL path] == nil) {
444             NSLog (@"ERROR (probably need to fix CFURL): unable to create URL with path");
445             return false;
446         }
447         
448         HTMLIFrameElementImpl *o = static_cast<HTMLIFrameElementImpl *>(frame->element());
449         if (![bridge createNewFrameNamed:name withURL:childURL
450                 renderPart:frame allowsScrolling:o->scrollingMode() != QScrollView::AlwaysOff
451                 marginWidth:o->getMarginWidth() marginHeight:o->getMarginHeight()]) {
452             return false;
453         }
454     }
455
456 #ifdef _SUPPORT_JAVASCRIPT_URL_    
457     if ( url.find( QString::fromLatin1( "javascript:" ), 0, false ) == 0 && !isIFrame )
458     {
459         // static cast is safe as of isIFrame being false.
460         // but: shouldn't we support this javascript hack for iframes aswell?
461         khtml::RenderFrame* rf = static_cast<khtml::RenderFrame*>(frame);
462         assert(rf);
463         QVariant res = executeScript( DOM::Node(rf->frameImpl()), url.right( url.length() - 11) );
464         if ( res.type() == QVariant::String ) {
465             KURL myurl;
466             myurl.setProtocol("javascript");
467             myurl.setPath(res.asString());
468             return processObjectRequest(&(*it), myurl, QString("text/html") );
469         }
470         return false;
471     }
472 #endif
473
474
475     return true;
476 }
477
478 bool KWQKHTMLPartImpl::requestObject(khtml::RenderPart *frame, const QString &url, const QString &serviceType, const QStringList &args)
479 {
480     if (url.isEmpty()) {
481         return false;
482     }
483     if (frame->widget()) {
484         return true;
485     }
486     
487     NSMutableArray *argsArray = [NSMutableArray arrayWithCapacity:args.count()];
488     for (uint i = 0; i < args.count(); i++) {
489         [argsArray addObject:args[i].getNSString()];
490     }
491     
492     QWidget *widget = new QWidget();
493     widget->setView([[WebCoreViewFactory sharedFactory]
494         viewForPluginWithURL:part->completeURL(url).url().getNSString()
495                     serviceType:serviceType.getNSString()
496                     arguments:argsArray
497                         baseURL:m_baseURL.url().getNSString()]);
498     frame->setWidget(widget);
499     
500     return true;
501 }
502
503 void KWQKHTMLPartImpl::submitForm( const char *action, const QString &url, const QByteArray &formData, const QString &_target, const QString& contentType, const QString& boundary )
504 {
505   QString target = _target;
506   
507   //if ( target.isEmpty() )
508   //  target = d->m_baseTarget;
509
510   KURL u = part->completeURL( url );
511
512   if ( u.isMalformed() )
513   {
514     // ### ERROR HANDLING!
515     return;
516   }
517
518   QString urlstring = u.url();
519
520   if ( urlstring.find( QString::fromLatin1( "javascript:" ), 0, false ) == 0 ) {
521     urlstring = KURL::decode_string(urlstring);
522     part->executeScript( urlstring.right( urlstring.length() - 11) );
523     return;
524   }
525
526 #ifdef NEED_THIS
527   if (!checkLinkSecurity(u,
528                          i18n( "<qt>The form will be submitted to <BR><B>%1</B><BR>on your local filesystem.<BR>Do you want to submit the form?" ),
529                          i18n( "Submit" )))
530     return;
531 #endif
532
533 #ifdef NEED_THIS
534   KParts::URLArgs args;
535
536   if (!d->m_referrer.isEmpty())
537      args.metaData()["referrer"] = d->m_referrer;
538
539   args.metaData().insert("main_frame_request",
540                          parentPart() == 0 ? "TRUE":"FALSE");
541   args.metaData().insert("ssl_was_in_use", d->m_ssl_in_use ? "TRUE":"FALSE");
542   args.metaData().insert("ssl_activate_warnings", "TRUE");
543   args.frameName = _target.isEmpty() ? d->m_doc->baseTarget() : _target ;
544 #endif
545
546   if ( strcmp( action, "get" ) == 0 )
547   {
548     u.setQuery( QString( formData.data(), formData.size() ) );
549     [bridge loadURL:u.getNSURL()];
550
551 #ifdef NEED_THIS
552     args.frameName = target;
553     args.setDoPost( false );
554 #endif
555   }
556   else
557   {
558 #ifdef NEED_THIS
559     args.postData = formData;
560     args.frameName = target;
561     args.setDoPost( true );
562
563     // construct some user headers if necessary
564     if (contentType.isNull() || contentType == "application/x-www-form-urlencoded")
565       args.setContentType( "Content-Type: application/x-www-form-urlencoded" );
566     else // contentType must be "multipart/form-data"
567       args.setContentType( "Content-Type: " + contentType + "; boundary=" + boundary );
568 #endif
569     NSData *postData = [NSData dataWithBytes:formData.data() length:formData.size()];
570     [bridge postWithURL:u.getNSURL() data:postData];
571   }
572
573 #ifdef NEED_THIS
574   if ( d->m_bParsing || d->m_runningScripts > 0 ) {
575     if( d->m_submitForm ) {
576         return;
577     }
578     d->m_submitForm = new KHTMLPartPrivate::SubmitForm;
579     d->m_submitForm->submitAction = action;
580     d->m_submitForm->submitUrl = url;
581     d->m_submitForm->submitFormData = formData;
582     d->m_submitForm->target = _target;
583     d->m_submitForm->submitContentType = contentType;
584     d->m_submitForm->submitBoundary = boundary;
585     connect(this, SIGNAL(completed()), this, SLOT(submitFormAgain()));
586   }
587   else
588     emit d->m_extension->openURLRequest( u, args );
589 #endif
590 }
591
592 bool KWQKHTMLPartImpl::frameExists( const QString &frameName )
593 {
594     return [bridge frameNamed:frameName.getNSString()];
595 }
596
597 QPtrList<KParts::ReadOnlyPart> KWQKHTMLPartImpl::frames() const
598 {
599     QPtrList<KParts::ReadOnlyPart> res;
600     NSArray *children = [bridge children];
601     WebCoreBridge *childPart;
602     unsigned int i;
603     
604     for (i = 0; i < [children count]; i++){
605         childPart = [children objectAtIndex: i];
606         res.append([childPart part]);
607     }
608     return res;
609 }
610
611 QString KWQKHTMLPartImpl::documentSource() const
612 {
613     return m_documentSource;
614 }
615
616 void KWQKHTMLPartImpl::setBaseURL(const KURL &url)
617 {
618     m_baseURL = url;
619     if (m_baseURL.protocol().startsWith( "http" ) && !m_baseURL.host().isEmpty() && m_baseURL.path().isEmpty()) {
620         m_baseURL.setPath( "/" );
621         // communicate the change in base URL to the document so that links and subloads work
622         if (d->m_doc) {
623             d->m_doc->setBaseURL(url.url());
624         }
625     }
626 }
627
628 void KWQKHTMLPartImpl::setView(KHTMLView *view)
629 {
630     d->m_view = view;
631 }
632
633 void KWQKHTMLPartImpl::setTitle(const DOMString &title)
634 {
635     [bridge setTitle:title.string().getNSString()];
636 }
637
638 IFWebDataSource *KWQKHTMLPartImpl::getDataSource()
639 {
640     return [bridge dataSource];
641 }
642
643 KHTMLPart *KWQKHTMLPartImpl::parentPart()
644 {
645     return [[bridge parent] part];
646 }