/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla XUL Toolkit. * * The Initial Developer of the Original Code is * the Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010-2019 * the Initial Developer. All Rights Reserved. * * Based on original works by Scott Greenlay (bug 608049). * Ported to TenFourFox and 10.4 SDK by Cameron Kaiser * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #import #import extern "C" { IMP class_lookupMethod(Class, SEL); }; #define class_getMethodImplementation(x,y) class_lookupMethod(x,y) #import "MacScripting.h" #include "nsIApplescriptService.h" #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" #include "nsArrayUtils.h" #include "nsString.h" #include "nsContentCID.h" #include "nsNetUtil.h" #include "nsIServiceManager.h" #include "nsServiceManagerUtils.h" #include "nsIAppStartup.h" #include "nsISelection.h" #include "nsIDocShell.h" #include "nsIDOMNode.h" #include "nsIDOMDocument.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMLocation.h" #include "nsIDOMSerializer.h" #include "nsIDocument.h" #include "nsIDocumentEncoder.h" #include "nsIWindowMediator.h" #include "nsISimpleEnumerator.h" #include "nsIBaseWindow.h" #include "nsIWidget.h" #include "nsIXULWindow.h" #include "nsIURI.h" #include "nsIWebNavigation.h" #include "nsIDOMWindow.h" #include "nsPIDOMWindow.h" #include "nsIDOMWindowUtils.h" #include "nsIInterfaceRequestor.h" #include "nsIDOMLocation.h" #include "nsIPresShell.h" #include "nsObjCExceptions.h" #include "nsToolkitCompsCID.h" #include "nsContentUtils.h" class nsLocation; // 10.4 no haz. typedef int NSInteger; typedef unsigned int NSUInteger; #define NSIntegerMax LONG_MAX #define NSIntegerMin LONG_MIN #define NSUIntegerMax ULONG_MAX @class GeckoObject; @class GeckoWindow; @class GeckoTab; #pragma mark - @interface GeckoScriptingRoot : NSObject { @private // These must persist for the life of the scripting application. struct objc_method swinMeth; struct objc_method insoMeth; struct objc_method insiMeth; struct objc_method remoMeth; struct objc_method openMeth; struct objc_method_list methodList; BOOL didInit; NSUInteger openExpected; } + (GeckoScriptingRoot*)sharedScriptingRoot; - (id)init; - (void)makeApplicationScriptable:(NSApplication*)application; - (void)insertObject:(NSObject*)object inScriptWindowsAtIndex:(NSUInteger)index; - (NSArray*)scriptWindows; - (void)insertInScriptWindows:(id)value; @end #pragma mark - @interface GeckoWindow : NSObject { NSUInteger mIndex; nsCOMPtr mXULWindow; } - (id)initWithIndex:(NSUInteger)index andXULWindow:(nsIXULWindow*)xulWindow; + (id)windowWithIndex:(NSUInteger)index andXULWindow:(nsIXULWindow*)xulWindow; // Default Scripting Dictionary - (NSString*)title; - (NSUInteger)orderedIndex; - (BOOL)isMiniaturizable; - (BOOL)isMiniaturized; - (void)setIsMiniaturized:(BOOL)miniaturized; - (BOOL)isResizable; - (BOOL)isVisible; - (void)setIsVisible:(BOOL)visible; - (BOOL)isZoomable; - (BOOL)isZoomed; - (void)setIsZoomed:(BOOL)zoomed; - (id)handleCloseScriptCommand:(NSCloseCommand*)command; // Gecko Scripting Dictionary - (NSArray*)scriptTabs; - (void)insertInScriptTabs:(id)value; - (GeckoTab*)selectedScriptTab; // Helper Methods - (void)_setIndex:(NSUInteger)index; @end #pragma mark - @interface GeckoTab : NSObject { NSUInteger mIndex; GeckoWindow *mWindow; nsCOMPtr mContentWindow; } - (id)initWithIndex:(NSUInteger)index andContentWindow:(nsIDOMWindow*)contentWindow andWindow:(GeckoWindow*)window; + (id)tabWithIndex:(NSUInteger)index andContentWindow:(nsIDOMWindow*)contentWindow andWindow:(GeckoWindow*)window; // Gecko Scripting Dictionary - (NSString*)title; - (NSUInteger)orderedIndex; - (NSString*)URL; - (NSString*)source; - (NSString*)text; - (NSString*)selectedText; - (void)setURL:(NSString*)newURL; - (id)handleCloseScriptCommand:(NSCloseCommand*)command; // Helper Methods - (void)_setWindow:(GeckoWindow*)window; - (void)_setIndex:(NSUInteger)index; @end #pragma mark - @interface GeckoQuit : NSScriptCommand { } @end #pragma mark - void SetupMacScripting(void) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK; [[GeckoScriptingRoot sharedScriptingRoot] makeApplicationScriptable:[NSApplication sharedApplication]]; NS_OBJC_END_TRY_ABORT_BLOCK; } #pragma mark - static GeckoScriptingRoot *sharedScriptingRoot = nil; @implementation GeckoScriptingRoot + (GeckoScriptingRoot*)sharedScriptingRoot { @synchronized (sharedScriptingRoot) { if (!sharedScriptingRoot) { sharedScriptingRoot = [[GeckoScriptingRoot alloc] init]; } } return sharedScriptingRoot; } - (id)init { self = [super init]; if (self) didInit = NO; openExpected = 0; return self; } - (void)makeApplicationScriptable:(NSApplication*)application { if (didInit) return; NS_WARNING("starting Script Host"); IMP scriptWindows = class_getMethodImplementation([self class], @selector(scriptWindows)); // class_addMethod([application class], @selector(scriptWindows), scriptWindows, "@@:"); IMP insertScriptWindowsAI = class_getMethodImplementation([self class], @selector(insertObject:inScriptWindowsAtIndex:)); // class_addMethod([application class], @selector(insertObject:inScriptWindowsAtIndex:), insertScriptWindowsAI, "v@:@I"); IMP insertScriptWindows = class_getMethodImplementation([self class], @selector(insertInScriptWindows:)); // class_addMethod([application class], @selector(insertInScriptWindows:), insertScriptWindows, "v@:@"); IMP removeScriptWindows = class_getMethodImplementation([self class], @selector(removeObjectFromScriptWindowsAtIndex:)); // class_addMethod([application class], @selector(removeObjectFromScriptWindowsAtIndex:), removeScriptWindows, "v@:I"); IMP openingP = class_getMethodImplementation([self class], @selector(opening)); // class_addMethod([application class], @selector(opening), openingP, "c@:"); // The 10.4 SDK doesn't have class_addMethod, but it does have class_addMethods. swinMeth.method_name = @selector(scriptWindows); swinMeth.method_imp = scriptWindows; swinMeth.method_types = "@@:"; insoMeth.method_name = @selector(insertObject:inScriptWindowsAtIndex:); insoMeth.method_imp = insertScriptWindowsAI; insoMeth.method_types = "v@:@l"; insiMeth.method_name = @selector(insertInScriptWindows:); insiMeth.method_imp = insertScriptWindows; insiMeth.method_types = "v@:@"; remoMeth.method_name = @selector(removeObjectFromScriptWindowsAtIndex:); remoMeth.method_imp = removeScriptWindows; remoMeth.method_types = "v@:l"; openMeth.method_name = @selector(opening); openMeth.method_imp = openingP; openMeth.method_types = "c@:"; methodList.method_count = 5; methodList.method_list[0] = swinMeth; methodList.method_list[1] = insoMeth; methodList.method_list[2] = insiMeth; methodList.method_list[3] = remoMeth; methodList.method_list[4] = openMeth; class_addMethods([application class], &methodList); didInit = YES; } - (NSArray*)scriptWindows { NS_WARNING("AppleScript: root scriptWindows"); nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (!applescriptService) { return [NSArray arrayWithObjects:nil]; } nsCOMPtr windows; if (NS_FAILED(applescriptService->GetWindows(getter_AddRefs(windows))) || !windows) { return [NSArray arrayWithObjects:nil]; } NSUInteger index = 0; NSMutableArray *windowArray = [NSMutableArray array]; uint32_t length; windows->GetLength(&length); for (uint32_t i = 0; i < length; ++i) { nsCOMPtr xulWindow(do_QueryElementAt(windows, i)); if (xulWindow) { GeckoWindow *window = [GeckoWindow windowWithIndex:index andXULWindow:xulWindow]; if (window) { [windowArray addObject:window]; index++; } } } return windowArray; } - (void)insertObject:(NSObject*)object inScriptWindowsAtIndex:(NSUInteger)index { NS_WARNING("AppleScript: root insertObject inScriptWindowsAtIndex"); if (![object isKindOfClass:[GeckoWindow class]]) { return; } nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (applescriptService) { (void)applescriptService->CreateWindowAtIndex(index); } openExpected = [[self scriptWindows] count] + 1; [(GeckoWindow*)object _setIndex:index]; } - (void)insertInScriptWindows:(id)value { NS_WARNING("AppleScript: root insertInScriptWindows"); if (![(NSObject*)value isKindOfClass:[GeckoWindow class]]) return; // The ordering works rather oddly for windows. The frontmost window is // zero, so for things like set w to make new browser window / tell w, we // have to insert at index 0 instead of at the end like we do for tabs or // we start talking to the wrong window and things show up in the wrong // place. In practise it seems the index really works more like Z-order. // We get away with this because the indices get rebuilt by scriptWindows: // off the actual XUL indexes; we don't really have multiple index 0s // because there isn't a persistent array of GeckoWindows. // // For some reason [self insertObject:inScriptWindowsAtIndex:] doesn't // work properly, and trying to access it through the singleton // sharedScriptingRoot causes memory failures, so we just copy the // logic here. On 10.4, [s iO:iSWAI:] doesn't even seem to be called. nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (applescriptService) { (void)applescriptService->CreateWindowAtIndex(0); } openExpected = [[self scriptWindows] count] + 1; [(GeckoWindow*)value _setIndex:0]; } - (void)removeObjectFromScriptWindowsAtIndex:(NSUInteger)index { NSArray *windows = [self scriptWindows]; if (windows && index < [windows count]) { NSCloseCommand *closeCommend = [[[NSCloseCommand alloc] init] autorelease]; [(GeckoWindow*)[windows objectAtIndex:index] handleCloseScriptCommand:closeCommend]; } } - (BOOL)opening { NS_WARNING("AppleScript: root opening"); // XXX: We don't decrement openExpected for closed windows since it will // be resynchronized after any new window operation. Should we? Is it useful // to call this method at any other time than after opening? return [[self scriptWindows] count] < openExpected; } @end #pragma mark - @implementation GeckoWindow + (id)windowWithIndex:(NSUInteger)index andXULWindow:(nsIXULWindow*)xulWindow { return [[[self alloc] initWithIndex:index andXULWindow:xulWindow] autorelease]; } - (id)initWithIndex:(NSUInteger)index andXULWindow:(nsIXULWindow*)xulWindow { self = [super init]; if (self) { mIndex = index; mXULWindow = xulWindow; } return self; } - (void)dealloc { [super dealloc]; } - (void)_setIndex:(NSUInteger)index { mIndex = index; } - (id)uniqueID { return [NSNumber numberWithInt:mIndex]; } - (NSScriptObjectSpecifier*)objectSpecifier { NSScriptObjectSpecifier *objectSpecifier = [[NSUniqueIDSpecifier alloc] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[NSApp class]] containerSpecifier:[NSApp objectSpecifier] key:@"scriptWindows" uniqueID:[self uniqueID]]; return [objectSpecifier autorelease]; } - (NSWindow*)window { nsresult rv; nsCOMPtr baseWindow = do_QueryInterface(mXULWindow, &rv); NS_ENSURE_SUCCESS(rv, nil); nsCOMPtr widget; rv = baseWindow->GetMainWidget(getter_AddRefs(widget)); NS_ENSURE_SUCCESS(rv, nil); return (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); } - (NSString*)title { NS_WARNING("AppleScript: window title"); NSWindow *window = [self window]; return window ? [window title] : @""; } - (NSUInteger)orderedIndex { return mIndex; } - (BOOL)isMiniaturizable { NSWindow *window = [self window]; return window ? [window isMiniaturizable] : false; } - (BOOL)isMiniaturized { NSWindow *window = [self window]; return window ? [window isMiniaturizable] : false; } - (void)setIsMiniaturized:(BOOL)miniaturized { NSWindow *window = [self window]; if (window) { [window setIsMiniaturized:miniaturized]; } } - (BOOL)isResizable { NSWindow *window = [self window]; return window ? [window isResizable] : false; } - (BOOL)isVisible { NSWindow *window = [self window]; return window ? [window isVisible] : false; } - (void)setIsVisible:(BOOL)visible { NSWindow *window = [self window]; if (window) { [window setIsVisible:visible]; } } - (BOOL)isZoomable { NSWindow *window = [self window]; return window ? [window isZoomable] : false; } - (BOOL)isZoomed { NSWindow *window = [self window]; return window ? [window isZoomed] : false; } - (void)setIsZoomed:(BOOL)zoomed { NSWindow *window = [self window]; if (window) { [window setIsZoomed:zoomed]; } } - (id)handleCloseScriptCommand:(NSCloseCommand*)command { NSWindow *window = [self window]; if (window) { return [window handleCloseScriptCommand:command]; } return nil; } - (NSArray*)scriptTabs { NS_WARNING("AppleScript: window scriptTabs"); NSScriptCommand* c = [NSScriptCommand currentCommand]; if (!mXULWindow) { // A newly created but not instantiated window the user held a reference to, // e.g., |set w to make new browser window| and then trying to |tell w|. // XXX: Right now, this is an error. if (c) { [c setScriptErrorNumber:-10003]; // errAENotModifiable [c setScriptErrorString:@"Parameter Error: Don't keep references to new windows."]; } return [NSArray arrayWithObjects:nil]; } nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (!applescriptService) { return [NSArray arrayWithObjects:nil]; } nsCOMPtr tabs; if (NS_FAILED(applescriptService->GetTabsInWindow(mIndex, getter_AddRefs(tabs))) || !tabs) { return [NSArray arrayWithObjects:nil]; } NSUInteger index = 0; NSMutableArray *tabArray = [NSMutableArray array]; uint32_t length; tabs->GetLength(&length); for (uint32_t i = 0; i < length; ++i) { nsCOMPtr contentWindow(do_QueryElementAt(tabs, i)); if (contentWindow) { GeckoTab *tab = [GeckoTab tabWithIndex:index andContentWindow:contentWindow andWindow:self]; [tabArray addObject:tab]; index++; } } return tabArray; } - (GeckoTab*)selectedScriptTab { NS_WARNING("AppleScript: window selectedScriptTab"); nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (!applescriptService) { return nil; } nsCOMPtr contentWindow; uint32_t tabIndex = 0; if (NS_FAILED(applescriptService->GetCurrentTabInWindow(mIndex, &tabIndex, getter_AddRefs(contentWindow))) || !contentWindow) { return nil; } return [GeckoTab tabWithIndex:tabIndex andContentWindow:contentWindow andWindow:self]; } - (void)insertObject:(NSObject*)object inScriptTabsAtIndex:(NSUInteger)index { NS_WARNING("AppleScript: window insertObject:inScriptTabsAtIndex"); if (![object isKindOfClass:[GeckoTab class]]) { return; } nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (applescriptService) { (void)applescriptService->CreateTabAtIndexInWindow(index, mIndex); } [(GeckoTab*)object _setWindow:self]; [(GeckoTab*)object _setIndex:index]; } - (void)insertInScriptTabs:(id)value { NS_WARNING("AppleScript: window insertInScriptTabs"); if (![(NSObject*)value isKindOfClass:[GeckoTab class]]) return; [self insertObject:value inScriptTabsAtIndex:[[self scriptTabs] count]]; } - (void)removeObjectFromScriptTabsAtIndex:(NSUInteger)index { NS_WARNING("AppleScript: window removeObjectFromScriptTabsAtIndex"); NSArray *tabs = [self scriptTabs]; if (tabs && index < [tabs count]) { NSCloseCommand *closeCommend = [[[NSCloseCommand alloc] init] autorelease]; [(GeckoTab*)[tabs objectAtIndex:index] handleCloseScriptCommand:closeCommend]; } } @end #pragma mark - @implementation GeckoTab + (id)tabWithIndex:(NSUInteger)index andContentWindow:(nsIDOMWindow*)contentWindow andWindow:(GeckoWindow*)window { return [[[self alloc] initWithIndex:index andContentWindow:contentWindow andWindow:window] autorelease]; } - (id)init { /* AppleScript may call directly. */ self = [super init]; return self; } - (id)initWithIndex:(NSUInteger)index andContentWindow:(nsIDOMWindow*)contentWindow andWindow:(GeckoWindow*)window { self = [super init]; if (self) { mIndex = index; mWindow = [window retain]; mContentWindow = contentWindow; } return self; } - (void)dealloc { [mWindow release]; [super dealloc]; } - (void)_setWindow:(GeckoWindow*)window { if (mWindow) { [mWindow release]; } mWindow = nil; if (window) { mWindow = [window retain]; } } - (void)_setIndex:(NSUInteger)index { mIndex = index; } - (NSScriptObjectSpecifier*)objectSpecifier { if (!mWindow) { return nil; } NSScriptObjectSpecifier *objectSpecifier = [[NSIndexSpecifier alloc] initWithContainerClassDescription:[NSScriptClassDescription classDescriptionForClass:[mWindow class]] containerSpecifier:[mWindow objectSpecifier] key:@"scriptTabs" index:[self orderedIndex]]; return [objectSpecifier autorelease]; } - (NSString*)title { NS_WARNING("AppleScript: tab title"); nsCOMPtr piWindow = do_QueryInterface(mContentWindow); if (!piWindow) return @""; nsCOMPtr pdoc = piWindow->GetDoc(); if (!pdoc) return @""; nsCOMPtr p = pdoc->GetShell(); if (!p) return @""; nsIDocument* doc = p->GetDocument(); if (doc) { nsCOMPtr htmlDocument(do_QueryInterface(doc)); if (htmlDocument) { nsAutoString title; if (NS_SUCCEEDED(htmlDocument->GetTitle(title))) { return [NSString stringWithUTF8String:NS_ConvertUTF16toUTF8(title).get()]; } } } return @""; } - (NSString*)URL { NS_WARNING("AppleScript: tab URL"); nsCOMPtr piWindow = do_QueryInterface(mContentWindow); if (!piWindow) return @""; nsCOMPtr pdoc = piWindow->GetDoc(); if (!pdoc) return @""; nsCOMPtr u = pdoc->GetDocumentURI(); if (u) { nsAutoCString url; u->GetAsciiSpec(url); return [NSString stringWithUTF8String:url.get()]; } return @""; } - (BOOL)busy { NS_WARNING("AppleScript: tab busy"); // This is deliberately inexact and is designed to mostly tell the caller when // it's okay to start working with the tab's properties. nsCOMPtr piWindow = do_QueryInterface(mContentWindow); if (!piWindow) return YES; // Gecko is still creating this tab. nsCOMPtr d = piWindow->GetDocShell(); if (!d) return YES; // Ditto. uint32_t bz; if (NS_FAILED(d->GetBusyFlags(&bz))) return YES; // Hmmm. return (bz == nsIDocShell::BUSY_FLAGS_NONE) ? NO : YES; } - (void)setURL:(NSString*)newURL { NS_WARNING("AppleScript: tab newURL"); nsAutoCString geckoURL; nsIURI* uri; NSScriptCommand* c = [NSScriptCommand currentCommand]; nsCOMPtr d; geckoURL.Assign([newURL UTF8String]); if (NS_FAILED(NS_NewURI(&uri, geckoURL, nullptr, nullptr, nsContentUtils::GetIOService()))) { if (c) { [c setScriptErrorNumber:-1700]; // errAECoercionFail [c setScriptErrorString:@"Parameter Error: URL is not valid."]; } return; } nsCOMPtr piWindow = do_QueryInterface(mContentWindow); if (piWindow) d = piWindow->GetDocShell(); if (!d) { // XXX: This usually happens when we try to open a new tab with properties. // Gecko's asynchronous nature doesn't work well with this, so right now // we just return a fatal error so people learn right away this won't fly. // The busy property would tell the caller if we're ready. if (c) { [c setScriptErrorNumber:-10003]; // errAENotModifiable [c setScriptErrorString:@"Parameter Error: The tab is busy. Don't use properties when creating a tab. Check busy first."]; } return; } d->LoadURI(uri, nullptr, nsIWebNavigation::LOAD_FLAGS_NONE, true); } - (NSString*)source { #if(0) nsCOMPtr document; if (NS_SUCCEEDED(mContentWindow->GetDocument(getter_AddRefs(document))) && document) { nsCOMPtr serializer(do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID)); if (serializer) { nsAutoString source; if (NS_SUCCEEDED(serializer->SerializeToString(document, source))) { return [NSString stringWithUTF8String:NS_ConvertUTF16toUTF8(source).get()]; } } } #endif return @""; } - (NSString*)text { #if(0) nsCOMPtr document; if (NS_SUCCEEDED(mContentWindow->GetDocument(getter_AddRefs(document))) && document) { nsresult rv = NS_OK; nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE); formatType.Append("text/plain"); nsCOMPtr encoder(do_CreateInstance(formatType.get(), &rv)); if (NS_SUCCEEDED(rv) && encoder) { uint32_t flags = nsIDocumentEncoder::SkipInvisibleContent; nsAutoString readstring; readstring.AssignASCII("text/plain"); if (NS_SUCCEEDED(encoder->Init(document, readstring, flags))) { nsAutoString text; if (NS_SUCCEEDED(encoder->EncodeToString(text))) { return [NSString stringWithUTF8String:NS_ConvertUTF16toUTF8(text).get()]; } } } } #endif return @""; } - (NSString*)selectedText { #if(0) nsCOMPtr selection; if (NS_SUCCEEDED(mContentWindow->GetSelection(getter_AddRefs(selection))) && selection) { nsXPIDLString selectedTextChars; if (NS_SUCCEEDED(selection->ToString(getter_Copies(selectedTextChars)))) { nsAutoString selectedText(selectedTextChars); return [NSString stringWithUTF8String:NS_ConvertUTF16toUTF8(selectedText).get()]; } } #endif return @""; } - (NSUInteger)orderedIndex { return mIndex; } - (id)handleCloseScriptCommand:(NSCloseCommand*)command { nsCOMPtr applescriptService(do_GetService("@mozilla.org/applescript-service;1")); if (applescriptService) { (void)applescriptService->CloseTabAtIndexInWindow(mIndex, [mWindow orderedIndex]); } return nil; } @end #pragma mark - @implementation GeckoQuit - (id)performDefaultImplementation { NS_WARNING("AppleScript: quit"); nsCOMPtr appStartup = do_GetService(NS_APPSTARTUP_CONTRACTID); if (appStartup) { appStartup->Quit(nsIAppStartup::eAttemptQuit); } return nil; } @end