/* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "UIScriptControllerMac.h" #if PLATFORM(MAC) #import "DumpRenderTree.h" #import "EventSendingController.h" #import "LayoutTestSpellChecker.h" #import "UIScriptContext.h" #import #import #import #import #import #import #import #import #import namespace WTR { Ref UIScriptController::create(UIScriptContext& context) { return adoptRef(*new UIScriptControllerMac(context)); } void UIScriptControllerMac::doAsyncTask(JSValueRef callback) { unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent); WorkQueue::main().dispatch([this, protectedThis = Ref { *this }, callbackID] { if (!m_context) return; m_context->asyncTaskComplete(callbackID); }); } void UIScriptControllerMac::replaceTextAtRange(JSStringRef text, int location, int length) { auto textToInsert = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, text)); auto rangeAttribute = adoptNS([[NSDictionary alloc] initWithObjectsAndKeys:NSStringFromRange(NSMakeRange(location == -1 ? NSNotFound : location, length)), NSTextInputReplacementRangeAttributeName, nil]); auto textAndRange = adoptNS([[NSAttributedString alloc] initWithString:(__bridge NSString *)textToInsert.get() attributes:rangeAttribute.get()]); [mainFrame.webView insertText:textAndRange.get()]; } void UIScriptControllerMac::zoomToScale(double scale, JSValueRef callback) { WebView *webView = [mainFrame webView]; [webView _scaleWebView:scale atOrigin:NSZeroPoint]; doAsyncTask(callback); } double UIScriptControllerMac::zoomScale() const { return mainFrame.webView._viewScaleFactor; } void UIScriptControllerMac::simulateAccessibilitySettingsChangeNotification(JSValueRef callback) { NSNotificationCenter *center = [[NSWorkspace sharedWorkspace] notificationCenter]; [center postNotificationName:NSWorkspaceAccessibilityDisplayOptionsDidChangeNotification object:[mainFrame webView]]; doAsyncTask(callback); } JSObjectRef UIScriptControllerMac::contentsOfUserInterfaceItem(JSStringRef interfaceItem) const { #if JSC_OBJC_API_ENABLED WebView *webView = [mainFrame webView]; RetainPtr interfaceItemCF = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, interfaceItem)); NSDictionary *contentDictionary = [webView _contentsOfUserInterfaceItem:(__bridge NSString *)interfaceItemCF.get()]; return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:contentDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr); #else UNUSED_PARAM(interfaceItem); return nullptr; #endif } void UIScriptControllerMac::activateDataListSuggestion(unsigned index, JSValueRef callback) { // FIXME: Not implemented. UNUSED_PARAM(index); unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent); WorkQueue::main().dispatch([this, protectedThis = Ref { *this }, callbackID] { if (!m_context) return; m_context->asyncTaskComplete(callbackID); }); } void UIScriptControllerMac::overridePreference(JSStringRef preferenceRef, JSStringRef valueRef) { WebPreferences *preferences = mainFrame.webView.preferences; RetainPtr value = adoptCF(JSStringCopyCFString(kCFAllocatorDefault, valueRef)); if (JSStringIsEqualToUTF8CString(preferenceRef, "WebKitMinimumFontSize")) preferences.minimumFontSize = [(__bridge NSString *)value.get() doubleValue]; } void UIScriptControllerMac::removeViewFromWindow(JSValueRef callback) { unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent); WebView *webView = [mainFrame webView]; [webView removeFromSuperview]; WorkQueue::main().dispatch([this, protectedThis = Ref { *this }, callbackID] { if (!m_context) return; m_context->asyncTaskComplete(callbackID); }); } void UIScriptControllerMac::addViewToWindow(JSValueRef callback) { unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent); WebView *webView = [mainFrame webView]; [[mainWindow contentView] addSubview:webView]; WorkQueue::main().dispatch([this, protectedThis = Ref { *this }, callbackID] { if (!m_context) return; m_context->asyncTaskComplete(callbackID); }); } void UIScriptControllerMac::toggleCapsLock(JSValueRef callback) { doAsyncTask(callback); } void UIScriptControllerMac::setWebViewEditable(bool editable) { [mainFrame webView].editable = editable; } NSUndoManager *UIScriptControllerMac::platformUndoManager() const { return nil; } void UIScriptControllerMac::copyText(JSStringRef text) { NSPasteboard *pasteboard = NSPasteboard.generalPasteboard; [pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil]; [pasteboard setString:text->string() forType:NSPasteboardTypeString]; } void UIScriptControllerMac::setSpellCheckerResults(JSValueRef results) { [[LayoutTestSpellChecker checker] setResultsFromJSValue:results inContext:m_context->jsContext()]; } static NSString *const TopLevelEventInfoKey = @"events"; static NSString *const EventTypeKey = @"type"; static NSString *const ViewRelativeXPositionKey = @"viewX"; static NSString *const ViewRelativeYPositionKey = @"viewY"; static NSString *const DeltaXKey = @"deltaX"; static NSString *const DeltaYKey = @"deltaY"; static NSString *const PhaseKey = @"phase"; static NSString *const MomentumPhaseKey = @"momentumPhase"; static CGGesturePhase gesturePhaseFromString(NSString *phaseStr) { if ([phaseStr isEqualToString:@"began"]) return kCGGesturePhaseBegan; if ([phaseStr isEqualToString:@"changed"]) return kCGGesturePhaseChanged; if ([phaseStr isEqualToString:@"ended"]) return kCGGesturePhaseEnded; if ([phaseStr isEqualToString:@"cancelled"]) return kCGGesturePhaseCancelled; if ([phaseStr isEqualToString:@"maybegin"]) return kCGGesturePhaseMayBegin; return kCGGesturePhaseNone; } static CGMomentumScrollPhase momentumPhaseFromString(NSString *phaseStr) { if ([phaseStr isEqualToString:@"began"]) return kCGMomentumScrollPhaseBegin; if ([phaseStr isEqualToString:@"changed"] || [phaseStr isEqualToString:@"continue"]) // Allow "continue" for ease of conversion from mouseScrollByWithWheelAndMomentumPhases values. return kCGMomentumScrollPhaseContinue; if ([phaseStr isEqualToString:@"ended"]) return kCGMomentumScrollPhaseEnd; return kCGMomentumScrollPhaseNone; } static EventSendingController *eventSenderFromView(WebView *webView) { auto frame = [webView mainFrame]; auto windowObject = [frame windowObject]; return [windowObject valueForKey:@"eventSender"]; } void UIScriptControllerMac::sendEventStream(JSStringRef eventsJSON, JSValueRef callback) { WebView *webView = [mainFrame webView]; // didClearWindowObjectInStandardWorldForFrame stashed EventSendingController on this window property. EventSendingController* eventSender = eventSenderFromView(webView); if (!eventSender) { ASSERT_NOT_REACHED(); return; } unsigned callbackID = m_context->prepareForAsyncTask(callback, CallbackTypeNonPersistent); auto jsonString = eventsJSON->string(); auto eventInfo = dynamic_objc_cast([NSJSONSerialization JSONObjectWithData:[(NSString *)jsonString dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves error:nil]); if (!eventInfo || ![eventInfo isKindOfClass:[NSDictionary class]]) { WTFLogAlways("JSON is not convertible to a dictionary"); return; } double currentViewRelativeX = 0; double currentViewRelativeY = 0; constexpr uint64_t nanosecondsPerSecond = 1e9; constexpr uint64_t nanosecondsEventInterval = nanosecondsPerSecond / 60; auto currentTime = mach_absolute_time(); for (NSMutableDictionary *event in eventInfo[TopLevelEventInfoKey]) { id eventType = event[EventTypeKey]; if (!event[EventTypeKey]) { WTFLogAlways("Missing event type"); break; } if ([eventType isEqualToString:@"wheel"]) { auto phase = kCGGesturePhaseNone; auto momentumPhase = kCGMomentumScrollPhaseNone; if (!event[PhaseKey] && !event[MomentumPhaseKey]) { WTFLogAlways("Event must specify phase or momentumPhase"); break; } if (id phaseString = event[PhaseKey]) phase = gesturePhaseFromString(phaseString); if (id phaseString = event[MomentumPhaseKey]) momentumPhase = momentumPhaseFromString(phaseString); ASSERT_IMPLIES(phase == kCGGesturePhaseNone, momentumPhase != kCGMomentumScrollPhaseNone); ASSERT_IMPLIES(momentumPhase == kCGMomentumScrollPhaseNone, phase != kCGGesturePhaseNone); if (event[ViewRelativeXPositionKey]) currentViewRelativeX = [event[ViewRelativeXPositionKey] floatValue]; if (event[ViewRelativeYPositionKey]) currentViewRelativeY = [event[ViewRelativeYPositionKey] floatValue]; double deltaX = 0; double deltaY = 0; if (event[DeltaXKey]) deltaX = [event[DeltaXKey] floatValue]; if (event[DeltaYKey]) deltaY = [event[DeltaYKey] floatValue]; auto windowPoint = [webView convertPoint:CGPointMake(currentViewRelativeX, [webView frame].size.height - currentViewRelativeY) toView:nil]; [eventSender sendScrollEventAt:windowPoint deltaX:deltaX deltaY:deltaY units:kCGScrollEventUnitPixel wheelPhase:phase momentumPhase:momentumPhase timestamp:currentTime]; } currentTime += nanosecondsEventInterval; } WorkQueue::main().dispatch([this, strongThis = Ref { *this }, callbackID] { if (!m_context) return; m_context->asyncTaskComplete(callbackID); }); } } // namespace WTR #endif // PLATFORM(MAC)