2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #import "KWQKHTMLPart.h"
28 #import "DOMInternal.h"
29 #import "KWQClipboard.h"
30 #import "KWQDOMNode.h"
31 #import "KWQDummyView.h"
32 #import "KWQEditCommand.h"
33 #import "KWQExceptions.h"
34 #import "KWQKJobClasses.h"
35 #import "KWQLogging.h"
36 #import "KWQPageState.h"
37 #import "KWQPrinter.h"
38 #import "KWQScrollBar.h"
39 #import "KWQWindowWidget.h"
40 #import "KWQFoundationExtras.h"
41 #import "WebCoreBridge.h"
42 #import "WebCoreGraphicsBridge.h"
43 #import "WebCoreViewFactory.h"
45 #import "dom2_eventsimpl.h"
46 #import "dom2_rangeimpl.h"
47 #import "dom_position.h"
48 #import "dom_selection.h"
49 #import "dom_textimpl.h"
50 #import "html_documentimpl.h"
53 #import "htmltokenizer.h"
54 #import "khtml_text_operations.h"
55 #import "khtmlpart_p.h"
57 #import "kjs_binding.h"
58 #import "kjs_window.h"
59 #import "render_canvas.h"
60 #import "render_frames.h"
61 #import "render_image.h"
62 #import "render_list.h"
63 #import "render_style.h"
64 #import "render_table.h"
65 #import "render_text.h"
66 #import <JavaScriptCore/identifier.h>
67 #import <JavaScriptCore/property_map.h>
68 #import <JavaScriptCore/runtime.h>
69 #import <JavaScriptCore/runtime_root.h>
70 #import <JavaScriptCore/WebScriptObjectPrivate.h>
74 using DOM::AtomicString;
75 using DOM::ClipboardEventImpl;
76 using DOM::DocumentImpl;
77 using DOM::DocumentMarker;
79 using DOM::ElementImpl;
81 using DOM::HTMLDocumentImpl;
82 using DOM::HTMLElementImpl;
83 using DOM::HTMLFormElementImpl;
84 using DOM::HTMLFrameElementImpl;
85 using DOM::HTMLGenericFormElementImpl;
86 using DOM::HTMLTableCellElementImpl;
96 using khtml::CharacterIterator;
97 using khtml::ChildFrame;
99 using khtml::findPlainText;
100 using khtml::InlineTextBox;
101 using khtml::MouseDoubleClickEvent;
102 using khtml::MouseMoveEvent;
103 using khtml::MousePressEvent;
104 using khtml::MouseReleaseEvent;
105 using khtml::parseURL;
107 using khtml::RenderCanvas;
108 using khtml::RenderImage;
109 using khtml::RenderLayer;
110 using khtml::RenderListItem;
111 using khtml::RenderObject;
112 using khtml::RenderPart;
113 using khtml::RenderStyle;
114 using khtml::RenderTableCell;
115 using khtml::RenderText;
116 using khtml::RenderWidget;
117 using khtml::TextIterator;
118 using khtml::VISIBLE;
119 using khtml::WordAwareIterator;
123 using KJS::Interpreter;
125 using KJS::SavedBuiltins;
126 using KJS::SavedProperties;
127 using KJS::ScheduledAction;
130 using KJS::Bindings::Instance;
132 using KParts::ReadOnlyPart;
133 using KParts::URLArgs;
135 NSEvent *KWQKHTMLPart::_currentEvent = nil;
136 NSResponder *KWQKHTMLPart::_firstResponderAtMouseDownTime = nil;
138 void KHTMLPart::completed()
140 KWQ(this)->_completed.call();
143 void KHTMLPart::completed(bool arg)
145 KWQ(this)->_completed.call(arg);
148 void KHTMLPart::nodeActivated(const Node &)
152 bool KHTMLPart::openURL(const KURL &URL)
154 ASSERT_NOT_REACHED();
158 void KHTMLPart::onURL(const QString &)
162 void KHTMLPart::setStatusBarText(const QString &status)
164 KWQ(this)->setStatusBarText(status);
167 void KHTMLPart::started(Job *j)
169 KWQ(this)->_started.call(j);
172 static void redirectionTimerMonitor(void *context)
174 KWQKHTMLPart *kwq = static_cast<KWQKHTMLPart *>(context);
175 kwq->redirectionTimerStartedOrStopped();
178 KWQKHTMLPart::KWQKHTMLPart()
179 : _started(this, SIGNAL(started(KIO::Job *)))
180 , _completed(this, SIGNAL(completed()))
181 , _completedWithBool(this, SIGNAL(completed(bool)))
182 , _mouseDownView(nil)
183 , _sendingEventToSubview(false)
184 , _mouseDownMayStartDrag(false)
185 , _mouseDownMayStartSelect(false)
186 , _activationEventNumber(0)
187 , _formValuesAboutToBeSubmitted(nil)
188 , _formAboutToBeSubmitted(nil)
189 , _windowWidget(NULL)
190 , _displaysWithFocusAttributes(false)
191 , _drawSelectionOnly(false)
193 , _windowScriptObject(0)
194 , _windowScriptNPObject(0)
199 // Must init the cache before connecting to any signals
202 // The widget is made outside this class in our case.
203 KHTMLPart::init( 0, DefaultGUI );
205 mutableInstances().prepend(this);
206 d->m_redirectionTimer.setMonitor(redirectionTimerMonitor, this);
209 KWQKHTMLPart::~KWQKHTMLPart()
211 cleanupPluginRootObjects();
213 mutableInstances().remove(this);
218 // these are all basic Foundation classes and our own classes - we
219 // know they will not raise in dealloc, so no need to block
221 KWQRelease(_formValuesAboutToBeSubmitted);
222 KWQRelease(_formAboutToBeSubmitted);
224 KWQRelease(_windowScriptObject);
226 delete _windowWidget;
229 void KWQKHTMLPart::freeClipboard()
231 if (_dragClipboard) {
232 _dragClipboard->setAccessPolicy(KWQClipboard::Numb);
233 _dragClipboard->deref();
238 void KWQKHTMLPart::setSettings (KHTMLSettings *settings)
240 d->m_settings = settings;
243 QString KWQKHTMLPart::generateFrameName()
245 KWQ_BLOCK_EXCEPTIONS;
246 return QString::fromNSString([_bridge generateFrameName]);
247 KWQ_UNBLOCK_EXCEPTIONS;
252 void KWQKHTMLPart::provisionalLoadStarted()
254 // we don't want to wait until we get an actual http response back
255 // to cancel pending redirects, otherwise they might fire before
257 cancelRedirection(true);
260 bool KWQKHTMLPart::openURL(const KURL &url)
262 KWQ_BLOCK_EXCEPTIONS;
264 bool userGesture = true;
266 if (jScript() && jScript()->interpreter()) {
267 KHTMLPart *rootPart = this;
268 while (rootPart->parentPart() != 0)
269 rootPart = rootPart->parentPart();
270 KJS::ScriptInterpreter *interpreter = static_cast<KJS::ScriptInterpreter *>(KJSProxy::proxy(rootPart)->interpreter());
271 userGesture = interpreter->wasRunByUserGesture();
274 // FIXME: The lack of args here to get the reload flag from
275 // indicates a problem in how we use KHTMLPart::processObjectRequest,
276 // where we are opening the URL before the args are set up.
277 [_bridge loadURL:url.getNSURL()
278 referrer:[_bridge referrer]
280 userGesture:userGesture
286 KWQ_UNBLOCK_EXCEPTIONS;
291 void KWQKHTMLPart::openURLRequest(const KURL &url, const URLArgs &args)
293 KWQ_BLOCK_EXCEPTIONS;
295 [_bridge loadURL:url.getNSURL()
296 referrer:[_bridge referrer]
299 target:args.frameName.getNSString()
304 KWQ_UNBLOCK_EXCEPTIONS;
307 void KWQKHTMLPart::didNotOpenURL(const KURL &URL)
309 if (_submittedFormURL == URL) {
310 _submittedFormURL = KURL();
314 // Scans logically forward from "start", including any child frames
315 static HTMLFormElementImpl *scanForForm(NodeImpl *start)
318 for (n = start; n; n = n->traverseNextNode()) {
319 NodeImpl::Id nodeID = idFromNode(n);
320 if (nodeID == ID_FORM) {
321 return static_cast<HTMLFormElementImpl *>(n);
322 } else if (n->isHTMLElement()
323 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
324 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
325 } else if (nodeID == ID_FRAME || nodeID == ID_IFRAME) {
326 NodeImpl *childDoc = static_cast<HTMLFrameElementImpl *>(n)->contentDocument();
327 HTMLFormElementImpl *frameResult = scanForForm(childDoc);
336 // We look for either the form containing the current focus, or for one immediately after it
337 HTMLFormElementImpl *KWQKHTMLPart::currentForm() const
339 // start looking either at the active (first responder) node, or where the selection is
340 NodeImpl *start = activeNode().handle();
342 start = selectionStart();
345 // try walking up the node tree to find a form element
347 for (n = start; n; n = n->parentNode()) {
348 if (idFromNode(n) == ID_FORM) {
349 return static_cast<HTMLFormElementImpl *>(n);
350 } else if (n->isHTMLElement()
351 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
352 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
356 // try walking forward in the node tree to find a form element
358 start = xmlDocImpl();
360 return scanForForm(start);
363 // Either get cached regexp or build one that matches any of the labels.
364 // The regexp we build is of the form: (STR1|STR2|STRN)
365 QRegExp *regExpForLabels(NSArray *labels)
367 // All the ObjC calls in this method are simple array and string
368 // calls which we can assume do not raise exceptions
371 // Parallel arrays that we use to cache regExps. In practice the number of expressions
372 // that the app will use is equal to the number of locales is used in searching.
373 static const unsigned int regExpCacheSize = 4;
374 static NSMutableArray *regExpLabels = nil;
375 static QPtrList <QRegExp> regExps;
376 static QRegExp wordRegExp = QRegExp("\\w");
380 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
382 unsigned int cacheHit = [regExpLabels indexOfObject:labels];
383 if (cacheHit != NSNotFound) {
384 result = regExps.at(cacheHit);
386 QString pattern("(");
387 unsigned int numLabels = [labels count];
389 for (i = 0; i < numLabels; i++) {
390 QString label = QString::fromNSString([labels objectAtIndex:i]);
392 bool startsWithWordChar = false;
393 bool endsWithWordChar = false;
394 if (label.length() != 0) {
395 startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
396 endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0;
402 // Search for word boundaries only if label starts/ends with "word characters".
403 // If we always searched for word boundaries, this wouldn't work for languages
405 if (startsWithWordChar) {
406 pattern.append("\\b");
408 pattern.append(label);
409 if (endsWithWordChar) {
410 pattern.append("\\b");
414 result = new QRegExp(pattern, false);
417 // add regexp to the cache, making sure it is at the front for LRU ordering
419 if (cacheHit != NSNotFound) {
420 // remove from old spot
421 [regExpLabels removeObjectAtIndex:cacheHit];
422 regExps.remove(cacheHit);
425 [regExpLabels insertObject:labels atIndex:0];
426 regExps.insert(0, result);
428 if ([regExpLabels count] > regExpCacheSize) {
429 [regExpLabels removeObjectAtIndex:regExpCacheSize];
430 QRegExp *last = regExps.last();
431 regExps.removeLast();
438 NSString *KWQKHTMLPart::searchForLabelsAboveCell(QRegExp *regExp, HTMLTableCellElementImpl *cell)
440 RenderTableCell *cellRenderer = static_cast<RenderTableCell *>(cell->renderer());
441 RenderTableCell *cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
443 if (cellAboveRenderer) {
444 HTMLTableCellElementImpl *aboveCell =
445 static_cast<HTMLTableCellElementImpl *>(cellAboveRenderer->element());
448 // search within the above cell we found for a match
449 for (NodeImpl *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
450 if (idFromNode(n) == ID_TEXT
451 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
453 // For each text chunk, run the regexp
454 QString nodeString = n->nodeValue().string();
455 int pos = regExp->searchRev(nodeString);
457 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
463 // Any reason in practice to search all cells in that are above cell?
467 NSString *KWQKHTMLPart::searchForLabelsBeforeElement(NSArray *labels, ElementImpl *element)
469 QRegExp *regExp = regExpForLabels(labels);
470 // We stop searching after we've seen this many chars
471 const unsigned int charsSearchedThreshold = 500;
472 // This is the absolute max we search. We allow a little more slop than
473 // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
474 const unsigned int maxCharsSearched = 600;
475 // If the starting element is within a table, the cell that contains it
476 HTMLTableCellElementImpl *startingTableCell = 0;
477 bool searchedCellAbove = false;
479 // walk backwards in the node tree, until another element, or form, or end of tree
480 int unsigned lengthSearched = 0;
482 for (n = element->traversePreviousNode();
483 n && lengthSearched < charsSearchedThreshold;
484 n = n->traversePreviousNode())
486 NodeImpl::Id nodeID = idFromNode(n);
487 if (nodeID == ID_FORM
488 || (n->isHTMLElement()
489 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()))
491 // We hit another form element or the start of the form - bail out
493 } else if (nodeID == ID_TD && !startingTableCell) {
494 startingTableCell = static_cast<HTMLTableCellElementImpl *>(n);
495 } else if (nodeID == ID_TR && startingTableCell) {
496 NSString *result = searchForLabelsAboveCell(regExp, startingTableCell);
500 searchedCellAbove = true;
501 } else if (nodeID == ID_TEXT
502 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
504 // For each text chunk, run the regexp
505 QString nodeString = n->nodeValue().string();
506 // add 100 for slop, to make it more likely that we'll search whole nodes
507 if (lengthSearched + nodeString.length() > maxCharsSearched) {
508 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
510 int pos = regExp->searchRev(nodeString);
512 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
514 lengthSearched += nodeString.length();
519 // If we started in a cell, but bailed because we found the start of the form or the
520 // previous element, we still might need to search the row above us for a label.
521 if (startingTableCell && !searchedCellAbove) {
522 return searchForLabelsAboveCell(regExp, startingTableCell);
528 NSString *KWQKHTMLPart::matchLabelsAgainstElement(NSArray *labels, ElementImpl *element)
530 QString name = element->getAttribute(ATTR_NAME).string();
531 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
532 name.replace(QRegExp("[[:digit:]]"), " ");
533 name.replace('_', ' ');
535 QRegExp *regExp = regExpForLabels(labels);
536 // Use the largest match we can find in the whole name string
543 pos = regExp->search(name, start);
545 length = regExp->matchedLength();
546 if (length >= bestLength) {
555 return name.mid(bestPos, bestLength).getNSString();
561 // Search from the end of the currently selected location if we are first responder, or from
562 // the beginning of the document if nothing is selected or we're not first responder.
563 bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag)
565 QString target = QString::fromNSString(string);
566 if (target.isEmpty()) {
570 // Start on the correct edge of the selection, search to edge of document.
571 Range searchRange(xmlDocImpl());
572 searchRange.selectNodeContents(xmlDocImpl());
573 if (selectionStart()) {
575 // Must ensure the position is on a container to be usable with a DOMRange
576 Position selEnd = selection().rangeEnd();
577 searchRange.setStart(selEnd.node(), selEnd.offset());
579 // Must ensure the position is on a container to be usable with a DOMRange
580 Position selStart = selection().rangeStart();
581 searchRange.setEnd(selStart.node(), selStart.offset());
585 // Do the search once, then do it a second time to handle wrapped search.
586 // Searches some or all of document twice in the failure case, but that's probably OK.
587 Range resultRange = findPlainText(searchRange, target, forward, caseFlag);
588 if (resultRange.collapsed() && wrapFlag) {
589 searchRange.selectNodeContents(xmlDocImpl());
590 resultRange = findPlainText(searchRange, target, forward, caseFlag);
591 // If we got back to the same place we started, that doesn't count as success.
592 if (resultRange == selection().toRange()) {
597 if (resultRange.collapsed()) {
601 setSelection(resultRange);
606 void KWQKHTMLPart::clearRecordedFormValues()
608 // It's safe to assume that our own classes and Foundation data
609 // structures won't raise exceptions in dealloc
611 KWQRelease(_formValuesAboutToBeSubmitted);
612 _formValuesAboutToBeSubmitted = nil;
613 KWQRelease(_formAboutToBeSubmitted);
614 _formAboutToBeSubmitted = nil;
617 void KWQKHTMLPart::recordFormValue(const QString &name, const QString &value, HTMLFormElementImpl *element)
619 // It's safe to assume that our own classes and basic Foundation
620 // data structures won't raise exceptions
622 if (!_formValuesAboutToBeSubmitted) {
623 _formValuesAboutToBeSubmitted = KWQRetainNSRelease([[NSMutableDictionary alloc] init]);
624 ASSERT(!_formAboutToBeSubmitted);
625 _formAboutToBeSubmitted = KWQRetain([DOMElement _elementWithImpl:element]);
627 ASSERT([_formAboutToBeSubmitted _elementImpl] == element);
629 [_formValuesAboutToBeSubmitted setObject:value.getNSString() forKey:name.getNSString()];
632 void KWQKHTMLPart::submitForm(const KURL &url, const URLArgs &args)
634 KWQ_BLOCK_EXCEPTIONS;
636 // The form multi-submit logic here is only right when we are submitting a form that affects this frame.
637 // Eventually when we find a better fix we can remove this altogether.
638 WebCoreBridge *target = args.frameName.isEmpty() ? _bridge : [_bridge findFrameNamed:args.frameName.getNSString()];
639 KHTMLPart *targetPart = [target part];
640 bool willReplaceThisFrame = false;
641 for (KHTMLPart *p = this; p; p = p->parentPart()) {
642 if (p == targetPart) {
643 willReplaceThisFrame = true;
647 if (willReplaceThisFrame) {
648 // We do not want to submit more than one form from the same page,
649 // nor do we want to submit a single form more than once.
650 // This flag prevents these from happening.
651 // Note that the flag is reset in setView()
652 // since this part may get reused if it is pulled from the b/f cache.
653 // Only do this for http and https since users may want to submit forms
654 // more than once for other schemes.
655 QString protocol = url.protocol().lower();
656 if (_submittedFormURL == url && (protocol == "http" || protocol == "https")) {
659 _submittedFormURL = url;
662 if (!args.doPost()) {
663 [_bridge loadURL:url.getNSURL()
664 referrer:[_bridge referrer]
667 target:args.frameName.getNSString()
668 triggeringEvent:_currentEvent
669 form:_formAboutToBeSubmitted
670 formValues:_formValuesAboutToBeSubmitted];
672 ASSERT(args.contentType().startsWith("Content-Type: "));
673 [_bridge postWithURL:url.getNSURL()
674 referrer:[_bridge referrer]
675 target:args.frameName.getNSString()
676 data:[NSData dataWithBytes:args.postData.data() length:args.postData.size()]
677 contentType:args.contentType().mid(14).getNSString()
678 triggeringEvent:_currentEvent
679 form:_formAboutToBeSubmitted
680 formValues:_formValuesAboutToBeSubmitted];
682 clearRecordedFormValues();
684 KWQ_UNBLOCK_EXCEPTIONS;
687 void KWQKHTMLPart::setEncoding(const QString &name, bool userChosen)
689 if (!d->m_workingURL.isEmpty()) {
692 d->m_encoding = name;
693 d->m_haveEncoding = userChosen;
696 void KWQKHTMLPart::addData(const char *bytes, int length)
698 ASSERT(d->m_workingURL.isEmpty());
700 ASSERT(d->m_doc->parsing());
701 write(bytes, length);
704 void KHTMLPart::frameDetached()
706 // FIXME: This should be a virtual function, with the first part in KWQKHTMLPart, and the second
707 // part in KHTMLPart, so it works for KHTML too.
709 KWQ_BLOCK_EXCEPTIONS;
710 [KWQ(this)->bridge() frameDetached];
711 KWQ_UNBLOCK_EXCEPTIONS;
713 KHTMLPart *parent = parentPart();
715 FrameList& parentFrames = parent->d->m_frames;
716 FrameIt end = parentFrames.end();
717 for (FrameIt it = parentFrames.begin(); it != end; ++it) {
718 ChildFrame &child = *it;
719 if (child.m_part == this) {
720 parent->disconnectChild(&child);
721 parentFrames.remove(it);
729 void KWQKHTMLPart::urlSelected(const KURL &url, int button, int state, const URLArgs &args)
731 KWQ_BLOCK_EXCEPTIONS;
732 [_bridge loadURL:url.getNSURL()
733 referrer:[_bridge referrer]
736 target:args.frameName.getNSString()
737 triggeringEvent:_currentEvent
740 KWQ_UNBLOCK_EXCEPTIONS;
743 class KWQPluginPart : public ReadOnlyPart
745 virtual bool openURL(const KURL &) { return true; }
746 virtual bool closeURL() { return true; }
749 ReadOnlyPart *KWQKHTMLPart::createPart(const ChildFrame &child, const KURL &url, const QString &mimeType)
751 KWQ_BLOCK_EXCEPTIONS;
754 BOOL needFrame = [_bridge frameRequiredForMIMEType:mimeType.getNSString() URL:url.getNSURL()];
755 if (child.m_type == ChildFrame::Object && !needFrame) {
756 NSMutableArray *attributesArray = [NSMutableArray arrayWithCapacity:child.m_params.count()];
757 for (uint i = 0; i < child.m_params.count(); i++) {
758 [attributesArray addObject:child.m_params[i].getNSString()];
761 KWQPluginPart *newPart = new KWQPluginPart;
762 newPart->setWidget(new QWidget([_bridge viewForPluginWithURL:url.getNSURL()
763 attributes:attributesArray
764 baseURL:KURL(d->m_doc->baseURL()).getNSURL()
765 MIMEType:child.m_args.serviceType.getNSString()]));
768 LOG(Frames, "name %s", child.m_name.ascii());
769 BOOL allowsScrolling = YES;
770 int marginWidth = -1;
771 int marginHeight = -1;
772 if (child.m_type != ChildFrame::Object) {
773 HTMLFrameElementImpl *o = static_cast<HTMLFrameElementImpl *>(child.m_frame->element());
774 allowsScrolling = o->scrollingMode() != QScrollView::AlwaysOff;
775 marginWidth = o->getMarginWidth();
776 marginHeight = o->getMarginHeight();
778 WebCoreBridge *childBridge = [_bridge createChildFrameNamed:child.m_name.getNSString()
779 withURL:url.getNSURL()
780 renderPart:child.m_frame
781 allowsScrolling:allowsScrolling
782 marginWidth:marginWidth
783 marginHeight:marginHeight];
784 // This call needs to return an object with a ref, since the caller will expect to own it.
785 // childBridge owns the only ref so far.
786 [childBridge part]->ref();
787 part = [childBridge part];
792 KWQ_UNBLOCK_EXCEPTIONS;
797 void KWQKHTMLPart::setView(KHTMLView *view)
799 // Detach the document now, so any onUnload handlers get run - if
800 // we wait until the view is destroyed, then things won't be
801 // hooked up enough for some JavaScript calls to work.
802 if (d->m_doc && view == NULL) {
815 // Only one form submission is allowed per view of a part.
816 // Since this part may be getting reused as a result of being
817 // pulled from the back/forward cache, reset this flag.
818 _submittedFormURL = KURL();
821 KHTMLView *KWQKHTMLPart::view() const
826 void KWQKHTMLPart::setTitle(const DOMString &title)
828 QString text = title.string();
829 text.replace('\\', backslashAsCurrencySymbol());
831 KWQ_BLOCK_EXCEPTIONS;
832 [_bridge setTitle:text.getNSString()];
833 KWQ_UNBLOCK_EXCEPTIONS;
836 void KWQKHTMLPart::setStatusBarText(const QString &status)
838 QString text = status;
839 text.replace('\\', backslashAsCurrencySymbol());
841 KWQ_BLOCK_EXCEPTIONS;
842 [_bridge setStatusText:text.getNSString()];
843 KWQ_UNBLOCK_EXCEPTIONS;
846 void KWQKHTMLPart::scheduleClose()
848 KWQ_BLOCK_EXCEPTIONS;
849 [_bridge closeWindowSoon];
850 KWQ_UNBLOCK_EXCEPTIONS;
853 void KWQKHTMLPart::unfocusWindow()
855 KWQ_BLOCK_EXCEPTIONS;
856 [_bridge unfocusWindow];
857 KWQ_UNBLOCK_EXCEPTIONS;
860 void KWQKHTMLPart::jumpToSelection()
862 // Assumes that selection will only ever be text nodes. This is currently
863 // true, but will it always be so?
864 if (d->m_selection.start().notEmpty()) {
865 RenderText *rt = dynamic_cast<RenderText *>(d->m_selection.start().node()->renderer());
868 rt->posOfChar(d->m_selection.start().offset(), x, y);
869 // The -50 offset is copied from KHTMLPart::findTextNext, which sets the contents position
870 // after finding a matched text string.
871 d->m_view->setContentsPos(x - 50, y - 50);
874 Something like this would fix <rdar://problem/3154293>: "Find Next should not scroll page if the next target is already visible"
876 I think this would be a better way to do this, to avoid needless horizontal scrolling,
877 but it is not feasible until selectionRect() returns a tighter rect around the
878 selected text. Right now it works at element granularity.
880 NSView *docView = d->m_view->getDocumentView();
882 KWQ_BLOCK_EXCEPTIONS;
883 NSRect selRect = NSRect(selectionRect());
884 NSRect visRect = [docView visibleRect];
885 if (!NSContainsRect(visRect, selRect)) {
886 // pad a bit so we overscroll slightly
887 selRect = NSInsetRect(selRect, -10.0, -10.0);
888 selRect = NSIntersectionRect(selRect, [docView bounds]);
889 [docView scrollRectToVisible:selRect];
891 KWQ_UNBLOCK_EXCEPTIONS;
896 QString KWQKHTMLPart::advanceToNextMisspelling()
898 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
899 // the we wrap and search from the doc start to (approximately) where we started.
901 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
902 // repeated "check spelling" commands work.
903 Range searchRange(xmlDocImpl());
904 searchRange.selectNodeContents(xmlDocImpl());
905 bool startedWithSelection = false;
906 if (selectionStart()) {
907 startedWithSelection = true;
908 // Must ensure the position is on a container to be usable with a DOMRange
909 Position selEnd = selection().rangeEnd();
910 searchRange.setStart(selEnd.node(), selEnd.offset());
913 // If we're not in an editable node, try to find one, make that our range to work in
914 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
915 if (!editableNodeImpl->isContentEditable()) {
916 editableNodeImpl = editableNodeImpl->nextEditable();
917 if (!editableNodeImpl) {
920 searchRange.setStartBefore(Node(editableNodeImpl));
921 startedWithSelection = false; // won't need to wrap
924 // topNode defines the whole range we want to operate on
925 Node topNode(editableNodeImpl->rootEditableElement());
926 searchRange.setEndAfter(topNode);
928 // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then
929 // forward by a word happens to do the trick.
930 if (startedWithSelection) {
931 Position start(searchRange.startContainer().handle(), searchRange.startOffset());
932 Position newStart = start.previousCharacterPosition();
933 if (newStart != start) {
934 newStart = newStart.nextWordBoundary();
935 // Must ensure the position is on a container to be usable with a DOMRange
936 newStart = newStart.equivalentRangeCompliantPosition();
937 searchRange.setStart(Node(newStart.node()), newStart.offset());
938 } // else we were already at the start of the editable node
941 if (searchRange.collapsed()) {
942 return QString(); // nothing to search in
945 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
946 WordAwareIterator it(searchRange);
947 bool wrapped = false;
949 // We go to the end of our first range instead of the start of it, just to be sure
950 // we don't get foiled by any word boundary problems at the start. It means we might
951 // do a tiny bit more searching.
952 Node searchEndAfterWrapNode = it.range().endContainer();
953 long searchEndAfterWrapOffset = it.range().endOffset();
956 if (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
957 const QChar *chars = it.characters();
958 long len = it.length();
959 if (len > 1 || !chars[0].isSpace()) {
960 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
961 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
963 if (misspelling.length > 0) {
964 // Build up result range and string. Note the misspelling may span many text nodes,
965 // but the CharIterator insulates us from this complexity
966 Range misspellingRange(xmlDocImpl());
967 CharacterIterator chars(it.range());
968 chars.advance(misspelling.location);
969 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
970 QString result = chars.string(misspelling.length);
971 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
973 setSelection(misspellingRange);
975 // Mark misspelling in document.
976 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
984 if (wrapped || !startedWithSelection) {
985 break; // finished the second range, or we did the whole doc with the first range
987 // we've gone from the selection to the end of doc, now wrap around
989 searchRange.setStartBefore(topNode);
990 // going until the end of the very first chunk we tested is far enough
991 searchRange.setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset);
992 it = WordAwareIterator(searchRange);
1000 void KWQKHTMLPart::redirectionTimerStartedOrStopped()
1002 // Don't report history navigations, just actual redirection.
1003 if (d->m_scheduledRedirection == historyNavigationScheduled) {
1007 KWQ_BLOCK_EXCEPTIONS;
1008 if (d->m_redirectionTimer.isActive()) {
1009 [_bridge reportClientRedirectToURL:KURL(d->m_redirectURL).getNSURL()
1010 delay:d->m_delayRedirect
1011 fireDate:[d->m_redirectionTimer.getNSTimer() fireDate]
1012 lockHistory:d->m_redirectLockHistory
1013 isJavaScriptFormAction:d->m_executingJavaScriptFormAction];
1015 [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress];
1017 KWQ_UNBLOCK_EXCEPTIONS;
1020 void KWQKHTMLPart::paint(QPainter *p, const QRect &rect)
1024 if (p->device()->devType() == QInternal::Printer)
1025 fillWithRed = false; // Printing, don't fill with red (can't remember why).
1026 else if (!xmlDocImpl() || xmlDocImpl()->ownerElement())
1027 fillWithRed = false; // Subframe, don't fill with red.
1028 else if (view() && view()->isTransparent())
1029 fillWithRed = false; // Transparent, don't fill with red.
1030 else if (_drawSelectionOnly)
1031 fillWithRed = false; // Selections are transparent, don't fill with red.
1032 else if (_elementToDraw != 0)
1033 fillWithRed = false; // Element images are transparent, don't fill with red.
1038 p->fillRect(rect.x(), rect.y(), rect.width(), rect.height(), QColor(0xFF, 0, 0));
1043 // _elementToDraw is used to draw only one element
1044 RenderObject *eltRenderer = (_elementToDraw != 0) ? _elementToDraw.handle()->renderer() : 0;
1045 renderer()->layer()->paint(p, rect, _drawSelectionOnly, eltRenderer);
1047 ERROR("called KWQKHTMLPart::paint with nil renderer");
1051 void KWQKHTMLPart::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit)
1053 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1055 // Use a printer device, with painting disabled for the pagination phase
1056 QPainter painter(true);
1057 painter.setPaintingDisabled(true);
1059 root->setTruncatedAt((int)floor(oldBottom));
1060 QRect dirtyRect(0, (int)floor(oldTop),
1061 root->docWidth(), (int)ceil(oldBottom-oldTop));
1062 root->layer()->paint(&painter, dirtyRect);
1063 *newBottom = root->bestTruncatedAt();
1064 if (*newBottom == 0) {
1065 *newBottom = oldBottom;
1068 *newBottom = oldBottom;
1072 RenderObject *KWQKHTMLPart::renderer()
1074 DocumentImpl *doc = xmlDocImpl();
1075 return doc ? doc->renderer() : 0;
1078 QString KWQKHTMLPart::userAgent() const
1080 KWQ_BLOCK_EXCEPTIONS;
1081 return QString::fromNSString([_bridge userAgentForURL:m_url.getNSURL()]);
1082 KWQ_UNBLOCK_EXCEPTIONS;
1087 QString KWQKHTMLPart::mimeTypeForFileName(const QString &fileName) const
1089 NSString *ns = fileName.getNSString();
1091 KWQ_BLOCK_EXCEPTIONS;
1092 return QString::fromNSString([_bridge MIMETypeForPath:ns]);
1093 KWQ_UNBLOCK_EXCEPTIONS;
1098 NSView *KWQKHTMLPart::nextKeyViewInFrame(NodeImpl *node, KWQSelectionDirection direction)
1100 DocumentImpl *doc = xmlDocImpl();
1105 node = direction == KWQSelectingNext
1106 ? doc->nextFocusNode(node) : doc->previousFocusNode(node);
1110 RenderWidget *renderWidget = dynamic_cast<RenderWidget *>(node->renderer());
1112 QWidget *widget = renderWidget->widget();
1113 KHTMLView *childFrameWidget = dynamic_cast<KHTMLView *>(widget);
1115 if (childFrameWidget) {
1116 view = KWQ(childFrameWidget->part())->nextKeyViewInFrame(0, direction);
1117 } else if (widget) {
1118 view = widget->getView();
1125 doc->setFocusNode(node);
1127 view()->ensureRectVisibleCentered(node->getRect());
1129 [_bridge makeFirstResponder:[_bridge documentView]];
1130 return [_bridge documentView];
1135 NSView *KWQKHTMLPart::nextKeyViewInFrameHierarchy(NodeImpl *node, KWQSelectionDirection direction)
1137 NSView *next = nextKeyViewInFrame(node, direction);
1142 // remove focus from currently focused node
1143 DocumentImpl *doc = xmlDocImpl();
1145 doc->setFocusNode(0);
1148 KWQKHTMLPart *parent = KWQ(parentPart());
1150 next = parent->nextKeyView(parent->childFrame(this)->m_frame->element(), direction);
1159 NSView *KWQKHTMLPart::nextKeyView(NodeImpl *node, KWQSelectionDirection direction)
1161 KWQ_BLOCK_EXCEPTIONS;
1163 NSView * next = nextKeyViewInFrameHierarchy(node, direction);
1168 // Look at views from the top level part up, looking for a next key view that we can use.
1170 next = direction == KWQSelectingNext
1171 ? [_bridge nextKeyViewOutsideWebFrameViews]
1172 : [_bridge previousKeyViewOutsideWebFrameViews];
1178 KWQ_UNBLOCK_EXCEPTIONS;
1180 // If all else fails, make a loop by starting from 0.
1181 return nextKeyViewInFrameHierarchy(0, direction);
1184 NSView *KWQKHTMLPart::nextKeyViewForWidget(QWidget *startingWidget, KWQSelectionDirection direction)
1186 // Use the event filter object to figure out which RenderWidget owns this QWidget and get to the DOM.
1187 // Then get the next key view in the order determined by the DOM.
1188 NodeImpl *node = nodeForWidget(startingWidget);
1190 return partForNode(node)->nextKeyView(node, direction);
1193 bool KWQKHTMLPart::currentEventIsMouseDownInWidget(QWidget *candidate)
1195 KWQ_BLOCK_EXCEPTIONS;
1196 switch ([[NSApp currentEvent] type]) {
1197 case NSLeftMouseDown:
1198 case NSRightMouseDown:
1199 case NSOtherMouseDown:
1204 KWQ_UNBLOCK_EXCEPTIONS;
1206 NodeImpl *node = nodeForWidget(candidate);
1208 return partForNode(node)->nodeUnderMouse() == node;
1211 bool KWQKHTMLPart::currentEventIsKeyboardOptionTab()
1213 KWQ_BLOCK_EXCEPTIONS;
1214 NSEvent *evt = [NSApp currentEvent];
1215 if ([evt type] != NSKeyDown) {
1219 if (([evt modifierFlags] & NSAlternateKeyMask) == 0) {
1223 NSString *chars = [evt charactersIgnoringModifiers];
1224 if ([chars length] != 1)
1227 const unichar tabKey = 0x0009;
1228 const unichar shiftTabKey = 0x0019;
1229 unichar c = [chars characterAtIndex:0];
1230 if (c != tabKey && c != shiftTabKey)
1233 KWQ_UNBLOCK_EXCEPTIONS;
1237 bool KWQKHTMLPart::handleKeyboardOptionTabInView(NSView *view)
1239 if (KWQKHTMLPart::currentEventIsKeyboardOptionTab()) {
1240 if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
1241 [[view window] selectKeyViewPrecedingView:view];
1243 [[view window] selectKeyViewFollowingView:view];
1251 bool KWQKHTMLPart::tabsToLinks() const
1253 if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessTabsToLinks)
1254 return !KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1256 return KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1259 bool KWQKHTMLPart::tabsToAllControls() const
1261 if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessFull)
1262 return !KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1264 return KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1267 KJS::Bindings::RootObject *KWQKHTMLPart::bindingRootObject()
1269 if (!_bindingRoot) {
1270 _bindingRoot = new KJS::Bindings::RootObject(0); // The root gets deleted by JavaScriptCore.
1271 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1272 _bindingRoot->setRootObjectImp (win);
1273 _bindingRoot->setInterpreter (KJSProxy::proxy(this)->interpreter());
1274 addPluginRootObject (_bindingRoot);
1276 return _bindingRoot;
1279 WebScriptObject *KWQKHTMLPart::windowScriptObject()
1281 if (!_windowScriptObject) {
1282 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1283 _windowScriptObject = KWQRetainNSRelease([[WebScriptObject alloc] _initWithObjectImp:win root:bindingRootObject()]);
1286 return _windowScriptObject;
1289 NPObject *KWQKHTMLPart::windowScriptNPObject()
1291 if (!_windowScriptNPObject) {
1292 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1293 _windowScriptNPObject = _NPN_CreateScriptObject (0, win, bindingRootObject());
1296 return _windowScriptNPObject;
1299 void KWQKHTMLPart::partClearedInBegin()
1301 [_bridge windowObjectCleared];
1304 QMap<int, ScheduledAction*> *KWQKHTMLPart::pauseActions(const void *key)
1306 if (d->m_doc && d->m_jscript) {
1307 Window *w = Window::retrieveWindow(this);
1308 if (w && w->hasTimeouts()) {
1309 return w->pauseTimeouts(key);
1315 void KWQKHTMLPart::resumeActions(QMap<int, ScheduledAction*> *actions, const void *key)
1317 if (d->m_doc && d->m_jscript && d->m_bJScriptEnabled) {
1318 Window *w = Window::retrieveWindow(this);
1320 w->resumeTimeouts(actions, key);
1325 bool KWQKHTMLPart::canCachePage()
1327 // Only save page state if:
1328 // 1. We're not a frame or frameset.
1329 // 2. The page has no unload handler.
1330 // 3. The page has no password fields.
1331 // 4. The URL for the page is not https.
1332 // 5. The page has no applets.
1333 if (d->m_frames.count() ||
1335 m_url.protocol().startsWith("https") ||
1336 (d->m_doc && (htmlDocument().applets().length() != 0 ||
1337 d->m_doc->hasWindowEventListener(EventImpl::UNLOAD_EVENT) ||
1338 d->m_doc->hasPasswordField()))) {
1344 void KWQKHTMLPart::saveWindowProperties(SavedProperties *windowProperties)
1346 Window *window = Window::retrieveWindow(this);
1348 window->saveProperties(*windowProperties);
1351 void KWQKHTMLPart::saveLocationProperties(SavedProperties *locationProperties)
1353 Window *window = Window::retrieveWindow(this);
1355 Interpreter::lock();
1356 Location *location = window->location();
1357 Interpreter::unlock();
1358 location->saveProperties(*locationProperties);
1362 void KWQKHTMLPart::restoreWindowProperties(SavedProperties *windowProperties)
1364 Window *window = Window::retrieveWindow(this);
1366 window->restoreProperties(*windowProperties);
1369 void KWQKHTMLPart::restoreLocationProperties(SavedProperties *locationProperties)
1371 Window *window = Window::retrieveWindow(this);
1373 Interpreter::lock();
1374 Location *location = window->location();
1375 Interpreter::unlock();
1376 location->restoreProperties(*locationProperties);
1380 void KWQKHTMLPart::saveInterpreterBuiltins(SavedBuiltins &interpreterBuiltins)
1382 if (jScript() && jScript()->interpreter()) {
1383 jScript()->interpreter()->saveBuiltins(interpreterBuiltins);
1387 void KWQKHTMLPart::restoreInterpreterBuiltins(const SavedBuiltins &interpreterBuiltins)
1389 if (jScript() && jScript()->interpreter()) {
1390 jScript()->interpreter()->restoreBuiltins(interpreterBuiltins);
1394 void KWQKHTMLPart::openURLFromPageCache(KWQPageState *state)
1396 // It's safe to assume none of the KWQPageState methods will raise
1397 // exceptions, since KWQPageState is implemented by WebCore and
1400 DocumentImpl *doc = [state document];
1401 KURL *url = [state URL];
1402 SavedProperties *windowProperties = [state windowProperties];
1403 SavedProperties *locationProperties = [state locationProperties];
1404 SavedBuiltins *interpreterBuiltins = [state interpreterBuiltins];
1405 QMap<int, ScheduledAction*> *actions = [state pausedActions];
1407 cancelRedirection();
1409 // We still have to close the previous part page.
1410 if (!d->m_restored){
1414 d->m_bComplete = false;
1416 // Don't re-emit the load event.
1417 d->m_bLoadEventEmitted = true;
1419 // delete old status bar msg's from kjs (if it _was_ activated on last URL)
1420 if( d->m_bJScriptEnabled )
1422 d->m_kjsStatusBarText = QString::null;
1423 d->m_kjsDefaultStatusBarText = QString::null;
1430 // initializing m_url to the new url breaks relative links when opening such a link after this call and _before_ begin() is called (when the first
1431 // data arrives) (Simon)
1432 if(m_url.protocol().startsWith( "http" ) && !m_url.host().isEmpty() && m_url.path().isEmpty()) {
1434 emit d->m_extension->setLocationBarURL( m_url.prettyURL() );
1437 // copy to m_workingURL after fixing m_url above
1438 d->m_workingURL = m_url;
1442 // -----------begin-----------
1445 doc->setInPageCache(NO);
1447 d->m_bCleared = false;
1449 d->m_bComplete = false;
1450 d->m_bLoadEventEmitted = false;
1451 d->m_referrer = m_url.url();
1453 setView(doc->view());
1458 Decoder *decoder = doc->decoder();
1463 d->m_decoder->deref();
1465 d->m_decoder = decoder;
1467 updatePolicyBaseURL();
1469 restoreWindowProperties (windowProperties);
1470 restoreLocationProperties (locationProperties);
1471 restoreInterpreterBuiltins (*interpreterBuiltins);
1474 resumeActions (actions, state);
1479 KWQKHTMLPart *KWQKHTMLPart::partForWidget(const QWidget *widget)
1481 ASSERT_ARG(widget, widget);
1483 NodeImpl *node = nodeForWidget(widget);
1485 return partForNode(node);
1488 // Assume all widgets are either form controls, or KHTMLViews.
1489 const KHTMLView *view = dynamic_cast<const KHTMLView *>(widget);
1491 return KWQ(view->part());
1494 WebCoreBridge *KWQKHTMLPart::bridgeForWidget(const QWidget *widget)
1496 ASSERT_ARG(widget, widget);
1498 KWQKHTMLPart *part = partForWidget(widget);
1500 return part->bridge();
1503 KWQKHTMLPart *KWQKHTMLPart::partForNode(NodeImpl *node)
1505 ASSERT_ARG(node, node);
1506 return KWQ(node->getDocument()->part());
1509 NSView *KWQKHTMLPart::documentViewForNode(DOM::NodeImpl *node)
1511 WebCoreBridge *bridge = partForNode(node)->bridge();
1512 return [bridge documentView];
1515 NodeImpl *KWQKHTMLPart::nodeForWidget(const QWidget *widget)
1517 ASSERT_ARG(widget, widget);
1518 const QObject *o = widget->eventFilterObject();
1519 return o ? static_cast<const RenderWidget *>(o)->element() : 0;
1522 void KWQKHTMLPart::setDocumentFocus(QWidget *widget)
1524 NodeImpl *node = nodeForWidget(widget);
1526 node->getDocument()->setFocusNode(node);
1528 ERROR("unable to clear focus because widget had no corresponding node");
1532 void KWQKHTMLPart::clearDocumentFocus(QWidget *widget)
1534 NodeImpl *node = nodeForWidget(widget);
1536 node->getDocument()->setFocusNode(0);
1538 ERROR("unable to clear focus because widget had no corresponding node");
1542 void KWQKHTMLPart::saveDocumentState()
1544 // Do not save doc state if the page has a password field and a form that would be submitted
1546 if (!(d->m_doc && d->m_doc->hasPasswordField() && d->m_doc->hasSecureForm())) {
1547 KWQ_BLOCK_EXCEPTIONS;
1548 [_bridge saveDocumentState];
1549 KWQ_UNBLOCK_EXCEPTIONS;
1553 void KWQKHTMLPart::restoreDocumentState()
1555 KWQ_BLOCK_EXCEPTIONS;
1556 [_bridge restoreDocumentState];
1557 KWQ_UNBLOCK_EXCEPTIONS;
1560 QPtrList<KWQKHTMLPart> &KWQKHTMLPart::mutableInstances()
1562 static QPtrList<KWQKHTMLPart> instancesList;
1563 return instancesList;
1566 void KWQKHTMLPart::updatePolicyBaseURL()
1568 // FIXME: docImpl() returns null for everything other than HTML documents; is this causing problems? -dwh
1569 if (parentPart() && parentPart()->docImpl()) {
1570 setPolicyBaseURL(parentPart()->docImpl()->policyBaseURL());
1572 setPolicyBaseURL(m_url.url());
1576 void KWQKHTMLPart::setPolicyBaseURL(const DOMString &s)
1578 // FIXME: XML documents will cause this to return null. docImpl() is
1579 // an HTMLdocument only. -dwh
1581 docImpl()->setPolicyBaseURL(s);
1582 ConstFrameIt end = d->m_frames.end();
1583 for (ConstFrameIt it = d->m_frames.begin(); it != end; ++it) {
1584 ReadOnlyPart *subpart = (*it).m_part;
1585 static_cast<KWQKHTMLPart *>(subpart)->setPolicyBaseURL(s);
1589 QString KWQKHTMLPart::requestedURLString() const
1591 KWQ_BLOCK_EXCEPTIONS;
1592 return QString::fromNSString([_bridge requestedURLString]);
1593 KWQ_UNBLOCK_EXCEPTIONS;
1598 QString KWQKHTMLPart::incomingReferrer() const
1600 KWQ_BLOCK_EXCEPTIONS;
1601 return QString::fromNSString([_bridge incomingReferrer]);
1602 KWQ_UNBLOCK_EXCEPTIONS;
1607 void KWQKHTMLPart::forceLayout()
1609 KHTMLView *v = d->m_view;
1612 // We cannot unschedule a pending relayout, since the force can be called with
1613 // a tiny rectangle from a drawRect update. By unscheduling we in effect
1614 // "validate" and stop the necessary full repaint from occurring. Basically any basic
1615 // append/remove DHTML is broken by this call. For now, I have removed the optimization
1616 // until we have a better invalidation stategy. -dwh
1617 //v->unscheduleRelayout();
1621 void KWQKHTMLPart::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth)
1623 // Dumping externalRepresentation(_part->renderer()).ascii() is a good trick to see
1624 // the state of things before and after the layout
1625 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1627 // This magic is basically copied from khtmlview::print
1628 int pageW = (int)ceil(minPageWidth);
1629 root->setWidth(pageW);
1630 root->setNeedsLayoutAndMinMaxRecalc();
1633 // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the
1634 // maximum page width, we will lay out to the maximum page width and clip extra content.
1635 // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping
1636 // implementation should not do this!
1637 int rightmostPos = root->rightmostPosition();
1638 if (rightmostPos > minPageWidth) {
1639 pageW = kMin(rightmostPos, (int)ceil(maxPageWidth));
1640 root->setWidth(pageW);
1641 root->setNeedsLayoutAndMinMaxRecalc();
1647 void KWQKHTMLPart::sendResizeEvent()
1649 KHTMLView *v = d->m_view;
1651 // Sending an event can result in the destruction of the view and part.
1652 // We ref so that happens after we return from the KHTMLView function.
1660 void KWQKHTMLPart::sendScrollEvent()
1662 KHTMLView *v = d->m_view;
1664 DocumentImpl *doc = xmlDocImpl();
1667 doc->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false);
1671 void KWQKHTMLPart::runJavaScriptAlert(const QString &message)
1673 QString text = message;
1674 text.replace('\\', backslashAsCurrencySymbol());
1675 KWQ_BLOCK_EXCEPTIONS;
1676 [_bridge runJavaScriptAlertPanelWithMessage:text.getNSString()];
1677 KWQ_UNBLOCK_EXCEPTIONS;
1680 bool KWQKHTMLPart::runJavaScriptConfirm(const QString &message)
1682 QString text = message;
1683 text.replace('\\', backslashAsCurrencySymbol());
1685 KWQ_BLOCK_EXCEPTIONS;
1686 return [_bridge runJavaScriptConfirmPanelWithMessage:text.getNSString()];
1687 KWQ_UNBLOCK_EXCEPTIONS;
1692 bool KWQKHTMLPart::runJavaScriptPrompt(const QString &prompt, const QString &defaultValue, QString &result)
1694 QString promptText = prompt;
1695 promptText.replace('\\', backslashAsCurrencySymbol());
1696 QString defaultValueText = defaultValue;
1697 defaultValueText.replace('\\', backslashAsCurrencySymbol());
1699 KWQ_BLOCK_EXCEPTIONS;
1700 NSString *returnedText = nil;
1702 bool ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt.getNSString()
1703 defaultText:defaultValue.getNSString() returningText:&returnedText];
1706 result = QString::fromNSString(returnedText);
1707 result.replace(backslashAsCurrencySymbol(), '\\');
1711 KWQ_UNBLOCK_EXCEPTIONS;
1716 bool KWQKHTMLPart::locationbarVisible()
1718 return [_bridge areToolbarsVisible];
1721 bool KWQKHTMLPart::menubarVisible()
1723 // The menubar is always on in Mac OS X UI
1727 bool KWQKHTMLPart::personalbarVisible()
1729 return [_bridge areToolbarsVisible];
1732 bool KWQKHTMLPart::scrollbarsVisible()
1737 if (view()->hScrollBarMode() == QScrollView::AlwaysOff || view()->vScrollBarMode() == QScrollView::AlwaysOff)
1743 bool KWQKHTMLPart::statusbarVisible()
1745 return [_bridge isStatusBarVisible];
1748 bool KWQKHTMLPart::toolbarVisible()
1750 return [_bridge areToolbarsVisible];
1753 void KWQKHTMLPart::addMessageToConsole(const QString &message, unsigned lineNumber, const QString &sourceURL)
1755 NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
1756 message.getNSString(), @"message",
1757 [NSNumber numberWithInt: lineNumber], @"lineNumber",
1758 sourceURL.getNSString(), @"sourceURL",
1760 [_bridge addMessageToConsole:dictionary];
1763 void KWQKHTMLPart::createEmptyDocument()
1765 // Although it's not completely clear from the name of this function,
1766 // it does nothing if we already have a document, and just creates an
1767 // empty one if we have no document at all.
1769 KWQ_BLOCK_EXCEPTIONS;
1770 [_bridge loadEmptyDocumentSynchronously];
1771 KWQ_UNBLOCK_EXCEPTIONS;
1773 if (parentPart() && (parentPart()->childFrame(this)->m_type == ChildFrame::IFrame ||
1774 parentPart()->childFrame(this)->m_type == ChildFrame::Object)) {
1775 d->m_doc->setBaseURL(parentPart()->d->m_doc->baseURL());
1780 void KWQKHTMLPart::addMetaData(const QString &key, const QString &value)
1782 d->m_job->addMetaData(key, value);
1785 bool KWQKHTMLPart::keyEvent(NSEvent *event)
1787 KWQ_BLOCK_EXCEPTIONS;
1789 ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
1791 // Check for cases where we are too early for events -- possible unmatched key up
1792 // from pressing return in the location bar.
1793 DocumentImpl *doc = xmlDocImpl();
1797 NodeImpl *node = doc->focusNode();
1798 if (!node && docImpl()) {
1799 node = docImpl()->body();
1805 NSEvent *oldCurrentEvent = _currentEvent;
1806 _currentEvent = KWQRetain(event);
1808 QKeyEvent qEvent(event);
1809 bool result = !node->dispatchKeyEvent(&qEvent);
1811 // We want to send both a down and a press for the initial key event.
1812 // To get KHTML to do this, we send a second KeyPress QKeyEvent with "is repeat" set to true,
1813 // which causes it to send a press to the DOM.
1814 // That's not a great hack; it would be good to do this in a better way.
1815 if ([event type] == NSKeyDown && ![event isARepeat]) {
1816 QKeyEvent repeatEvent(event, true);
1817 if (!node->dispatchKeyEvent(&repeatEvent)) {
1822 ASSERT(_currentEvent == event);
1824 _currentEvent = oldCurrentEvent;
1828 KWQ_UNBLOCK_EXCEPTIONS;
1833 // This does the same kind of work that KHTMLPart::openURL does, except it relies on the fact
1834 // that a higher level already checked that the URLs match and the scrolling is the right thing to do.
1835 void KWQKHTMLPart::scrollToAnchor(const KURL &URL)
1837 cancelRedirection();
1844 // It's important to model this as a load that starts and immediately finishes.
1845 // Otherwise, the parent frame may think we never finished loading.
1846 d->m_bComplete = false;
1850 bool KWQKHTMLPart::closeURL()
1852 saveDocumentState();
1853 return KHTMLPart::closeURL();
1856 void KWQKHTMLPart::khtmlMousePressEvent(MousePressEvent *event)
1858 bool singleClick = [_currentEvent clickCount] <= 1;
1860 // If we got the event back, that must mean it wasn't prevented,
1861 // so it's allowed to start a drag or selection.
1862 _mouseDownMayStartSelect = true;
1863 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
1864 _mouseDownMayStartDrag = singleClick;
1866 if (!passWidgetMouseDownEventToWidget(event)) {
1867 // We don't do this at the start of mouse down handling (before calling into WebCore),
1868 // because we don't want to do it until we know we didn't hit a widget.
1869 NSView *view = d->m_view->getDocumentView();
1872 KWQ_BLOCK_EXCEPTIONS;
1873 if ([_bridge firstResponder] != view) {
1874 [_bridge makeFirstResponder:view];
1876 KWQ_UNBLOCK_EXCEPTIONS;
1879 KHTMLPart::khtmlMousePressEvent(event);
1883 void KWQKHTMLPart::khtmlMouseDoubleClickEvent(MouseDoubleClickEvent *event)
1885 if (!passWidgetMouseDownEventToWidget(event)) {
1886 KHTMLPart::khtmlMouseDoubleClickEvent(event);
1890 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(khtml::MouseEvent *event)
1892 // Figure out which view to send the event to.
1893 RenderObject *target = event->innerNode().handle() ? event->innerNode().handle()->renderer() : 0;
1897 QWidget* widget = RenderLayer::gScrollBar;
1899 if (!target->isWidget())
1901 widget = static_cast<RenderWidget *>(target)->widget();
1904 // Doubleclick events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
1905 // just pass _currentEvent down to the widget, we don't want to call it for events that
1906 // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
1907 // part of the pressed/released handling.
1908 if (!MouseDoubleClickEvent::test(event))
1909 return passWidgetMouseDownEventToWidget(widget);
1914 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(RenderWidget *renderWidget)
1916 return passWidgetMouseDownEventToWidget(renderWidget->widget());
1919 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(QWidget* widget)
1921 // FIXME: this method always returns true
1924 ERROR("hit a RenderWidget without a corresponding QWidget, means a frame is half-constructed");
1928 KWQ_BLOCK_EXCEPTIONS;
1930 NSView *nodeView = widget->getView();
1932 ASSERT([nodeView superview]);
1933 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]];
1935 ERROR("KHTML says we hit a RenderWidget, but AppKit doesn't agree we hit the corresponding NSView");
1939 if ([_bridge firstResponder] == view) {
1940 // In the case where we just became first responder, we should send the mouseDown:
1941 // to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
1942 // If we don't do this, we see a flash of selected text when clicking in a text field.
1943 if (_firstResponderAtMouseDownTime != view && [view isKindOfClass:[NSTextView class]]) {
1944 NSView *superview = view;
1945 while (superview != nodeView) {
1946 superview = [superview superview];
1948 if ([superview isKindOfClass:[NSControl class]]) {
1949 NSControl *control = superview;
1950 if ([control currentEditor] == view) {
1958 // Normally [NSWindow sendEvent:] handles setting the first responder.
1959 // But in our case, the event was sent to the view representing the entire web page.
1960 if ([_currentEvent clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
1961 [_bridge makeFirstResponder:view];
1965 // We need to "defer loading" and defer timers while we are tracking the mouse.
1966 // That's because we don't want the new page to load while the user is holding the mouse down.
1968 BOOL wasDeferringLoading = [_bridge defersLoading];
1969 if (!wasDeferringLoading) {
1970 [_bridge setDefersLoading:YES];
1972 BOOL wasDeferringTimers = QObject::defersTimers();
1973 if (!wasDeferringTimers) {
1974 QObject::setDefersTimers(true);
1977 ASSERT(!_sendingEventToSubview);
1978 _sendingEventToSubview = true;
1979 [view mouseDown:_currentEvent];
1980 _sendingEventToSubview = false;
1982 if (!wasDeferringTimers) {
1983 QObject::setDefersTimers(false);
1985 if (!wasDeferringLoading) {
1986 [_bridge setDefersLoading:NO];
1989 // Remember which view we sent the event to, so we can direct the release event properly.
1990 _mouseDownView = view;
1991 _mouseDownWasInSubframe = false;
1993 KWQ_UNBLOCK_EXCEPTIONS;
1998 bool KWQKHTMLPart::lastEventIsMouseUp()
2000 // Many AK widgets run their own event loops and consume events while the mouse is down.
2001 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
2002 // the khtml state with this mouseUp, which khtml never saw. This method lets us detect
2005 KWQ_BLOCK_EXCEPTIONS;
2006 NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
2007 if (_currentEvent != currentEventAfterHandlingMouseDown) {
2008 if ([currentEventAfterHandlingMouseDown type] == NSLeftMouseUp) {
2012 KWQ_UNBLOCK_EXCEPTIONS;
2017 // Note that this does the same kind of check as [target isDescendantOf:superview].
2018 // There are two differences: This is a lot slower because it has to walk the whole
2019 // tree, and this works in cases where the target has already been deallocated.
2020 static bool findViewInSubviews(NSView *superview, NSView *target)
2022 KWQ_BLOCK_EXCEPTIONS;
2023 NSEnumerator *e = [[superview subviews] objectEnumerator];
2025 while ((subview = [e nextObject])) {
2026 if (subview == target || findViewInSubviews(subview, target)) {
2030 KWQ_UNBLOCK_EXCEPTIONS;
2035 NSView *KWQKHTMLPart::mouseDownViewIfStillGood()
2037 // Since we have no way of tracking the lifetime of _mouseDownView, we have to assume that
2038 // it could be deallocated already. We search for it in our subview tree; if we don't find
2039 // it, we set it to nil.
2040 NSView *mouseDownView = _mouseDownView;
2041 if (!mouseDownView) {
2044 KHTMLView *topKHTMLView = d->m_view;
2045 NSView *topView = topKHTMLView ? topKHTMLView->getView() : nil;
2046 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
2047 _mouseDownView = nil;
2050 return mouseDownView;
2053 // The link drag hysteresis is much larger than the others because there
2054 // needs to be enough space to cancel the link press without starting a link drag,
2055 // and because dragging links is rare.
2056 #define LinkDragHysteresis 40.0
2057 #define ImageDragHysteresis 5.0
2058 #define TextDragHysteresis 3.0
2059 #define GeneralDragHysteresis 3.0
2061 #define TextDragDelay 0.15
2063 bool KWQKHTMLPart::dragHysteresisExceeded(float dragLocationX, float dragLocationY) const
2066 d->m_view->viewportToContents((int)dragLocationX, (int)dragLocationY, dragX, dragY);
2067 float deltaX = QABS(dragX - _mouseDownX);
2068 float deltaY = QABS(dragY - _mouseDownY);
2070 float threshold = GeneralDragHysteresis;
2071 if (_dragSrcIsImage) {
2072 threshold = ImageDragHysteresis;
2073 } else if (_dragSrcIsLink) {
2074 threshold = LinkDragHysteresis;
2075 } else if (_dragSrcInSelection) {
2076 threshold = TextDragHysteresis;
2078 return deltaX >= threshold || deltaY >= threshold;
2081 // returns if we should continue "default processing", i.e., whether eventhandler canceled
2082 bool KWQKHTMLPart::dispatchDragSrcEvent(int eventId, const QPoint &loc) const
2084 bool noDefaultProc = d->m_view->dispatchDragEvent(eventId, _dragSrc.handle(), loc, _dragClipboard);
2085 return !noDefaultProc;
2088 bool KWQKHTMLPart::eventMayStartDrag(NSEvent *event)
2090 // This is a pre-flight check of whether the event might lead to a drag being started. Be careful
2091 // that its logic needs to stay in sync with khtmlMouseMoveEvent() and the way we set
2092 // _mouseDownMayStartDrag in khtmlMousePressEvent
2094 if ([event type] != NSLeftMouseDown || [event clickCount] != 1) {
2098 BOOL DHTMLFlag, UAFlag;
2099 [_bridge allowDHTMLDrag:&DHTMLFlag UADrag:&UAFlag];
2100 if (!DHTMLFlag && !UAFlag) {
2104 NSPoint loc = [event locationInWindow];
2105 int mouseDownX, mouseDownY;
2106 d->m_view->viewportToContents((int)loc.x, (int)loc.y, mouseDownX, mouseDownY);
2107 RenderObject::NodeInfo nodeInfo(true, false);
2108 renderer()->layer()->nodeAtPoint(nodeInfo, mouseDownX, mouseDownY);
2110 Node possibleSrc = nodeInfo.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, mouseDownX, mouseDownY, srcIsDHTML);
2111 return !possibleSrc.isNull();
2114 void KWQKHTMLPart::khtmlMouseMoveEvent(MouseMoveEvent *event)
2116 KWQ_BLOCK_EXCEPTIONS;
2118 if ([_currentEvent type] == NSLeftMouseDragged) {
2119 NSView *view = mouseDownViewIfStillGood();
2122 _sendingEventToSubview = true;
2123 [view mouseDragged:_currentEvent];
2124 _sendingEventToSubview = false;
2128 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
2130 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2131 BOOL tempFlag1, tempFlag2;
2132 [_bridge allowDHTMLDrag:&tempFlag1 UADrag:&tempFlag2];
2133 _dragSrcMayBeDHTML = tempFlag1;
2134 _dragSrcMayBeUA = tempFlag2;
2135 if (!_dragSrcMayBeDHTML && !_dragSrcMayBeUA) {
2136 _mouseDownMayStartDrag = false; // no element is draggable
2140 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2141 // try to find an element that wants to be dragged
2142 RenderObject::NodeInfo nodeInfo(true, false);
2143 renderer()->layer()->nodeAtPoint(nodeInfo, _mouseDownX, _mouseDownY);
2144 _dragSrc = nodeInfo.innerNode()->renderer()->draggableNode(_dragSrcMayBeDHTML, _dragSrcMayBeUA, _mouseDownX, _mouseDownY, _dragSrcIsDHTML);
2145 if (_dragSrc.isNull()) {
2146 _mouseDownMayStartDrag = false; // no element is draggable
2148 // remember some facts about this source, while we have a NodeInfo handy
2149 NodeImpl *node = nodeInfo.URLElement();
2150 _dragSrcIsLink = node ? node->hasAnchor() : false;
2152 node = nodeInfo.innerNonSharedNode();
2153 _dragSrcIsImage = (node && node->renderer() && node->renderer()->isImage());
2155 _dragSrcInSelection = isPointInsideSelection(_mouseDownX, _mouseDownY);
2159 // For drags starting in the selection, the user must wait between the mousedown and mousedrag,
2160 // or else we bail on the dragging stuff and allow selection to occur
2161 if (_mouseDownMayStartDrag && _dragSrcInSelection && [_currentEvent timestamp] - _mouseDownTimestamp < TextDragDelay) {
2162 _mouseDownMayStartDrag = false;
2163 // ...but if this was the first click in the window, we don't even want to start selection
2164 if (_activationEventNumber == [_currentEvent eventNumber]) {
2165 _mouseDownMayStartSelect = false;
2169 if (_mouseDownMayStartDrag) {
2170 // We are starting a text/image/url drag, so the cursor should be an arrow
2171 d->m_view->resetCursor();
2173 NSPoint dragLocation = [_currentEvent locationInWindow];
2174 if (dragHysteresisExceeded(dragLocation.x, dragLocation.y)) {
2176 // Once we're past the hysteresis point, we don't want to treat this gesture as a click
2177 d->m_view->invalidateClick();
2179 NSImage *dragImage = nil; // we use these values if WC is out of the loop
2180 NSPoint dragLoc = NSZeroPoint;
2181 NSDragOperation srcOp = NSDragOperationNone;
2182 BOOL wcWrotePasteboard = NO;
2183 if (_dragSrcMayBeDHTML) {
2184 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
2185 // Must be done before ondragstart adds types and data to the pboard,
2186 // also done for security, as it erases data from the last drag
2187 [pasteboard declareTypes:[NSArray array] owner:nil];
2189 freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just
2190 // to make sure it gets numbified
2191 _dragClipboard = new KWQClipboard(true, pasteboard, KWQClipboard::Writable, this);
2192 _dragClipboard->ref();
2194 // If this is drag of an element, get set up to generate a default image. Otherwise
2195 // WebKit will generate the default, the element doesn't override.
2196 if (_dragSrcIsDHTML) {
2198 _dragSrc.handle()->renderer()->absolutePosition(srcX, srcY);
2199 _dragClipboard->setDragImageElement(_dragSrc, QPoint(_mouseDownX - srcX, _mouseDownY - srcY));
2202 _mouseDownMayStartDrag = dispatchDragSrcEvent(EventImpl::DRAGSTART_EVENT, QPoint(_mouseDownWinX, _mouseDownWinY));
2203 // Invalidate clipboard here against anymore pasteboard writing for security. The drag
2204 // image can still be changed as we drag, but not the pasteboard data.
2205 _dragClipboard->setAccessPolicy(KWQClipboard::ImageWritable);
2207 if (_mouseDownMayStartDrag) {
2208 // gather values from DHTML element, if it set any
2209 _dragClipboard->sourceOperation(&srcOp);
2211 NSArray *types = [pasteboard types];
2212 wcWrotePasteboard = types && [types count] > 0;
2214 if (_dragSrcMayBeDHTML) {
2215 dragImage = _dragClipboard->dragNSImage(&dragLoc);
2218 // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with
2219 // dragImage! Because of that dumb reentrancy, we may think we've not started the
2220 // drag when that happens. So we have to assume it's started before we kick it off.
2221 _dragClipboard->setDragHasStarted();
2225 if (_mouseDownMayStartDrag) {
2226 BOOL startedDrag = [_bridge startDraggingImage:dragImage at:dragLoc operation:srcOp event:_currentEvent sourceIsDHTML:_dragSrcIsDHTML DHTMLWroteData:wcWrotePasteboard];
2227 if (!startedDrag && _dragSrcMayBeDHTML) {
2228 // WebKit canned the drag at the last minute - we owe _dragSrc a DRAGEND event
2229 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, QPoint(dragLocation));
2230 _mouseDownMayStartDrag = false;
2234 if (!_mouseDownMayStartDrag) {
2235 // something failed to start the drag, cleanup
2241 // No more default handling (like selection), whether we're past the hysteresis bounds or not
2244 if (!_mouseDownMayStartSelect) {
2248 // Don't allow dragging or click handling after we've started selecting.
2249 _mouseDownMayStartDrag = false;
2250 d->m_view->invalidateClick();
2252 // We use khtml's selection but our own autoscrolling.
2253 [_bridge handleAutoscrollForMouseDragged:_currentEvent];
2255 // If we allowed the other side of the bridge to handle a drag
2256 // last time, then m_bMousePressed might still be set. So we
2257 // clear it now to make sure the next move after a drag
2258 // doesn't look like a drag.
2259 d->m_bMousePressed = false;
2262 KHTMLPart::khtmlMouseMoveEvent(event);
2264 KWQ_UNBLOCK_EXCEPTIONS;
2267 void KWQKHTMLPart::dragSourceMovedTo(const QPoint &loc)
2269 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2270 // for now we don't care if event handler cancels default behavior, since there is none
2271 dispatchDragSrcEvent(EventImpl::DRAG_EVENT, loc);
2275 void KWQKHTMLPart::dragSourceEndedAt(const QPoint &loc, NSDragOperation operation)
2277 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2278 _dragClipboard->setDestinationOperation(operation);
2279 // for now we don't care if event handler cancels default behavior, since there is none
2280 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, loc);
2286 // Returns whether caller should continue with "the default processing", which is the same as
2287 // the event handler NOT setting the return value to false
2288 bool KWQKHTMLPart::dispatchCPPEvent(int eventId, KWQClipboard::AccessPolicy policy)
2290 NodeImpl *target = d->m_selection.start().element();
2291 if (!target && docImpl()) {
2292 target = docImpl()->body();
2298 KWQClipboard *clipboard = new KWQClipboard(false, [NSPasteboard generalPasteboard], (KWQClipboard::AccessPolicy)policy);
2301 int exceptioncode = 0;
2302 EventImpl *evt = new ClipboardEventImpl(static_cast<EventImpl::EventId>(eventId), true, true, clipboard);
2304 target->dispatchEvent(evt, exceptioncode, true);
2305 bool noDefaultProcessing = evt->defaultPrevented();
2308 // invalidate clipboard here for security
2309 clipboard->setAccessPolicy(KWQClipboard::Numb);
2312 return !noDefaultProcessing;
2315 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
2316 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
2317 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
2318 // normally selectable to implement copy/paste (like divs, or a document body).
2320 bool KWQKHTMLPart::mayCut()
2322 return !dispatchCPPEvent(EventImpl::BEFORECUT_EVENT, KWQClipboard::Numb);
2325 bool KWQKHTMLPart::mayCopy()
2327 return !dispatchCPPEvent(EventImpl::BEFORECOPY_EVENT, KWQClipboard::Numb);
2330 bool KWQKHTMLPart::mayPaste()
2332 return !dispatchCPPEvent(EventImpl::BEFOREPASTE_EVENT, KWQClipboard::Numb);
2335 bool KWQKHTMLPart::tryCut()
2337 // Must be done before oncut adds types and data to the pboard,
2338 // also done for security, as it erases data from the last copy/paste.
2339 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2341 return !dispatchCPPEvent(EventImpl::CUT_EVENT, KWQClipboard::Writable);
2344 bool KWQKHTMLPart::tryCopy()
2346 // Must be done before oncopy adds types and data to the pboard,
2347 // also done for security, as it erases data from the last copy/paste.
2348 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2350 return !dispatchCPPEvent(EventImpl::COPY_EVENT, KWQClipboard::Writable);
2353 bool KWQKHTMLPart::tryPaste()
2355 return !dispatchCPPEvent(EventImpl::PASTE_EVENT, KWQClipboard::Readable);
2358 void KWQKHTMLPart::khtmlMouseReleaseEvent(MouseReleaseEvent *event)
2360 NSView *view = mouseDownViewIfStillGood();
2362 // If this was the first click in the window, we don't even want to clear the selection.
2363 // This case occurs when the user clicks on a draggable element, since we have to process
2364 // the mouse down and drag events to see if we might start a drag. For other first clicks
2365 // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets
2366 // ignored upstream of this layer.
2367 if (_activationEventNumber != [_currentEvent eventNumber]) {
2368 KHTMLPart::khtmlMouseReleaseEvent(event);
2373 _sendingEventToSubview = true;
2374 KWQ_BLOCK_EXCEPTIONS;
2375 [view mouseUp:_currentEvent];
2376 KWQ_UNBLOCK_EXCEPTIONS;
2377 _sendingEventToSubview = false;
2380 void KWQKHTMLPart::clearTimers(KHTMLView *view)
2383 view->unscheduleRelayout();
2385 DocumentImpl* document = view->part()->xmlDocImpl();
2386 if (document && document->renderer() && document->renderer()->layer())
2387 document->renderer()->layer()->suspendMarquees();
2392 void KWQKHTMLPart::clearTimers()
2394 clearTimers(d->m_view);
2397 bool KWQKHTMLPart::passSubframeEventToSubframe(NodeImpl::MouseEvent &event)
2399 KWQ_BLOCK_EXCEPTIONS;
2401 switch ([_currentEvent type]) {
2402 case NSLeftMouseDown: {
2403 NodeImpl *node = event.innerNode.handle();
2407 RenderPart *renderPart = dynamic_cast<RenderPart *>(node->renderer());
2411 if (!passWidgetMouseDownEventToWidget(renderPart)) {
2414 _mouseDownWasInSubframe = true;
2417 case NSLeftMouseUp: {
2418 if (!_mouseDownWasInSubframe) {
2421 NSView *view = mouseDownViewIfStillGood();
2425 ASSERT(!_sendingEventToSubview);
2426 _sendingEventToSubview = true;
2427 [view mouseUp:_currentEvent];
2428 _sendingEventToSubview = false;
2431 case NSLeftMouseDragged: {
2432 if (!_mouseDownWasInSubframe) {
2435 NSView *view = mouseDownViewIfStillGood();
2439 ASSERT(!_sendingEventToSubview);
2440 _sendingEventToSubview = true;
2441 [view mouseDragged:_currentEvent];
2442 _sendingEventToSubview = false;
2448 KWQ_UNBLOCK_EXCEPTIONS;
2453 void KWQKHTMLPart::mouseDown(NSEvent *event)
2455 KHTMLView *v = d->m_view;
2456 if (!v || _sendingEventToSubview) {
2460 KWQ_BLOCK_EXCEPTIONS;
2462 _mouseDownView = nil;
2465 NSEvent *oldCurrentEvent = _currentEvent;
2466 _currentEvent = KWQRetain(event);
2467 NSPoint loc = [event locationInWindow];
2468 _mouseDownWinX = (int)loc.x;
2469 _mouseDownWinY = (int)loc.y;
2470 d->m_view->viewportToContents(_mouseDownWinX, _mouseDownWinY, _mouseDownX, _mouseDownY);
2471 _mouseDownTimestamp = [event timestamp];
2473 NSResponder *oldFirstResponderAtMouseDownTime = _firstResponderAtMouseDownTime;
2474 // Unlike other places in WebCore where we get the first
2475 // responder, in this case we must be talking about the real first
2476 // responder, so we could just ask the bridge's window, instead of
2477 // the bridge. It's unclear which is better.
2478 _firstResponderAtMouseDownTime = KWQRetain([_bridge firstResponder]);
2480 _mouseDownMayStartDrag = false;
2481 _mouseDownMayStartSelect = false;
2483 // Sending an event can result in the destruction of the view and part.
2484 // We ref so that happens after we return from the KHTMLView function.
2486 QMouseEvent kEvent(QEvent::MouseButtonPress, event);
2487 v->viewportMousePressEvent(&kEvent);
2490 KWQRelease(_firstResponderAtMouseDownTime);
2491 _firstResponderAtMouseDownTime = oldFirstResponderAtMouseDownTime;
2493 ASSERT(_currentEvent == event);
2495 _currentEvent = oldCurrentEvent;
2497 KWQ_UNBLOCK_EXCEPTIONS;
2500 void KWQKHTMLPart::mouseDragged(NSEvent *event)
2502 KHTMLView *v = d->m_view;
2503 if (!v || _sendingEventToSubview) {
2507 KWQ_BLOCK_EXCEPTIONS;
2509 NSEvent *oldCurrentEvent = _currentEvent;
2510 _currentEvent = KWQRetain(event);
2512 // Sending an event can result in the destruction of the view and part.
2513 // We ref so that happens after we return from the KHTMLView function.
2515 QMouseEvent kEvent(QEvent::MouseMove, event);
2516 v->viewportMouseMoveEvent(&kEvent);
2519 ASSERT(_currentEvent == event);
2521 _currentEvent = oldCurrentEvent;
2523 KWQ_UNBLOCK_EXCEPTIONS;
2526 void KWQKHTMLPart::mouseUp(NSEvent *event)
2528 KHTMLView *v = d->m_view;
2529 if (!v || _sendingEventToSubview) {
2533 KWQ_BLOCK_EXCEPTIONS;
2535 NSEvent *oldCurrentEvent = _currentEvent;
2536 _currentEvent = KWQRetain(event);
2538 // Sending an event can result in the destruction of the view and part.
2539 // We ref so that happens after we return from the KHTMLView function.
2541 // Our behavior here is a little different that Qt. Qt always sends
2542 // a mouse release event, even for a double click. To correct problems
2543 // in khtml's DOM click event handling we do not send a release here
2544 // for a double click. Instead we send that event from KHTMLView's
2545 // viewportMouseDoubleClickEvent. Note also that the third click of
2546 // a triple click is treated as a single click, but the fourth is then
2547 // treated as another double click. Hence the "% 2" below.
2548 int clickCount = [event clickCount];
2549 if (clickCount > 0 && clickCount % 2 == 0) {
2550 QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, event);
2551 v->viewportMouseDoubleClickEvent(&doubleClickEvent);
2553 QMouseEvent releaseEvent(QEvent::MouseButtonRelease, event);
2554 v->viewportMouseReleaseEvent(&releaseEvent);
2558 ASSERT(_currentEvent == event);
2560 _currentEvent = oldCurrentEvent;
2562 _mouseDownView = nil;
2564 KWQ_UNBLOCK_EXCEPTIONS;
2568 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
2569 eats all subsequent events after it is starts its modal tracking loop. After the interaction
2570 is done, this routine is used to fix things up. When a mouse down started us tracking in
2571 the widget, we post a fake mouse up to balance the mouse down we started with. When a
2572 key down started us tracking in the widget, we post a fake key up to balance things out.
2573 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
2574 be over after the tracking is done.
2576 void KWQKHTMLPart::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
2578 KWQ_BLOCK_EXCEPTIONS;
2580 _sendingEventToSubview = false;
2581 int eventType = [initiatingEvent type];
2582 ASSERT(eventType == NSLeftMouseDown || eventType == NSKeyDown);
2583 NSEvent *fakeEvent = nil;
2584 if (eventType == NSLeftMouseDown) {
2585 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2586 location:[initiatingEvent locationInWindow]
2587 modifierFlags:[initiatingEvent modifierFlags]
2588 timestamp:[initiatingEvent timestamp]
2589 windowNumber:[initiatingEvent windowNumber]
2590 context:[initiatingEvent context]
2591 eventNumber:[initiatingEvent eventNumber]
2592 clickCount:[initiatingEvent clickCount]
2593 pressure:[initiatingEvent pressure]];
2597 else { // eventType == NSKeyDown
2598 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
2599 location:[initiatingEvent locationInWindow]
2600 modifierFlags:[initiatingEvent modifierFlags]
2601 timestamp:[initiatingEvent timestamp]
2602 windowNumber:[initiatingEvent windowNumber]
2603 context:[initiatingEvent context]
2604 characters:[initiatingEvent characters]
2605 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
2606 isARepeat:[initiatingEvent isARepeat]
2607 keyCode:[initiatingEvent keyCode]];
2608 keyEvent(fakeEvent);
2610 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
2611 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
2612 // no up-to-date cache of them anywhere.
2613 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
2614 location:[[_bridge window] convertScreenToBase:[NSEvent mouseLocation]]
2615 modifierFlags:[initiatingEvent modifierFlags]
2616 timestamp:[initiatingEvent timestamp]
2617 windowNumber:[initiatingEvent windowNumber]
2618 context:[initiatingEvent context]
2622 mouseMoved(fakeEvent);
2624 KWQ_UNBLOCK_EXCEPTIONS;
2627 void KWQKHTMLPart::mouseMoved(NSEvent *event)
2629 KHTMLView *v = d->m_view;
2630 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
2631 // These happen because WebKit sometimes has to fake up moved events.
2632 if (!v || d->m_bMousePressed) {
2636 KWQ_BLOCK_EXCEPTIONS;
2638 NSEvent *oldCurrentEvent = _currentEvent;
2639 _currentEvent = KWQRetain(event);
2641 // Sending an event can result in the destruction of the view and part.
2642 // We ref so that happens after we return from the KHTMLView function.
2644 QMouseEvent kEvent(QEvent::MouseMove, event);
2645 v->viewportMouseMoveEvent(&kEvent);
2648 ASSERT(_currentEvent == event);
2650 _currentEvent = oldCurrentEvent;
2652 KWQ_UNBLOCK_EXCEPTIONS;
2655 // Called as we walk up the element chain for nodes with CSS property -khtml-user-drag == auto
2656 bool KWQKHTMLPart::shouldDragAutoNode(DOM::NodeImpl* node, int x, int y) const
2658 // We assume that WebKit only cares about dragging things that can be leaf nodes (text, images, urls).
2659 // This saves a bunch of expensive calls (creating WC and WK element dicts) as we walk farther up
2660 // the node hierarchy, and we also don't have to cook up a way to ask WK about non-leaf nodes
2661 // (since right now WK just hit-tests using a cached lastMouseDown).
2662 if (!node->hasChildNodes() && d->m_view) {
2663 int windowX, windowY;
2664 d->m_view->contentsToViewport(x, y, windowX, windowY);
2665 NSPoint eventLoc = {windowX, windowY};
2666 return [_bridge mayStartDragAtEventLocation:eventLoc];
2672 bool KWQKHTMLPart::sendContextMenuEvent(NSEvent *event)
2674 DocumentImpl *doc = d->m_doc;
2675 KHTMLView *v = d->m_view;
2680 KWQ_BLOCK_EXCEPTIONS;
2682 NSEvent *oldCurrentEvent = _currentEvent;
2683 _currentEvent = KWQRetain(event);
2685 QMouseEvent qev(QEvent::MouseButtonPress, event);
2688 v->viewportToContents(qev.x(), qev.y(), xm, ym);
2690 NodeImpl::MouseEvent mev(qev.stateAfter(), NodeImpl::MousePress);
2691 doc->prepareMouseEvent(false, xm, ym, &mev);
2693 // Sending an event can result in the destruction of the view and part.
2694 // We ref so that happens after we return from the KHTMLView function.
2696 bool swallowEvent = v->dispatchMouseEvent(EventImpl::CONTEXTMENU_EVENT,
2697 mev.innerNode.handle(), true, 0, &qev, true, NodeImpl::MousePress);
2700 ASSERT(_currentEvent == event);
2702 _currentEvent = oldCurrentEvent;
2704 return swallowEvent;
2706 KWQ_UNBLOCK_EXCEPTIONS;
2711 struct ListItemInfo {
2716 NSFileWrapper *KWQKHTMLPart::fileWrapperForElement(ElementImpl *e)
2718 KWQ_BLOCK_EXCEPTIONS;
2720 NSFileWrapper *wrapper = nil;
2722 AtomicString attr = e->getAttribute(ATTR_SRC);
2723 if (!attr.isEmpty()) {
2724 NSURL *URL = completeURL(attr.string()).getNSURL();
2725 wrapper = [_bridge fileWrapperForURL:URL];
2728 RenderImage *renderer = static_cast<RenderImage *>(e->renderer());
2729 NSImage *image = renderer->pixmap().image();
2730 NSData *tiffData = [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0];
2731 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:tiffData];
2732 [wrapper setPreferredFilename:@"image.tiff"];
2733 [wrapper autorelease];
2738 KWQ_UNBLOCK_EXCEPTIONS;
2743 static ElementImpl *listParent(ElementImpl *item)
2745 // Ick! Avoid use of item->id() which confuses ObjC++.
2746 unsigned short _id = Node(item).elementId();
2748 while (_id != ID_UL && _id != ID_OL) {
2749 item = static_cast<ElementImpl *>(item->parentNode());
2752 _id = Node(item).elementId();
2757 static NodeImpl* isTextFirstInListItem(NodeImpl *e)
2759 if (Node(e).nodeType() != Node::TEXT_NODE)
2761 NodeImpl* par = e->parentNode();
2763 if (par->firstChild() != e)
2765 if (Node(par).elementId() == ID_LI)
2768 par = par->parentNode();
2773 // FIXME: This collosal function needs to be refactored into maintainable smaller bits.
2775 #define BULLET_CHAR 0x2022
2776 #define SQUARE_CHAR 0x25AA
2777 #define CIRCLE_CHAR 0x25E6
2779 NSAttributedString *KWQKHTMLPart::attributedString(NodeImpl *_start, int startOffset, NodeImpl *endNode, int endOffset)
2781 KWQ_BLOCK_EXCEPTIONS;
2783 NodeImpl * _startNode = _start;
2785 if (_startNode == nil) {
2789 NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] init] autorelease];
2791 bool hasNewLine = true;
2792 bool addedSpace = true;
2793 NSAttributedString *pendingStyledSpace = nil;
2794 bool hasParagraphBreak = true;
2795 const ElementImpl *linkStartNode = 0;
2796 unsigned linkStartLocation = 0;
2797 QPtrList<ElementImpl> listItems;
2798 QValueList<ListItemInfo> listItemLocations;
2799 float maxMarkerWidth = 0;
2801 Node n = _startNode;
2803 // If the first item is the entire text of a list item, use the list item node as the start of the
2804 // selection, not the text node. The user's intent was probably to select the list.
2805 if (n.nodeType() == Node::TEXT_NODE && startOffset == 0) {
2806 NodeImpl *startListNode = isTextFirstInListItem(_startNode);
2808 _startNode = startListNode;
2813 while (!n.isNull()) {
2814 RenderObject *renderer = n.handle()->renderer();
2816 RenderStyle *style = renderer->style();
2817 NSFont *font = style->font().getNSFont();
2818 bool needSpace = pendingStyledSpace != nil;
2819 if (n.nodeType() == Node::TEXT_NODE) {
2823 [pendingStyledSpace release];
2824 pendingStyledSpace = nil;
2828 QString str = n.nodeValue().string();
2829 int start = (n == _startNode) ? startOffset : -1;
2830 int end = (n == endNode) ? endOffset : -1;
2831 if (renderer->isText()) {
2832 if (renderer->style()->whiteSpace() == PRE) {
2833 if (needSpace && !addedSpace) {
2834 if (text.isEmpty() && linkStartLocation == [result length]) {
2835 ++linkStartLocation;
2837 [result appendAttributedString:pendingStyledSpace];
2839 int runStart = (start == -1) ? 0 : start;
2840 int runEnd = (end == -1) ? str.length() : end;
2841 text += str.mid(runStart, runEnd-runStart);
2842 [pendingStyledSpace release];
2843 pendingStyledSpace = nil;
2844 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2847 RenderText* textObj = static_cast<RenderText*>(renderer);
2848 if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) {
2849 // We have no runs, but we do have a length. This means we must be
2850 // whitespace that collapsed away at the end of a line.
2856 for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
2857 int runStart = (start == -1) ? box->m_start : start;
2858 int runEnd = (end == -1) ? box->m_start + box->m_len : end;
2859 runEnd = kMin(runEnd, box->m_start + box->m_len);
2860 if (runStart >= box->m_start &&
2861 runStart < box->m_start + box->m_len) {
2862 if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) {
2863 needSpace = true; // collapsed space at the start
2865 if (needSpace && !addedSpace) {
2866 if (pendingStyledSpace != nil) {
2867 if (text.isEmpty() && linkStartLocation == [result length]) {
2868 ++linkStartLocation;
2870 [result appendAttributedString:pendingStyledSpace];
2875 QString runText = str.mid(runStart, runEnd - runStart);
2876 runText.replace('\n', ' ');
2878 int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
2879 needSpace = nextRunStart > runEnd;
2880 [pendingStyledSpace release];
2881 pendingStyledSpace = nil;
2882 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2885 if (end != -1 && runEnd >= end)
2892 text.replace('\\', renderer->backslashAsCurrencySymbol());
2894 if (text.length() > 0 || needSpace) {
2895 NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
2896 [attrs setObject:font forKey:NSFontAttributeName];
2897 if (style && style->color().isValid() && qAlpha(style->color().rgb()) != 0)
2898 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
2899 if (style && style->backgroundColor().isValid() && qAlpha(style->backgroundColor().rgb()) != 0)
2900 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
2902 if (text.length() > 0) {
2903 hasParagraphBreak = false;
2904 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs];
2905 [result appendAttributedString: partialString];
2906 [partialString release];
2910 [pendingStyledSpace release];
2911 pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs];
2917 // This is our simple HTML -> ASCII transformation:
2919 unsigned short _id = n.elementId();
2922 // Note the start of the <a> element. We will add the NSLinkAttributeName
2923 // attribute to the attributed string when navigating to the next sibling
2925 linkStartLocation = [result length];
2926 linkStartNode = static_cast<ElementImpl*>(n.handle());
2937 ElementImpl *itemParent = listParent(static_cast<ElementImpl *>(n.handle()));
2943 listItems.append(static_cast<ElementImpl*>(n.handle()));
2945 info.start = [result length];
2947 listItemLocations.append (info);
2951 khtml::RenderListItem *listRenderer = static_cast<khtml::RenderListItem*>(renderer);
2953 maxMarkerWidth = MAX([font pointSize], maxMarkerWidth);
2954 switch(listRenderer->style()->listStyleType()) {
2956 listText += ((QChar)BULLET_CHAR);
2959 listText += ((QChar)CIRCLE_CHAR);
2962 listText += ((QChar)SQUARE_CHAR);
2967 QString marker = listRenderer->markerStringValue();
2969 // Use AppKit metrics. Will be rendered by AppKit.
2970 float markerWidth = [font widthOfString: marker.getNSString()];
2971 maxMarkerWidth = MAX(markerWidth, maxMarkerWidth);
2977 NSMutableDictionary *attrs;
2979 attrs = [[NSMutableDictionary alloc] init];
2980 [attrs setObject:font forKey:NSFontAttributeName];
2981 if (style && style->color().isValid())
2982 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
2983 if (style && style->backgroundColor().isValid())
2984 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
2986 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs];
2988 [result appendAttributedString: partialString];
2989 [partialString release];
3024 if (!hasParagraphBreak) {
3026 hasParagraphBreak = true;
3032 if (pendingStyledSpace != nil) {
3033 if (linkStartLocation == [result length]) {
3034 ++linkStartLocation;
3036 [result appendAttributedString:pendingStyledSpace];
3037 [pendingStyledSpace release];
3038 pendingStyledSpace = nil;
3040 NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<ElementImpl *>(n.handle()));
3041 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
3042 NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment];
3043 [result appendAttributedString: iString];
3044 [attachment release];
3047 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3048 [result appendAttributedString: partialString];
3049 [partialString release];
3056 Node next = n.firstChild();
3058 next = n.nextSibling();
3061 while (next.isNull() && !n.parentNode().isNull()) {
3066 next = n.nextSibling();
3068 unsigned short _id = n.elementId();
3071 // End of a <a> element. Create an attributed string NSLinkAttributeName attribute
3072 // for the range of the link. Note that we create the attributed string from the DOM, which
3073 // will have corrected any illegally nested <a> elements.
3074 if (linkStartNode && n.handle() == linkStartNode){
3075 DOMString href = parseURL(linkStartNode->getAttribute(ATTR_HREF));
3076 KURL kURL = KWQ(linkStartNode->getDocument()->part())->completeURL(href.string());
3078 NSURL *URL = kURL.getNSURL();
3079 [result addAttribute:NSLinkAttributeName value:URL range:NSMakeRange(linkStartLocation, [result length]-linkStartLocation)];
3093 int i, count = listItems.count();
3094 for (i = 0; i < count; i++){
3095 if (listItems.at(i) == n.handle()){
3096 listItemLocations[i].end = [result length];
3129 // An extra newline is needed at the start, not the end, of these types of tags,
3130 // so don't add another here.
3135 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3136 [result appendAttributedString:partialString];
3137 [partialString release];
3143 [pendingStyledSpace release];
3145 // Apply paragraph styles from outside in. This ensures that nested lists correctly
3146 // override their parent's paragraph style.
3148 unsigned i, count = listItems.count();
3152 #ifdef POSITION_LIST
3153 NodeImpl *containingBlock;
3154 int containingBlockX, containingBlockY;
3156 // Determine the position of the outermost containing block. All paragraph
3157 // styles and tabs should be relative to this position. So, the horizontal position of
3158 // each item in the list (in the resulting attributed string) will be relative to position
3159 // of the outermost containing block.
3161 containingBlock = _startNode;
3162 while (containingBlock->renderer()->isInline()){
3163 containingBlock = containingBlock->parentNode();
3165 containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY);
3169 for (i = 0; i < count; i++){
3170 e = listItems.at(i);
3171 info = listItemLocations[i];
3173 if (info.end < info.start)
3174 info.end = [result length];
3176 RenderObject *r = e->renderer();
3177 RenderStyle *style = r->style();
3180 NSFont *font = style->font().getNSFont();
3181 float pointSize = [font pointSize];
3183 #ifdef POSITION_LIST
3185 r->absolutePosition(rx, ry);
3186 rx -= containingBlockX;
3188 // Ensure that the text is indented at least enough to allow for the markers.
3189 rx = MAX(rx, (int)maxMarkerWidth);
3191 rx = (int)MAX(maxMarkerWidth, pointSize);
3194 // The bullet text will be right aligned at the first tab marker, followed
3195 // by a space, followed by the list item text. The space is arbitrarily
3196 // picked as pointSize*2/3. The space on the first line of the text item
3197 // is established by a left aligned tab, on subsequent lines it's established
3198 // by the head indent.
3199 NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
3200 [mps setFirstLineHeadIndent: 0];
3201 [mps setHeadIndent: rx];
3202 [mps setTabStops:[NSArray arrayWithObjects:
3203 [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease],
3204 [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease],
3206 [result addAttribute:NSParagraphStyleAttributeName value:mps range:NSMakeRange(info.start, info.end-info.start)];
3213 KWQ_UNBLOCK_EXCEPTIONS;
3218 QRect KWQKHTMLPart::selectionRect() const
3224 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
3229 return root->selectionRect();
3232 // returns NSRect because going through QRect would truncate any floats
3233 NSRect KWQKHTMLPart::visibleSelectionRect() const
3238 NSView *documentView = d->m_view->getDocumentView();
3239 if (!documentView) {
3242 return NSIntersectionRect(selectionRect(), [documentView visibleRect]);
3245 NSImage *KWQKHTMLPart::imageFromRect(NSRect rect) const
3247 NSView *view = d->m_view->getDocumentView();
3252 NSRect bounds = [view bounds];
3253 NSImage *resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
3255 KWQ_BLOCK_EXCEPTIONS;
3257 [resultImage setFlipped:YES];
3258 [resultImage lockFocus];
3260 [NSGraphicsContext saveGraphicsState];
3261 NSPoint translation = { -(NSMinX(rect) - NSMinX(bounds)), -(NSMinY(rect) - NSMinY(bounds)) };
3262 CGContextTranslateCTM((CGContext *)[[NSGraphicsContext currentContext] graphicsPort], translation.x, translation.y);
3264 // We change the coord system at the CG level, out from under the AK focus machinery, because it doesn't
3265 // work to change the coord system of a focused view. However, WebImageRenderer uses the difference
3266 // between the focused view's coord system and the window's coord system to adjust the pattern phase, and
3267 // that calc ignores our translation. So we must tell it about this extra phase offset.
3269 // Window is not flipped, we are, so y coord must be inverted when describing phase, which is a
3270 // window level notion.
3271 translation.y = -translation.y;
3272 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:translation];
3274 [view drawRect:rect];
3276 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:NSZeroPoint];
3277 [NSGraphicsContext restoreGraphicsState];
3279 [resultImage unlockFocus];
3280 [resultImage setFlipped:NO];
3282 KWQ_UNBLOCK_EXCEPTIONS;
3287 NSImage *KWQKHTMLPart::selectionImage() const
3289 _drawSelectionOnly = true; // invoke special drawing mode
3290 NSImage *result = imageFromRect(visibleSelectionRect());
3291 _drawSelectionOnly = false;
3295 NSImage *KWQKHTMLPart::snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRect *elementRect) const
3297 RenderObject *renderer = node.handle()->renderer();
3302 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS)
3303 d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might
3304 // imply new styles, plus JS could have changed other things
3306 NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
3308 _elementToDraw = node; // invoke special sub-tree drawing mode
3309 NSImage *result = imageFromRect(paintingRect);
3310 renderer->updateDragState(false);
3314 *elementRect = topLevelRect;
3317 *imageRect = paintingRect;
3322 NSFont *KWQKHTMLPart::fontForCurrentPosition() const
3324 if (d->m_selection.state() == Selection::NONE)
3327 Range range(d->m_selection.toRange());
3328 Position pos(range.startContainer().handle(), range.startOffset());
3329 ASSERT(pos.notEmpty());
3330 ElementImpl *elem = pos.element();
3331 if (d->m_typingStyle) {
3335 int exceptionCode = 0;
3336 ElementImpl *styleElement = xmlDocImpl()->createHTMLElement("SPAN", exceptionCode);
3337 ASSERT(exceptionCode == 0);
3339 styleElement->setAttribute(ATTR_STYLE, d->m_typingStyle->cssText().implementation(), exceptionCode);
3340 ASSERT(exceptionCode == 0);
3342 TextImpl *text = xmlDocImpl()->createEditingTextNode("");
3343 styleElement->appendChild(text, exceptionCode);
3344 ASSERT(exceptionCode == 0);
3346 elem->appendChild(styleElement, exceptionCode);
3347 ASSERT(exceptionCode == 0);
3349 RenderObject *renderer = styleElement->renderer();
3351 NSFont *result = renderer->style()->font().getNSFont();
3353 styleElement->removeChild(text, exceptionCode);
3354 ASSERT(exceptionCode == 0);
3356 elem->removeChild(styleElement, exceptionCode);
3357 ASSERT(exceptionCode == 0);
3362 RenderObject *renderer = elem->renderer();
3364 return renderer->style()->font().getNSFont();
3369 KWQWindowWidget *KWQKHTMLPart::topLevelWidget()
3371 return _windowWidget;
3374 int KWQKHTMLPart::selectionStartOffset() const
3376 return d->m_selection.start().offset();
3379 int KWQKHTMLPart::selectionEndOffset() const
3381 return d->m_selection.end().offset();
3384 NodeImpl *KWQKHTMLPart::selectionStart() const
3386 return d->m_selection.start().node();
3389 NodeImpl *KWQKHTMLPart::selectionEnd() const
3391 return d->m_selection.end().node();
3394 void KWQKHTMLPart::setBridge(WebCoreBridge *p)
3397 delete _windowWidget;
3400 _windowWidget = new KWQWindowWidget(_bridge);
3403 QString KWQKHTMLPart::overrideMediaType()
3405 NSString *overrideType = [_bridge overrideMediaType];
3407 return QString::fromNSString(overrideType);
3411 void KWQKHTMLPart::setMediaType(const QString &type)
3414 d->m_view->setMediaType(type);
3418 void KWQKHTMLPart::setDisplaysWithFocusAttributes(bool flag)
3420 if (_displaysWithFocusAttributes == flag)
3422 _displaysWithFocusAttributes = flag;
3424 // This method does the job of updating the view based on whether the view is "active".
3425 // This involves three kinds of drawing updates:
3427 // 1. The background color used to draw behind selected content (active | inactive color)
3429 d->m_view->updateContents(QRect(visibleSelectionRect()));
3431 // 2. Caret blinking (blinks | does not blink)
3432 // Also, put the caret someplace if the selection is empty and the part is editable.
3433 // This has the effect of flashing the caret in a contentEditable view automatically
3434 // without requiring the programmer to set a selection explicitly.
3435 DocumentImpl *doc = xmlDocImpl();
3436 if (doc && flag && selection().state() == Selection::NONE && isContentEditable()) {
3437 NodeImpl *node = doc->documentElement();
3439 // Look for a block flow, but skip over the HTML element, since we really
3440 // want to get at least as far as the the BODY element in a document.
3441 if (node->isBlockFlow() && node->identifier() != ID_HTML)
3443 node = node->traverseNextNode();
3446 setSelection(Position(node, 0));
3448 setCaretVisible(flag);
3450 // 3. The drawing of a focus ring around links in web pages.
3452 NodeImpl *node = doc->focusNode();
3453 if (node && node->renderer())
3454 node->renderer()->repaint();
3458 QChar KWQKHTMLPart::backslashAsCurrencySymbol() const
3460 DocumentImpl *doc = xmlDocImpl();
3464 Decoder *decoder = doc->decoder();
3468 const QTextCodec *codec = decoder->codec();
3472 return codec->backslashAsCurrencySymbol();
3475 NSColor *KWQKHTMLPart::bodyBackgroundColor() const
3477 if (docImpl() && docImpl()->body() && docImpl()->body()->renderer()) {
3478 QColor bgColor = docImpl()->body()->renderer()->style()->backgroundColor();
3479 if (bgColor.isValid()) {
3480 return bgColor.getNSColor();
3486 WebCoreKeyboardUIMode KWQKHTMLPart::keyboardUIMode() const
3488 KWQ_BLOCK_EXCEPTIONS;
3489 return [_bridge keyboardUIMode];
3490 KWQ_UNBLOCK_EXCEPTIONS;
3492 return WebCoreKeyboardAccessDefault;
3495 void KWQKHTMLPart::setName(const QString &name)
3499 KWQKHTMLPart *parent = KWQ(parentPart());
3501 if (parent && (name.isEmpty() || parent->frameExists(name))) {
3502 n = parent->requestFrameName();
3505 KHTMLPart::setName(n);
3507 KWQ_BLOCK_EXCEPTIONS;
3508 [_bridge didSetName:n.getNSString()];
3509 KWQ_UNBLOCK_EXCEPTIONS;
3513 void KWQKHTMLPart::didTellBridgeAboutLoad(const QString &urlString)
3515 urlsBridgeKnowsAbout.insert(urlString, (char *)1);
3519 bool KWQKHTMLPart::haveToldBridgeAboutLoad(const QString &urlString)
3521 return urlsBridgeKnowsAbout.find(urlString) != 0;
3524 void KWQKHTMLPart::clear()
3526 urlsBridgeKnowsAbout.clear();
3531 void KHTMLPart::print()
3533 [KWQ(this)->_bridge print];
3536 KJS::Bindings::Instance *KWQKHTMLPart::getAppletInstanceForView (NSView *aView)
3540 // Get a pointer to the actual Java applet instance.
3541 if ([_bridge respondsToSelector:@selector(getAppletInView:)])
3542 applet = [_bridge getAppletInView:aView];
3544 applet = [_bridge pollForAppletInView:aView];
3547 // Wrap the Java instance in a language neutral binding and hand
3548 // off ownership to the APPLET element.
3549 KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::JavaLanguage, applet);
3551 KJS::Bindings::RootObject *root = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView);
3552 instance->setExecutionContext (root);
3560 @interface NSObject (WebPlugIn)
3561 - (id)objectForWebScript;
3562 - (void *)pluginScriptableObject;
3565 KJS::Bindings::Instance *KWQKHTMLPart::getEmbedInstanceForView (NSView *aView)
3567 if ([aView respondsToSelector:@selector(objectForWebScript)]){
3568 id object = [aView objectForWebScript];
3570 return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::ObjectiveCLanguage, object);
3572 else if ([aView respondsToSelector:@selector(pluginScriptableObject)]){
3573 void *object = [aView pluginScriptableObject];
3575 return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::CLanguage, object);
3580 void KWQKHTMLPart::addPluginRootObject(const KJS::Bindings::RootObject *root)
3582 rootObjects.append (root);
3585 void KWQKHTMLPart::cleanupPluginRootObjects()
3587 KJS::Bindings::RootObject *root;
3588 while ((root = rootObjects.getLast())) {
3589 root->removeAllNativeReferences ();
3590 rootObjects.removeLast();
3594 void KWQKHTMLPart::registerCommandForUndo(const khtml::EditCommand &cmd)
3596 ASSERT(cmd.handle());
3597 KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.handle()];
3598 [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(undoEditing:) object:kwq];
3599 _haveUndoRedoOperations = YES;
3602 void KWQKHTMLPart::registerCommandForRedo(const khtml::EditCommand &cmd)
3604 ASSERT(cmd.handle());
3605 KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.handle()];
3606 [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(redoEditing:) object:kwq];
3607 _haveUndoRedoOperations = YES;
3610 void KWQKHTMLPart::clearUndoRedoOperations()
3612 if (_haveUndoRedoOperations) {
3613 [[_bridge undoManager] removeAllActionsWithTarget:_bridge];
3614 _haveUndoRedoOperations = NO;
3618 void KWQKHTMLPart::issueUndoCommand()
3621 [[_bridge undoManager] undo];
3624 void KWQKHTMLPart::issueRedoCommand()
3627 [[_bridge undoManager] redo];
3630 void KWQKHTMLPart::issueCutCommand()
3632 [_bridge issueCutCommand];
3635 void KWQKHTMLPart::issueCopyCommand()
3637 [_bridge issueCopyCommand];
3640 void KWQKHTMLPart::issuePasteCommand()
3642 [_bridge issuePasteCommand];
3645 bool KHTMLPart::canUndo() const
3647 return [[KWQ(this)->_bridge undoManager] canUndo];
3650 bool KHTMLPart::canRedo() const
3652 return [[KWQ(this)->_bridge undoManager] canRedo];
3655 void KWQKHTMLPart::markMisspellingsInSelection(const Selection &selection)
3657 // No work to do if there is no selection or continuous spell check is off, or the
3658 // selection start position is not now rendered (maybe it has been deleted).
3659 if (selection.state() == Selection::NONE ||
3660 ![_bridge isContinuousSpellCheckingEnabled] ||
3661 !selection.start().inRenderedContent())
3664 // Expand selection to word boundaries so that complete words wind up being passed to the spell checker.
3666 // FIXME: It seems that NSSpellChecker is too slow to handle finding multiple misspellings in an
3667 // arbitrarily large selection.
3668 // So, for now, the idea is to mimic AppKit behavior and limit the selection to the first word
3669 // of the selection passed in.
3670 // This is not ideal by any means, but this is the convention.
3671 Position end(selection.start().nextWordBoundary());
3672 if (end == selection.start())
3673 end = end.nextCharacterPosition().nextWordBoundary();
3674 Selection s(selection.start().previousWordBoundary(), end);
3675 if (s.state() == Selection::NONE)
3677 // Change to this someday to spell check the entire selection.
3678 // The rest of this function is prepared to handle finding multiple misspellings in a
3679 // more-than-one-word selection.
3680 //Selection s = Selection(selection.start().previousWordBoundary(), selection.end().nextWordBoundary());
3682 Range searchRange(s.toRange());
3684 // If we're not in an editable node, bail.
3685 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
3686 if (!editableNodeImpl->isContentEditable())
3689 // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then
3690 // forward by a word happens to do the trick.
3691 Position start(searchRange.startContainer().handle(), searchRange.startOffset());
3692 Position newStart = start.previousCharacterPosition();
3693 if (newStart != start) {
3694 newStart = newStart.nextWordBoundary();
3695 // Must ensure the position is on a container to be usable with a DOMRange
3696 newStart = newStart.equivalentRangeCompliantPosition();
3697 searchRange.setStart(Node(newStart.node()), newStart.offset());
3698 } // else we were already at the start of the editable node
3700 if (searchRange.collapsed())
3701 // nothing to search in
3704 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
3705 WordAwareIterator it(searchRange);
3707 while (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
3708 const QChar *chars = it.characters();
3709 long len = it.length();
3710 if (len > 1 || !chars[0].isSpace()) {
3711 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
3713 // Loop over the chunk to find each misspelling in it.
3714 while (startIndex < len) {
3715 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:startIndex language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
3716 if (misspelling.length == 0) {
3720 // Build up result range and string. Note the misspelling may span many text nodes,
3721 // but the CharIterator insulates us from this complexity
3722 Range misspellingRange(xmlDocImpl());
3723 CharacterIterator chars(it.range());
3724 chars.advance(misspelling.location);
3725 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
3726 chars.advance(misspelling.length);
3727 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
3728 // Mark misspelling in document.
3729 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
3730 startIndex = misspelling.location + misspelling.length;
3740 void KWQKHTMLPart::updateSpellChecking()
3743 if ([_bridge isContinuousSpellCheckingEnabled]) {
3744 // This only erases a marker in the first word of the selection. Perhaps peculiar, but it
3746 Position start(selection().start().previousWordBoundary());
3747 Position end(selection().start().nextWordBoundary());
3748 if (end == selection().start())
3749 end = end.nextCharacterPosition().nextWordBoundary();
3750 Selection selection(start, end);
3751 xmlDocImpl()->removeMarker(selection.toRange(), DocumentMarker::Spelling);
3754 // When continuous spell checking is off, no markers appear after the selection changes.
3755 xmlDocImpl()->removeAllMarkers();
3760 void KWQKHTMLPart::respondToChangedSelection()
3762 updateSpellChecking();
3763 [_bridge respondToChangedSelection];
3766 void KWQKHTMLPart::respondToChangedContents()
3768 [_bridge respondToChangedContents];
3771 bool KWQKHTMLPart::isContentEditable() const
3773 return [_bridge isEditable];
3776 bool KWQKHTMLPart::shouldBeginEditing(const Range &range) const
3778 ASSERT(!range.isNull());
3779 return [_bridge shouldBeginEditing:[DOMRange _rangeWithImpl:range.handle()]];
3782 bool KWQKHTMLPart::shouldEndEditing(const Range &range) const
3784 ASSERT(!range.isNull());
3785 return [_bridge shouldEndEditing:[DOMRange _rangeWithImpl:range.handle()]];
3788 DOM::Range KWQKHTMLPart::markedRange() const
3790 return m_markedRange;
3793 void KWQKHTMLPart::setMarkedRange(const DOM::Range &range)
3795 ASSERT(!range.handle() || range.startContainer() == range.endContainer());
3796 ASSERT(!range.handle() || range.startContainer().nodeType() == Node::TEXT_NODE);
3798 if (m_markedRange.handle() && xmlDocImpl()
3799 && m_markedRange.startContainer().handle()->renderer()) {
3800 m_markedRange.startContainer().handle()->renderer()->repaint();
3803 m_markedRange = range;
3805 if (m_markedRange.handle() && xmlDocImpl()
3806 && m_markedRange.startContainer().handle()->renderer()) {
3807 m_markedRange.startContainer().handle()->renderer()->repaint();