1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/applescript/tab_applescript.h" 6 7 #include "base/bind.h" 8 #include "base/files/file_path.h" 9 #include "base/logging.h" 10 #import "base/mac/scoped_nsobject.h" 11 #include "base/strings/sys_string_conversions.h" 12 #include "chrome/browser/printing/print_view_manager.h" 13 #include "chrome/browser/sessions/session_id.h" 14 #include "chrome/browser/sessions/session_tab_helper.h" 15 #include "chrome/browser/ui/cocoa/applescript/apple_event_util.h" 16 #include "chrome/browser/ui/cocoa/applescript/error_applescript.h" 17 #include "chrome/common/url_constants.h" 18 #include "content/public/browser/navigation_controller.h" 19 #include "content/public/browser/navigation_entry.h" 20 #include "content/public/browser/render_view_host.h" 21 #include "content/public/browser/save_page_type.h" 22 #include "content/public/browser/web_contents.h" 23 #include "content/public/browser/web_contents_delegate.h" 24 #include "url/gurl.h" 25 26 using content::NavigationController; 27 using content::NavigationEntry; 28 using content::OpenURLParams; 29 using content::RenderViewHost; 30 using content::Referrer; 31 using content::WebContents; 32 33 namespace { 34 35 void ResumeAppleEventAndSendReply(NSAppleEventManagerSuspensionID suspension_id, 36 const base::Value* result_value) { 37 NSAppleEventDescriptor* result_descriptor = 38 chrome::mac::ValueToAppleEventDescriptor(result_value); 39 40 NSAppleEventManager* manager = [NSAppleEventManager sharedAppleEventManager]; 41 NSAppleEventDescriptor* reply_event = 42 [manager replyAppleEventForSuspensionID:suspension_id]; 43 [reply_event setParamDescriptor:result_descriptor 44 forKeyword:keyDirectObject]; 45 [manager resumeWithSuspensionID:suspension_id]; 46 } 47 48 } // namespace 49 50 @interface TabAppleScript() 51 @property (nonatomic, copy) NSString* tempURL; 52 @end 53 54 @implementation TabAppleScript 55 56 @synthesize tempURL = tempURL_; 57 58 - (id)init { 59 if ((self = [super init])) { 60 SessionID session; 61 SessionID::id_type futureSessionIDOfTab = session.id() + 1; 62 // Holds the SessionID that the new tab is going to get. 63 base::scoped_nsobject<NSNumber> numID( 64 [[NSNumber alloc] initWithInt:futureSessionIDOfTab]); 65 [self setUniqueID:numID]; 66 } 67 return self; 68 } 69 70 - (void)dealloc { 71 [tempURL_ release]; 72 [super dealloc]; 73 } 74 75 - (id)initWithWebContents:(content::WebContents*)webContents { 76 if (!webContents) { 77 [self release]; 78 return nil; 79 } 80 81 if ((self = [super init])) { 82 // It is safe to be weak; if a tab goes away (e.g. the user closes a tab) 83 // the AppleScript runtime calls tabs in AppleScriptWindow and this 84 // particular tab is never returned. 85 webContents_ = webContents; 86 SessionTabHelper* session_tab_helper = 87 SessionTabHelper::FromWebContents(webContents); 88 base::scoped_nsobject<NSNumber> numID( 89 [[NSNumber alloc] initWithInt:session_tab_helper->session_id().id()]); 90 [self setUniqueID:numID]; 91 } 92 return self; 93 } 94 95 - (void)setWebContents:(content::WebContents*)webContents { 96 DCHECK(webContents); 97 // It is safe to be weak; if a tab goes away (e.g. the user closes a tab) 98 // the AppleScript runtime calls tabs in AppleScriptWindow and this 99 // particular tab is never returned. 100 webContents_ = webContents; 101 SessionTabHelper* session_tab_helper = 102 SessionTabHelper::FromWebContents(webContents); 103 base::scoped_nsobject<NSNumber> numID( 104 [[NSNumber alloc] initWithInt:session_tab_helper->session_id().id()]); 105 [self setUniqueID:numID]; 106 107 if ([self tempURL]) 108 [self setURL:[self tempURL]]; 109 } 110 111 - (NSString*)URL { 112 if (!webContents_) { 113 return nil; 114 } 115 116 NavigationEntry* entry = webContents_->GetController().GetActiveEntry(); 117 if (!entry) { 118 return nil; 119 } 120 const GURL& url = entry->GetVirtualURL(); 121 return base::SysUTF8ToNSString(url.spec()); 122 } 123 124 - (void)setURL:(NSString*)aURL { 125 // If a scripter sets a URL before the node is added save it at a temporary 126 // location. 127 if (!webContents_) { 128 [self setTempURL:aURL]; 129 return; 130 } 131 132 GURL url(base::SysNSStringToUTF8(aURL)); 133 // check for valid url. 134 if (!url.is_empty() && !url.is_valid()) { 135 AppleScript::SetError(AppleScript::errInvalidURL); 136 return; 137 } 138 139 NavigationEntry* entry = webContents_->GetController().GetActiveEntry(); 140 if (!entry) 141 return; 142 143 const GURL& previousURL = entry->GetVirtualURL(); 144 webContents_->OpenURL(OpenURLParams( 145 url, 146 content::Referrer(previousURL, blink::WebReferrerPolicyDefault), 147 CURRENT_TAB, 148 content::PAGE_TRANSITION_TYPED, 149 false)); 150 } 151 152 - (NSString*)title { 153 NavigationEntry* entry = webContents_->GetController().GetActiveEntry(); 154 if (!entry) 155 return nil; 156 157 base::string16 title = entry ? entry->GetTitle() : base::string16(); 158 return base::SysUTF16ToNSString(title); 159 } 160 161 - (NSNumber*)loading { 162 BOOL loadingValue = webContents_->IsLoading() ? YES : NO; 163 return [NSNumber numberWithBool:loadingValue]; 164 } 165 166 - (void)handlesUndoScriptCommand:(NSScriptCommand*)command { 167 RenderViewHost* view = webContents_->GetRenderViewHost(); 168 if (!view) { 169 NOTREACHED(); 170 return; 171 } 172 173 view->Undo(); 174 } 175 176 - (void)handlesRedoScriptCommand:(NSScriptCommand*)command { 177 RenderViewHost* view = webContents_->GetRenderViewHost(); 178 if (!view) { 179 NOTREACHED(); 180 return; 181 } 182 183 view->Redo(); 184 } 185 186 - (void)handlesCutScriptCommand:(NSScriptCommand*)command { 187 RenderViewHost* view = webContents_->GetRenderViewHost(); 188 if (!view) { 189 NOTREACHED(); 190 return; 191 } 192 193 view->Cut(); 194 } 195 196 - (void)handlesCopyScriptCommand:(NSScriptCommand*)command { 197 RenderViewHost* view = webContents_->GetRenderViewHost(); 198 if (!view) { 199 NOTREACHED(); 200 return; 201 } 202 203 view->Copy(); 204 } 205 206 - (void)handlesPasteScriptCommand:(NSScriptCommand*)command { 207 RenderViewHost* view = webContents_->GetRenderViewHost(); 208 if (!view) { 209 NOTREACHED(); 210 return; 211 } 212 213 view->Paste(); 214 } 215 216 - (void)handlesSelectAllScriptCommand:(NSScriptCommand*)command { 217 RenderViewHost* view = webContents_->GetRenderViewHost(); 218 if (!view) { 219 NOTREACHED(); 220 return; 221 } 222 223 view->SelectAll(); 224 } 225 226 - (void)handlesGoBackScriptCommand:(NSScriptCommand*)command { 227 NavigationController& navigationController = webContents_->GetController(); 228 if (navigationController.CanGoBack()) 229 navigationController.GoBack(); 230 } 231 232 - (void)handlesGoForwardScriptCommand:(NSScriptCommand*)command { 233 NavigationController& navigationController = webContents_->GetController(); 234 if (navigationController.CanGoForward()) 235 navigationController.GoForward(); 236 } 237 238 - (void)handlesReloadScriptCommand:(NSScriptCommand*)command { 239 NavigationController& navigationController = webContents_->GetController(); 240 const bool checkForRepost = true; 241 navigationController.Reload(checkForRepost); 242 } 243 244 - (void)handlesStopScriptCommand:(NSScriptCommand*)command { 245 RenderViewHost* view = webContents_->GetRenderViewHost(); 246 if (!view) { 247 // We tolerate Stop being called even before a view has been created. 248 // So just log a warning instead of a NOTREACHED(). 249 DLOG(WARNING) << "Stop: no view for handle "; 250 return; 251 } 252 253 view->Stop(); 254 } 255 256 - (void)handlesPrintScriptCommand:(NSScriptCommand*)command { 257 bool initiated = 258 printing::PrintViewManager::FromWebContents(webContents_)->PrintNow(); 259 if (!initiated) { 260 AppleScript::SetError(AppleScript::errInitiatePrinting); 261 } 262 } 263 264 - (void)handlesSaveScriptCommand:(NSScriptCommand*)command { 265 NSDictionary* dictionary = [command evaluatedArguments]; 266 267 NSURL* fileURL = [dictionary objectForKey:@"File"]; 268 // Scripter has not specifed the location at which to save, so we prompt for 269 // it. 270 if (!fileURL) { 271 webContents_->OnSavePage(); 272 return; 273 } 274 275 base::FilePath mainFile(base::SysNSStringToUTF8([fileURL path])); 276 // We create a directory path at the folder within which the file exists. 277 // Eg. if main_file = '/Users/Foo/Documents/Google.html' 278 // then directory_path = '/Users/Foo/Documents/Google_files/'. 279 base::FilePath directoryPath = mainFile.RemoveExtension(); 280 directoryPath = directoryPath.InsertBeforeExtension(std::string("_files/")); 281 282 NSString* saveType = [dictionary objectForKey:@"FileType"]; 283 284 content::SavePageType savePageType = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; 285 if (saveType) { 286 if ([saveType isEqualToString:@"only html"]) { 287 savePageType = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; 288 } else if ([saveType isEqualToString:@"complete html"]) { 289 savePageType = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; 290 } else { 291 AppleScript::SetError(AppleScript::errInvalidSaveType); 292 return; 293 } 294 } 295 296 webContents_->SavePage(mainFile, directoryPath, savePageType); 297 } 298 299 - (void)handlesCloseScriptCommand:(NSScriptCommand*)command { 300 webContents_->GetDelegate()->CloseContents(webContents_); 301 } 302 303 - (void)handlesViewSourceScriptCommand:(NSScriptCommand*)command { 304 NavigationEntry* entry = 305 webContents_->GetController().GetLastCommittedEntry(); 306 if (entry) { 307 webContents_->OpenURL( 308 OpenURLParams(GURL(content::kViewSourceScheme + std::string(":") + 309 entry->GetURL().spec()), 310 Referrer(), 311 NEW_FOREGROUND_TAB, 312 content::PAGE_TRANSITION_LINK, 313 false)); 314 } 315 } 316 317 - (id)handlesExecuteJavascriptScriptCommand:(NSScriptCommand*)command { 318 RenderViewHost* view = webContents_->GetRenderViewHost(); 319 if (!view) { 320 NOTREACHED(); 321 return nil; 322 } 323 324 NSAppleEventManager* manager = [NSAppleEventManager sharedAppleEventManager]; 325 NSAppleEventManagerSuspensionID suspensionID = 326 [manager suspendCurrentAppleEvent]; 327 content::RenderViewHost::JavascriptResultCallback callback = 328 base::Bind(&ResumeAppleEventAndSendReply, suspensionID); 329 330 base::string16 script = base::SysNSStringToUTF16( 331 [[command evaluatedArguments] objectForKey:@"javascript"]); 332 view->ExecuteJavascriptInWebFrameCallbackResult( 333 base::string16(), // frame_xpath 334 script, 335 callback); 336 337 return nil; 338 } 339 340 @end 341