blob: d4c9283b3cef95ec8333e2fd2bce0c122b2c26e1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// 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/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#import "base/mac/scoped_nsobject.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/sessions/session_tab_helper.h"
#include "chrome/browser/ui/cocoa/applescript/apple_event_util.h"
#include "chrome/browser/ui/cocoa/applescript/error_applescript.h"
#include "chrome/browser/ui/cocoa/applescript/metrics_applescript.h"
#include "chrome/common/chrome_isolated_world_ids.h"
#include "chrome/common/url_constants.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()
@property (nonatomic, copy) NSString* tempURL;
@end
@implementation TabAppleScript
@synthesize tempURL = tempURL_;
- (instancetype)init {
if ((self = [super init])) {
SessionID::id_type futureSessionIDOfTab = SessionID::NewUnique().id() + 1;
// Holds the SessionID that the new tab is going to get.
base::scoped_nsobject<NSNumber> numID(
[[NSNumber alloc] initWithInt:futureSessionIDOfTab]);
[self setUniqueID:numID];
}
return self;
}
- (void)dealloc {
[tempURL_ release];
[super dealloc];
}
- (instancetype)initWithWebContents:(content::WebContents*)webContents {
if (!webContents) {
[self release];
return nil;
}
if ((self = [super init])) {
// It is safe to be weak; if a tab goes away (e.g. the user closes a tab)
// the AppleScript runtime calls tabs in AppleScriptWindow and this
// particular tab is never returned.
webContents_ = webContents;
profile_ = Profile::FromBrowserContext(webContents->GetBrowserContext());
SessionTabHelper* session_tab_helper =
SessionTabHelper::FromWebContents(webContents);
base::scoped_nsobject<NSNumber> numID(
[[NSNumber alloc] initWithInt:session_tab_helper->session_id().id()]);
[self setUniqueID:numID];
}
return self;
}
- (void)setWebContents:(content::WebContents*)webContents {
DCHECK(webContents);
// It is safe to be weak; if a tab goes away (e.g. the user closes a tab)
// the AppleScript runtime calls tabs in AppleScriptWindow and this
// particular tab is never returned.
webContents_ = webContents;
SessionTabHelper* session_tab_helper =
SessionTabHelper::FromWebContents(webContents);
profile_ = Profile::FromBrowserContext(webContents->GetBrowserContext());
base::scoped_nsobject<NSNumber> numID(
[[NSNumber alloc] initWithInt:session_tab_helper->session_id().id()]);
[self setUniqueID:numID];
if ([self tempURL])
[self setURL:[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*)aURL {
// 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 (!profile_ || !webContents_) {
[self setTempURL:aURL];
return;
}
GURL url(base::SysNSStringToUTF8(aURL));
if (!chrome::mac::IsJavaScriptEnabledForProfile(profile_) &&
url.SchemeIs(url::kJavaScriptScheme)) {
AppleScript::SetError(AppleScript::errJavaScriptUnsupported);
return;
}
// check for valid url.
if (!url.is_empty() && !url.is_valid()) {
AppleScript::SetError(AppleScript::errInvalidURL);
return;
}
NavigationEntry* entry = webContents_->GetController().GetActiveEntry();
if (!entry)
return;
const GURL& previousURL = entry->GetVirtualURL();
webContents_->OpenURL(OpenURLParams(
url,
content::Referrer(previousURL, network::mojom::ReferrerPolicy::kDefault),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
}
- (NSString*)title {
NavigationEntry* entry = webContents_->GetController().GetActiveEntry();
if (!entry)
return nil;
base::string16 title = entry ? entry->GetTitle() : base::string16();
return base::SysUTF16ToNSString(title);
}
- (NSNumber*)loading {
BOOL loadingValue = webContents_->IsLoading() ? YES : NO;
return [NSNumber numberWithBool:loadingValue];
}
- (void)handlesUndoScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_UNDO);
webContents_->Undo();
}
- (void)handlesRedoScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_REDO);
webContents_->Redo();
}
- (void)handlesCutScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_CUT);
webContents_->Cut();
}
- (void)handlesCopyScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_COPY);
webContents_->Copy();
}
- (void)handlesPasteScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_PASTE);
webContents_->Paste();
}
- (void)handlesSelectAllScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(
AppleScript::AppleScriptCommand::TAB_SELECT_ALL);
webContents_->SelectAll();
}
- (void)handlesGoBackScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_GO_BACK);
NavigationController& navigationController = webContents_->GetController();
if (navigationController.CanGoBack())
navigationController.GoBack();
}
- (void)handlesGoForwardScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(
AppleScript::AppleScriptCommand::TAB_GO_FORWARD);
NavigationController& navigationController = webContents_->GetController();
if (navigationController.CanGoForward())
navigationController.GoForward();
}
- (void)handlesReloadScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_RELOAD);
NavigationController& navigationController = webContents_->GetController();
const bool checkForRepost = true;
navigationController.Reload(content::ReloadType::NORMAL, checkForRepost);
}
- (void)handlesStopScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_STOP);
webContents_->Stop();
}
- (void)handlesPrintScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_PRINT);
bool initiated = printing::PrintViewManager::FromWebContents(webContents_)
->PrintNow(webContents_->GetMainFrame());
if (!initiated) {
AppleScript::SetError(AppleScript::errInitiatePrinting);
}
}
- (void)handlesSaveScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_SAVE);
NSDictionary* dictionary = [command evaluatedArguments];
NSURL* fileURL = [dictionary objectForKey:@"File"];
// Scripter has not specifed the location at which to save, so we prompt for
// it.
if (!fileURL) {
webContents_->OnSavePage();
return;
}
base::FilePath mainFile(base::SysNSStringToUTF8([fileURL path]));
// 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 objectForKey:@"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::errInvalidSaveType);
return;
}
}
webContents_->SavePage(mainFile, directoryPath, savePageType);
}
- (void)handlesCloseScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(AppleScript::AppleScriptCommand::TAB_CLOSE);
webContents_->GetDelegate()->CloseContents(webContents_);
}
- (void)handlesViewSourceScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(
AppleScript::AppleScriptCommand::TAB_VIEW_SOURCE);
NavigationEntry* entry =
webContents_->GetController().GetLastCommittedEntry();
if (entry) {
webContents_->OpenURL(
OpenURLParams(GURL(content::kViewSourceScheme + std::string(":") +
entry->GetURL().spec()),
Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false));
}
}
- (id)handlesExecuteJavascriptScriptCommand:(NSScriptCommand*)command {
AppleScript::LogAppleScriptUMA(
AppleScript::AppleScriptCommand::TAB_EXECUTE_JAVASCRIPT);
if (!chrome::mac::IsJavaScriptEnabledForProfile(profile_)) {
AppleScript::SetError(AppleScript::errJavaScriptUnsupported);
return nil;
}
content::RenderFrameHost* frame = webContents_->GetMainFrame();
if (!frame) {
NOTREACHED();
return nil;
}
NSAppleEventManager* manager = [NSAppleEventManager sharedAppleEventManager];
NSAppleEventManagerSuspensionID suspensionID =
[manager suspendCurrentAppleEvent];
content::RenderFrameHost::JavaScriptResultCallback callback =
base::BindOnce(&ResumeAppleEventAndSendReply, suspensionID);
base::string16 script = base::SysNSStringToUTF16(
[[command evaluatedArguments] objectForKey:@"javascript"]);
frame->ExecuteJavaScriptInIsolatedWorld(script, std::move(callback),
ISOLATED_WORLD_ID_APPLESCRIPT);
return nil;
}
@end