| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/ui/cocoa/applescript/tab_applescript.h" |
| |
| #include "base/apple/foundation_util.h" |
| #include "base/check.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "chrome/browser/printing/print_view_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/cocoa/applescript/apple_event_util.h" |
| #include "chrome/browser/ui/cocoa/applescript/error_applescript.h" |
| #include "chrome/common/chrome_isolated_world_ids.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/sessions/core/session_id.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/save_page_type.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "url/gurl.h" |
| |
| using content::NavigationController; |
| using content::NavigationEntry; |
| using content::OpenURLParams; |
| using content::RenderFrameHost; |
| using content::RenderViewHost; |
| using content::Referrer; |
| using content::WebContents; |
| |
| namespace { |
| |
| void ResumeAppleEventAndSendReply(NSAppleEventManagerSuspensionID suspension_id, |
| base::Value result_value) { |
| NSAppleEventDescriptor* result_descriptor = |
| chrome::mac::ValueToAppleEventDescriptor(result_value); |
| |
| NSAppleEventManager* manager = [NSAppleEventManager sharedAppleEventManager]; |
| NSAppleEventDescriptor* reply_event = |
| [manager replyAppleEventForSuspensionID:suspension_id]; |
| [reply_event setParamDescriptor:result_descriptor |
| forKeyword:keyDirectObject]; |
| [manager resumeWithSuspensionID:suspension_id]; |
| } |
| |
| } // namespace |
| |
| @interface TabAppleScript () |
| |
| // Contains the temporary URL when a user creates a new folder/item with the URL |
| // specified like: |
| // |
| // make new tab with properties {URL:"http://google.com"} |
| @property (nonatomic, copy) NSString* tempURL; |
| |
| - (bool)isJavaScriptEnabled; |
| |
| @end |
| |
| @implementation TabAppleScript { |
| // A note about lifetimes: It's not expected that this object will ever be |
| // deleted behind the back of this class. AppleScript does not hold onto |
| // objects between script runs; it will retain the object specifier, and if |
| // needed again, AppleScript will re-iterate over the objects, and look for |
| // the specified object. However, there's no hard guarantee that a race |
| // couldn't be made to happen, and in tests things are torn down at odd times, |
| // so it's best to use a real weak pointer. |
| base::WeakPtr<content::WebContents> _webContents; |
| } |
| |
| @synthesize tempURL = _tempURL; |
| |
| - (bool)isJavaScriptEnabled { |
| if (!_webContents) { |
| return false; |
| } |
| |
| return chrome::mac::IsJavaScriptEnabledForProfile( |
| Profile::FromBrowserContext(_webContents->GetBrowserContext())); |
| } |
| |
| - (instancetype)init { |
| if ((self = [super init])) { |
| // Holds the SessionID that the new tab is going to get. |
| SessionID::id_type futureSessionIDOfTab = SessionID::NewUnique().id() + 1; |
| self.uniqueID = [NSString stringWithFormat:@"%d", futureSessionIDOfTab]; |
| } |
| return self; |
| } |
| |
| - (instancetype)initWithWebContents:(content::WebContents*)webContents { |
| if (!webContents) { |
| return nil; |
| } |
| |
| if ((self = [super init])) { |
| _webContents = webContents->GetWeakPtr(); |
| |
| sessions::SessionTabHelper* session_tab_helper = |
| sessions::SessionTabHelper::FromWebContents(webContents); |
| self.uniqueID = [NSString |
| stringWithFormat:@"%d", session_tab_helper->session_id().id()]; |
| } |
| return self; |
| } |
| |
| - (void)setWebContents:(content::WebContents*)webContents { |
| DCHECK(webContents); |
| _webContents = webContents->GetWeakPtr(); |
| |
| sessions::SessionTabHelper* session_tab_helper = |
| sessions::SessionTabHelper::FromWebContents(webContents); |
| self.uniqueID = |
| [NSString stringWithFormat:@"%d", session_tab_helper->session_id().id()]; |
| |
| if (self.tempURL) { |
| self.URL = self.tempURL; |
| } |
| } |
| |
| - (NSString*)URL { |
| if (!_webContents) { |
| return nil; |
| } |
| |
| NavigationEntry* entry = _webContents->GetController().GetActiveEntry(); |
| if (!entry) { |
| return nil; |
| } |
| const GURL& url = entry->GetVirtualURL(); |
| return base::SysUTF8ToNSString(url.spec()); |
| } |
| |
| - (void)setURL:(NSString*)url { |
| // If a scripter sets a URL before |webContents_| or |profile_| is set, save |
| // it at a temporary location. Once they're set, -setURL: will be call again |
| // with the temporary URL. |
| if (!_webContents) { |
| self.tempURL = url; |
| return; |
| } |
| |
| GURL gurl(base::SysNSStringToUTF8(url)); |
| if (![self isJavaScriptEnabled] && gurl.SchemeIs(url::kJavaScriptScheme)) { |
| AppleScript::SetError(AppleScript::Error::kJavaScriptUnsupported); |
| return; |
| } |
| |
| // Check if the URL is valid; if not, then attempting to open it will trip |
| // a fatal check in navigation. |
| if (!gurl.is_valid()) { |
| AppleScript::SetError(AppleScript::Error::kInvalidURL); |
| return; |
| } |
| |
| NavigationEntry* entry = _webContents->GetController().GetActiveEntry(); |
| if (!entry) { |
| return; |
| } |
| |
| _webContents->OpenURL(OpenURLParams(gurl, content::Referrer(), |
| WindowOpenDisposition::CURRENT_TAB, |
| ui::PAGE_TRANSITION_TYPED, false)); |
| } |
| |
| - (NSString*)title { |
| if (!_webContents) { |
| return nil; |
| } |
| |
| NavigationEntry* entry = _webContents->GetController().GetActiveEntry(); |
| if (!entry) { |
| return nil; |
| } |
| |
| std::u16string title = entry ? entry->GetTitle() : std::u16string(); |
| return base::SysUTF16ToNSString(title); |
| } |
| |
| - (NSNumber*)loading { |
| if (!_webContents) { |
| return nil; |
| } |
| |
| BOOL loadingValue = _webContents->IsLoading() ? YES : NO; |
| return @(loadingValue); |
| } |
| |
| - (void)handlesUndoScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Undo(); |
| } |
| |
| - (void)handlesRedoScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Redo(); |
| } |
| |
| - (void)handlesCutScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Cut(); |
| } |
| |
| - (void)handlesCopyScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Copy(); |
| } |
| |
| - (void)handlesPasteScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Paste(); |
| } |
| |
| - (void)handlesSelectAllScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->SelectAll(); |
| } |
| |
| - (void)handlesGoBackScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| NavigationController& navigationController = _webContents->GetController(); |
| if (navigationController.CanGoBack()) { |
| navigationController.GoBack(); |
| } |
| } |
| |
| - (void)handlesGoForwardScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| NavigationController& navigationController = _webContents->GetController(); |
| if (navigationController.CanGoForward()) { |
| navigationController.GoForward(); |
| } |
| } |
| |
| - (void)handlesReloadScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| NavigationController& navigationController = _webContents->GetController(); |
| navigationController.Reload(content::ReloadType::NORMAL, |
| /*check_for_repost=*/true); |
| } |
| |
| - (void)handlesStopScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->Stop(); |
| } |
| |
| - (void)handlesPrintScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| bool initiated = |
| printing::PrintViewManager::FromWebContents(_webContents.get()) |
| ->PrintNow(_webContents->GetPrimaryMainFrame()); |
| if (!initiated) { |
| AppleScript::SetError(AppleScript::Error::kInitiatePrinting); |
| } |
| } |
| |
| - (void)handlesSaveScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| NSDictionary* dictionary = command.evaluatedArguments; |
| |
| NSURL* fileURL = dictionary[@"File"]; |
| // Scripter has not specified the location at which to save, so we prompt for |
| // it. |
| if (!fileURL) { |
| _webContents->OnSavePage(); |
| return; |
| } |
| |
| base::FilePath mainFile = base::apple::NSURLToFilePath(fileURL); |
| // We create a directory path at the folder within which the file exists. |
| // Eg. if main_file = '/Users/Foo/Documents/Google.html' |
| // then directory_path = '/Users/Foo/Documents/Google_files/'. |
| base::FilePath directoryPath = mainFile.RemoveExtension(); |
| directoryPath = directoryPath.InsertBeforeExtension(std::string("_files/")); |
| |
| NSString* saveType = dictionary[@"FileType"]; |
| |
| content::SavePageType savePageType = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; |
| if (saveType) { |
| if ([saveType isEqualToString:@"only html"]) { |
| savePageType = content::SAVE_PAGE_TYPE_AS_ONLY_HTML; |
| } else if ([saveType isEqualToString:@"complete html"]) { |
| savePageType = content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML; |
| } else { |
| AppleScript::SetError(AppleScript::Error::kInvalidSaveType); |
| return; |
| } |
| } |
| |
| _webContents->SavePage(mainFile, directoryPath, savePageType); |
| } |
| |
| - (void)handlesCloseScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->GetDelegate()->CloseContents(_webContents.get()); |
| } |
| |
| - (void)handlesViewSourceScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return; |
| } |
| |
| _webContents->GetPrimaryMainFrame()->ViewSource(); |
| } |
| |
| - (id)handlesExecuteJavascriptScriptCommand:(NSScriptCommand*)command { |
| if (!_webContents) { |
| return nil; |
| } |
| |
| if (![self isJavaScriptEnabled]) { |
| AppleScript::SetError(AppleScript::Error::kJavaScriptUnsupported); |
| return nil; |
| } |
| |
| content::RenderFrameHost* frame = _webContents->GetPrimaryMainFrame(); |
| if (!frame) { |
| return nil; |
| } |
| |
| NSAppleEventManager* manager = [NSAppleEventManager sharedAppleEventManager]; |
| NSAppleEventManagerSuspensionID suspensionID = |
| [manager suspendCurrentAppleEvent]; |
| content::RenderFrameHost::JavaScriptResultCallback callback = |
| base::BindOnce(&ResumeAppleEventAndSendReply, suspensionID); |
| |
| std::u16string script = |
| base::SysNSStringToUTF16(command.evaluatedArguments[@"javascript"]); |
| frame->ExecuteJavaScriptInIsolatedWorld(script, std::move(callback), |
| ISOLATED_WORLD_ID_APPLESCRIPT); |
| |
| return nil; |
| } |
| |
| @end |