chromium/chrome/browser/ui/cocoa/applescript/apple_event_util.mm

// Copyright 2013 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/apple_event_util.h"

#import <Carbon/Carbon.h>
#include <stddef.h>

#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"

namespace chrome::mac {

namespace {

void AppendValueToListDescriptor(NSAppleEventDescriptor* list,
                                 const base::Value& value) {
  // Note that index 0 means "append to end of list"; see the docs for
  // -[NSAppleEventDescriptor insertDescriptor:atIndex:] and ultimately for
  // AEPutDesc().
  [list insertDescriptor:ValueToAppleEventDescriptor(value) atIndex:0];
}

NSAppleEventDescriptor* RecordDescriptorForKeyValuePairs(
    NSAppleEventDescriptor* list) {
  // /!\ Extreme undocumented subtlety ahead /!\
  //
  // AERecords have the restriction that their keys are `FourChar` codes
  // (e.g. keyAS* values from ASRegistry.h). This does not mesh well with
  // Cocoa, where keys in `NSDictionary` objects are full objects (though
  // usually strings).
  //
  // Therefore, the AppleScript team developed a special way to wrap an
  // NSDictionary in an AERecord. NSDictionary objects are wrapped by
  // creating an an AERecord has a single key of keyASUserRecordFields
  // that has a value of an AEList. That list must have alternating
  // keys (required to be string type) and values.
  //
  // As Value dictionaries have string-typed keys, they are perfect for
  // this wrapping, so wrap them in this manner too. See
  //   https://lists.apple.com/archives/cocoa-dev/2009/Jul/msg01216.html
  // for more details.
  NSAppleEventDescriptor* descriptor =
      [NSAppleEventDescriptor recordDescriptor];
  [descriptor setDescriptor:list forKeyword:keyASUserRecordFields];
  return descriptor;
}

}  // namespace

NSAppleEventDescriptor* ValueToAppleEventDescriptor(const base::Value& value) {
  NSAppleEventDescriptor* descriptor = nil;

  switch (value.type()) {
    case base::Value::Type::NONE:
      descriptor = [NSAppleEventDescriptor
          descriptorWithTypeCode:cMissingValue];
      break;

    case base::Value::Type::BOOLEAN: {
      descriptor =
          [NSAppleEventDescriptor descriptorWithBoolean:value.GetBool()];
      break;
    }

    case base::Value::Type::INTEGER: {
      descriptor = [NSAppleEventDescriptor descriptorWithInt32:value.GetInt()];
      break;
    }

    case base::Value::Type::DOUBLE: {
      double double_value = value.GetDouble();
      descriptor = [NSAppleEventDescriptor
          descriptorWithDescriptorType:typeIEEE64BitFloatingPoint
                                 bytes:&double_value
                                length:sizeof(double_value)];
      break;
    }

    case base::Value::Type::STRING: {
      descriptor = [NSAppleEventDescriptor
          descriptorWithString:base::SysUTF8ToNSString(value.GetString())];
      break;
    }

    case base::Value::Type::BINARY:
      NOTREACHED_IN_MIGRATION();
      break;

    case base::Value::Type::DICT: {
      NSAppleEventDescriptor* keyValuePairs =
          [NSAppleEventDescriptor listDescriptor];
      for (auto iter : value.GetDict()) {
        AppendValueToListDescriptor(keyValuePairs, base::Value(iter.first));
        AppendValueToListDescriptor(keyValuePairs, iter.second);
      }
      descriptor = RecordDescriptorForKeyValuePairs(keyValuePairs);
      break;
    }

    case base::Value::Type::LIST: {
      descriptor = [NSAppleEventDescriptor listDescriptor];
      for (const auto& item : value.GetList()) {
        AppendValueToListDescriptor(descriptor, item);
      }
      break;
    }
  }

  return descriptor;
}

bool IsJavaScriptEnabledForProfile(Profile* profile) {
  DCHECK(profile);
  PrefService* prefs = profile->GetPrefs();
  return prefs->GetBoolean(prefs::kAllowJavascriptAppleEvents);
}

}  // namespace chrome::mac