| // 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/window_applescript.h" |
| |
| #include <memory> |
| |
| #import "base/apple/foundation_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/time/time.h" |
| #import "chrome/browser/app_controller_mac.h" |
| #import "chrome/browser/chrome_browser_application_mac.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| #include "chrome/browser/ui/browser_navigator_params.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/cocoa/applescript/constants_applescript.h" |
| #include "chrome/browser/ui/cocoa/applescript/error_applescript.h" |
| #import "chrome/browser/ui/cocoa/applescript/tab_applescript.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" |
| #include "chrome/browser/ui/tab_contents/core_tab_helper.h" |
| #include "chrome/browser/ui/tabs/tab_enums.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_user_gesture_details.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/public/browser/web_contents.h" |
| |
| @interface WindowAppleScript () |
| |
| // The NSWindow that corresponds to this window. |
| @property(readonly) NSWindow* nativeHandle; |
| |
| @end |
| |
| @implementation WindowAppleScript { |
| // 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<Browser> _browser; |
| } |
| |
| - (instancetype)init { |
| // Check which mode to open a new window. |
| NSScriptCommand* command = [NSScriptCommand currentCommand]; |
| NSString* mode = command.evaluatedArguments[@"KeyDictionary"][@"mode"]; |
| |
| Profile* lastProfile = AppController.sharedController.lastProfile; |
| if (!lastProfile) { |
| AppleScript::SetError(AppleScript::Error::kGetProfile); |
| return nil; |
| } else { |
| // Ensure that the profile is a non-OTR profile, so that it's possible to |
| // create a non-OTR window, below. |
| lastProfile = lastProfile->GetOriginalProfile(); |
| } |
| |
| Profile* profile; |
| if ([mode isEqualToString:AppleScript::kIncognitoWindowMode]) { |
| profile = lastProfile->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| } else if ([mode isEqualToString:AppleScript::kNormalWindowMode] || !mode) { |
| profile = lastProfile; |
| } else { |
| // Mode cannot be anything else. |
| AppleScript::SetError(AppleScript::Error::kInvalidMode); |
| return nil; |
| } |
| // Set the mode to nil, to ensure that it is not set once more. |
| [command.evaluatedArguments[@"KeyDictionary"] setValue:nil forKey:@"mode"]; |
| return [self initWithProfile:profile]; |
| } |
| |
| - (instancetype)initWithProfile:(Profile*)aProfile { |
| if (!aProfile) { |
| self = nil; |
| return nil; |
| } |
| |
| if ((self = [super init])) { |
| // Since AppleScript requests can arrive at any time, including during |
| // browser shutdown or profile deletion, we have to check whether it's okay |
| // to spawn a new browser for the specified profile or not. |
| if (Browser::GetCreationStatusForProfile(aProfile) != |
| Browser::CreationStatus::kOk) { |
| self = nil; |
| return nil; |
| } |
| |
| Browser* browser = Browser::Create( |
| Browser::CreateParams(aProfile, /*user_gesture=*/false)); |
| chrome::NewTab(browser); |
| browser->window()->Show(); |
| |
| _browser = browser->AsWeakPtr(); |
| self.uniqueID = |
| [NSString stringWithFormat:@"%d", _browser->session_id().id()]; |
| } |
| return self; |
| } |
| |
| - (instancetype)initWithBrowser:(Browser*)browser { |
| if (!browser) { |
| self = nil; |
| return nil; |
| } |
| |
| if ((self = [super init])) { |
| // It is safe to be weak, if a window goes away (eg user closing a window) |
| // the AppleScript runtime calls appleScriptWindows in |
| // BrowserCrApplication and this particular window is never returned. |
| _browser = browser->AsWeakPtr(); |
| self.uniqueID = |
| [NSString stringWithFormat:@"%d", _browser->session_id().id()]; |
| } |
| return self; |
| } |
| |
| - (NSWindow*)nativeHandle { |
| if (!_browser) { |
| return nil; |
| } |
| |
| // window() can be null during startup. |
| if (_browser->window()) { |
| return _browser->window()->GetNativeWindow().GetNativeNSWindow(); |
| } |
| return nil; |
| } |
| |
| - (NSNumber*)activeTabIndex { |
| if (!_browser) { |
| return nil; |
| } |
| |
| // Note: AppleScript is 1-based, that is lists begin with index 1. |
| int activeTabIndex = _browser->tab_strip_model()->active_index() + 1; |
| if (!activeTabIndex) { |
| return nil; |
| } |
| return @(activeTabIndex); |
| } |
| |
| - (void)setActiveTabIndex:(NSNumber*)anActiveTabIndex { |
| if (!_browser) { |
| return; |
| } |
| |
| // Note: AppleScript is 1-based, that is lists begin with index 1. |
| int atIndex = anActiveTabIndex.intValue - 1; |
| if (atIndex >= 0 && atIndex < _browser->tab_strip_model()->count()) { |
| _browser->tab_strip_model()->ActivateTabAt( |
| atIndex, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| } else { |
| AppleScript::SetError(AppleScript::Error::kInvalidTabIndex); |
| } |
| } |
| |
| - (NSString*)givenName { |
| if (!_browser) { |
| return nil; |
| } |
| |
| return base::SysUTF8ToNSString(_browser->user_title()); |
| } |
| |
| - (void)setGivenName:(NSString*)name { |
| if (!_browser) { |
| return; |
| } |
| |
| _browser->SetWindowUserTitle(base::SysNSStringToUTF8(name)); |
| } |
| |
| - (NSString*)mode { |
| if (!_browser) { |
| return nil; |
| } |
| |
| Profile* profile = _browser->profile(); |
| if (profile->IsOffTheRecord()) { |
| return AppleScript::kIncognitoWindowMode; |
| } |
| return AppleScript::kNormalWindowMode; |
| } |
| |
| - (void)setMode:(NSString*)theMode { |
| // Cannot set mode after window is created. |
| if (theMode) { |
| AppleScript::SetError(AppleScript::Error::kSetMode); |
| } |
| } |
| |
| - (TabAppleScript*)activeTab { |
| if (!_browser) { |
| return nil; |
| } |
| |
| TabAppleScript* currentTab = [[TabAppleScript alloc] |
| initWithWebContents:_browser->tab_strip_model()->GetActiveWebContents()]; |
| [currentTab setContainer:self property:AppleScript::kTabsProperty]; |
| return currentTab; |
| } |
| |
| - (NSArray<TabAppleScript*>*)tabs { |
| if (!_browser) { |
| return nil; |
| } |
| |
| TabStripModel* tabStrip = _browser->tab_strip_model(); |
| NSMutableArray* tabs = [NSMutableArray arrayWithCapacity:tabStrip->count()]; |
| |
| for (int i = 0; i < tabStrip->count(); ++i) { |
| // Check to see if tab is closing. |
| content::WebContents* webContents = tabStrip->GetWebContentsAt(i); |
| if (webContents->IsBeingDestroyed()) { |
| continue; |
| } |
| |
| TabAppleScript* tab = |
| [[TabAppleScript alloc] initWithWebContents:webContents]; |
| [tab setContainer:self |
| property:AppleScript::kTabsProperty]; |
| [tabs addObject:tab]; |
| } |
| return tabs; |
| } |
| |
| - (void)insertInTabs:(TabAppleScript*)aTab { |
| if (!_browser) { |
| return; |
| } |
| |
| // This method gets called when a new tab is created so |
| // the container and property are set here. |
| [aTab setContainer:self property:AppleScript::kTabsProperty]; |
| |
| // Set how long it takes a tab to be created. |
| base::TimeTicks newTabStartTime = base::TimeTicks::Now(); |
| content::WebContents* contents = chrome::AddSelectedTabWithURL( |
| _browser.get(), GURL(chrome::kChromeUINewTabURL), |
| ui::PAGE_TRANSITION_TYPED); |
| CoreTabHelper* core_tab_helper = CoreTabHelper::FromWebContents(contents); |
| core_tab_helper->set_new_tab_start_time(newTabStartTime); |
| [aTab setWebContents:contents]; |
| } |
| |
| - (void)insertInTabs:(TabAppleScript*)aTab atIndex:(int)index { |
| if (!_browser) { |
| return; |
| } |
| |
| // This method gets called when a new tab is created so |
| // the container and property are set here. |
| [aTab setContainer:self property:AppleScript::kTabsProperty]; |
| |
| // Set how long it takes a tab to be created. |
| base::TimeTicks newTabStartTime = base::TimeTicks::Now(); |
| NavigateParams params(_browser.get(), GURL(chrome::kChromeUINewTabURL), |
| ui::PAGE_TRANSITION_TYPED); |
| params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB; |
| params.tabstrip_index = index; |
| Navigate(¶ms); |
| CoreTabHelper* core_tab_helper = |
| CoreTabHelper::FromWebContents(params.navigated_or_inserted_contents); |
| core_tab_helper->set_new_tab_start_time(newTabStartTime); |
| |
| [aTab setWebContents:params.navigated_or_inserted_contents]; |
| } |
| |
| - (void)removeFromTabsAtIndex:(int)index { |
| if (!_browser) { |
| return; |
| } |
| |
| if (index < 0 || index >= _browser->tab_strip_model()->count()) { |
| return; |
| } |
| _browser->tab_strip_model()->CloseWebContentsAt( |
| index, TabCloseTypes::CLOSE_CREATE_HISTORICAL_TAB); |
| } |
| |
| - (NSNumber*)orderedIndex { |
| return @(self.nativeHandle.orderedIndex); |
| } |
| |
| - (void)setOrderedIndex:(NSNumber*)anIndex { |
| int index = anIndex.intValue - 1; |
| if (index < 0 || index >= static_cast<int>(chrome::GetTotalBrowserCount())) { |
| AppleScript::SetError(AppleScript::Error::kWrongIndex); |
| return; |
| } |
| self.nativeHandle.orderedIndex = index; |
| } |
| |
| // Get and set values from the associated NSWindow. |
| - (id)valueForUndefinedKey:(NSString*)key { |
| return [self.nativeHandle valueForKey:key]; |
| } |
| |
| - (void)setValue:(id)value forUndefinedKey:(NSString*)key { |
| [self.nativeHandle setValue:value forKey:key]; |
| } |
| |
| - (void)handlesCloseScriptCommand:(NSCloseCommand*)command { |
| if (!_browser) { |
| return; |
| } |
| |
| // window() can be null during startup. |
| if (_browser->window()) { |
| _browser->window()->Close(); |
| } |
| } |
| |
| @end |