// Copyright (c) 2014 Jefry Tedjokusumo <jtg_gg@yahoo.com.sg>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co
// pies of the Software, and to permit persons to whom the Software is furnished
//  to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in al
// l copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM
// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES
// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
//  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH
// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#ifndef CONTENT_NW_NOTIFICATION_MANAGER_TOAST_WIN_H_
#define CONTENT_NW_NOTIFICATION_MANAGER_TOAST_WIN_H_

#include "content/nw/src/nw_notification_manager.h"
#include <windows.ui.notifications.h>
#include <wrl\client.h>

namespace nw {
  using namespace Microsoft::WRL;
  using namespace ABI::Windows::UI::Notifications;
  using namespace ABI::Windows::Data::Xml::Dom;

class NotificationManagerToastWin : public NotificationManager {
  friend class ToastEventHandler;

  ComPtr<IToastNotificationManagerStatics> toastStatics_;
  ComPtr<IToastNotifier> notifier_;
  std::map<int, ComPtr<IToastNotification>> notification_map_;
  static bool ForceDisable;

  HRESULT CreateToast(_In_ IToastNotificationManagerStatics *toastManager, _In_ IXmlDocument *xml,
    const int render_process_id, const int notification_id);

  // Create the toast XML from a template
  HRESULT CreateToastXml(_In_ IToastNotificationManagerStatics *toastManager,
    const content::PlatformNotificationData& params, const SkBitmap& icon, _Outptr_ IXmlDocument** inputXml);

  // Set the value of the "src" attribute of the "image" node
  HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml);

  // Set the values of each of the text nodes
  HRESULT SetTextValues(_In_reads_(textValuesCount) const wchar_t **textValues, _In_ UINT32 textValuesCount,
    _In_reads_(textValuesCount) UINT32 *textValuesLengths, _In_ IXmlDocument *toastXml);

  HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml);

  bool Init();

public:
  static bool IsSupported();
  explicit NotificationManagerToastWin();
  virtual ~NotificationManagerToastWin();
  virtual bool AddDesktopNotification(const content::PlatformNotificationData& params,
    const int render_process_id, const int notification_id, const SkBitmap& icon) override;
  virtual bool CancelDesktopNotification(int notification_id) override;
};

} // namespace nw

#endif // CONTENT_NW_NOTIFICATION_MANAGER_WIN_H_


keep keep keep keep add keep keep keep keep keep

#!/usr/bin/env python
import argparse
import getnwisrelease
import getnwversion
import gzip
import os
import platform
import shutil
import sys
import tarfile
import zipfile
from hashlib import sha256

from subprocess import call

steps = ['nw', 'symbol', 'headers', 'others']
################################
# Parse command line args
parser = argparse.ArgumentParser(description='Package nw binaries.')
parser.add_argument('-p','--path', help='Where to find the binaries, like out/Release', required=False)
parser.add_argument('-a','--arch', help='target arch', required=False)
parser.add_argument('-m','--mode', help='package mode', required=False)
parser.add_argument('-i','--icudat', help='icudat override', required=False)
group = parser.add_mutually_exclusive_group()
group.add_argument('-s','--step', choices=steps, help='Execute specified step.', required=False)
group.add_argument('-n','--skip', choices=steps, help='Skip specified step.', required=False)
args = parser.parse_args()

################################
# Init variables.
binaries_location = None        # .../out/Release
platform_name = None            # win/linux/osx
arch = None                     # ia32/x64
step = None                     # nw/chromedriver/symbol
skip = None
nw_ver = None                   # x.xx
dist_dir = None                 # .../out/Release/dist
flavor = args.mode
is_headers_ok = False           # record whether nw-headers generated
package_name = 'nwjs'

if flavor in ['sdk', 'nacl']:
    package_name = 'nwjs-' + args.mode

step = args.step
skip = args.skip
binaries_location = args.path
# If the binaries location is not given, calculate it from script related dir.
if binaries_location == None:
    binaries_location = os.path.join(os.path.dirname(__file__),
            os.pardir, os.pardir, os.pardir, 'out', 'Release')

if not os.path.isabs(binaries_location):
    binaries_location = os.path.join(os.getcwd(), binaries_location)

if not os.path.isdir(binaries_location):
    print 'Invalid path: ' + binaries_location
    exit(-1)
binaries_location = os.path.normpath(binaries_location)
dist_dir = os.path.join(binaries_location, 'dist')

print 'Working on ' + binaries_location

nwfolder = os.path.join(dist_dir, '..', 'nwdist')
try:
    shutil.rmtree(nwfolder)
except:
    pass

if args.icudat != None:
    #FIXME: for some reason they are the same file (hard link) and copy will fail
    os.remove(os.path.join(binaries_location, 'icudtl.dat'))
    shutil.copy(args.icudat, binaries_location)

if sys.platform.startswith('linux'):
    platform_name = 'linux'
elif sys.platform in ('win32', 'cygwin'):
    platform_name = 'win'
elif sys.platform == 'darwin':
    platform_name = 'osx'
else:
    print 'Unsupported platform: ' + sys.platform
    exit(-1)

_arch = platform.architecture()[0]
if _arch == '64bit':
    arch = 'x64'
elif _arch == '32bit':
    arch = 'ia32'
else:
    print 'Unsupported arch: ' + _arch
    exit(-1)

if platform_name == 'win':
    libfile = os.path.join(binaries_location, 'nw.lib')
    expfile = os.path.join(binaries_location, 'nw.exp')
    libfile2 = os.path.join(binaries_location, 'node.lib')
    expfile2 = os.path.join(binaries_location, 'node.exp')

    shutil.copy(os.path.join(binaries_location, 'nw.dll.lib'), libfile)
    shutil.copy(os.path.join(binaries_location, 'nw.dll.exp'), expfile)
    shutil.copy(os.path.join(binaries_location, 'node.dll.lib'), libfile2)
    shutil.copy(os.path.join(binaries_location, 'node.dll.exp'), expfile2)

if platform_name == 'win':
    arch = 'ia32'

if platform_name == 'osx':
    # detect output arch
    nw_bin = binaries_location + '/nwjs.app/Contents/MacOS/nwjs'
    import subprocess
    if 'i386' in subprocess.check_output(['file',nw_bin]):
        arch = 'ia32'
    else: # should be 'x86_64'
        arch = 'x64'

if args.arch != None:
    arch = args.arch

nw_ver = getnwversion.nw_version
if getnwisrelease.release == 0:
    nw_ver += getnwisrelease.postfix

################################
# Generate targets
#
# target example:
# {
#    'input'    : [ 'nw', 'nw.pak', ... ]
#    'output'   : 'nwjs-v0.9.2-linux-x64'
#    'compress' : 'tar.gz'
#    'folder'   : True   # Optional. More than 2 files will be put into a seprate folder
#                        # normally, if you want do to this for only 1 file, set this flag.
# }
def generate_target_nw(platform_name, arch, version):
    target = {}
    # Output
    target['output'] = ''.join([
                       package_name, '-',
                       'v', version,
                       '-', platform_name,
                       '-', arch])
    target['keep4test'] = 'nwdist'
    # Compress type
    if platform_name == 'linux':
        target['compress'] = 'tar.gz'
    else:
        target['compress'] = 'zip'
    # Input
    if platform_name == 'linux':
        target['input'] = [
                           'credits.html',
                           'resources.pak',
                           'nw_100_percent.pak',
                           'nw_200_percent.pak',
                           'nw_material_100_percent.pak',
                           'nw_material_200_percent.pak',
                           'nw',
                           'icudtl.dat',
                           'locales',
                           'snapshot_blob.bin',
                           'natives_blob.bin',
                           'lib/libnw.so',
                           'lib/libnode.so',
                           'lib/libffmpeg.so',
                           ]
        if flavor == 'sdk':
            target['input'].append('nwjc')
            target['input'].append('payload')
            target['input'].append('chromedriver')
        if flavor in ['nacl','sdk'] :
            target['input'] += ['nacl_helper', 'nacl_helper_bootstrap', 'pnacl']
            if arch == 'x64':
                target['input'].append('nacl_irt_x86_64.nexe')
            else:
                target['input'].append('nacl_irt_x86_32.nexe')

    elif platform_name == 'win':
        target['input'] = [
                           'snapshot_blob.bin',
                           'natives_blob.bin',
                           'd3dcompiler_47.dll',
                           'libEGL.dll',
                           'libGLESv2.dll',
                           'nw.dll',
                           'node.dll',
                           'nw_elf.dll',
                           'nw.exe',
                           'locales',
                           'icudtl.dat',
                           'credits.html',
                           'resources.pak',
                           'nw_100_percent.pak',
                           'nw_200_percent.pak',
                           'nw_material_100_percent.pak',
                           'nw_material_200_percent.pak',
                           'ffmpeg.dll',
                            # To be removed in CR51
                           ]
        if flavor == 'sdk':
            target['input'].append('nwjc.exe')
            target['input'].append('payload.exe')
            target['input'].append('chromedriver.exe')
        if flavor in ['nacl','sdk'] :
            target['input'].append('pnacl')
            if arch == 'x64':
                target['input'].append('nacl_irt_x86_64.nexe')
            else:
                target['input'].append('nacl_irt_x86_32.nexe')
    elif platform_name == 'osx':
        target['input'] = [
                           'nwjs.app',
                           'credits.html',
                          ]
        if flavor == 'sdk':
            target['input'].append('nwjc')
            target['input'].append('payload')
            target['input'].append('chromedriver')
    else:
        print 'Unsupported platform: ' + platform_name
        exit(-1)
    return target

def generate_target_chromedriver(platform_name, arch, version):
    if args.mode != 'sdk':
        return generate_target_empty(platform_name, arch, version)

    target = {}
    # Output
    target['output'] = ''.join([
                       'chromedriver-nw-',
                       'v', version,
                       '-', platform_name,
                       '-', arch])
    # Compress type
    if platform_name == 'linux':
        target['compress'] = 'tar.gz'
    else:
        target['compress'] = 'zip'
    # Input
    if platform_name == 'win':
        target['input'] = ['chromedriver.exe']
    else:
        target['input'] = ['chromedriver']
    target['folder'] = True # always create a folder
    return target

def generate_target_symbols(platform_name, arch, version):
    target = {}
    target['output'] = ''.join([package_name, '-symbol-',
                                'v', version,
                                '-', platform_name,
                                '-', arch])
    if platform_name == 'linux':
        target['compress'] = 'tar.gz'
        target['input'] = [
            'nw.breakpad.' + arch,
            'node.so.breakpad.' + arch,
            'nw.so.breakpad.' + arch
        ]
        if flavor in ['sdk', 'nacl']:
            target['input'].append('nacl_helper.breakpad.' + arch)

        target['folder'] = True
    elif platform_name == 'win':
        target['compress'] = None
        target['input'] = ['nw.sym.7z']
        target['output'] = ''.join([package_name, '-symbol-',
                                    'v', version,
                                    '-', platform_name,
                                    '-', arch, '.7z'])
    elif platform_name == 'osx':
        target['compress'] = 'zip'
        target['input'] = [
                          'nwjs.breakpad.tar'
                          ]
        target['folder'] = True
    else:
        print 'Unsupported platform: ' + platform_name
        exit(-1)
    return target

def generate_target_headers(platform_name, arch, version):
    # here, call make_nw_header tool to generate headers
    # then, move to binaries_location
    target = {}
    target['output'] = ''
    target['compress'] = None
    if platform_name == 'osx':
        target['input'] = []
        # here , call make-nw-headers.py to generate nw headers
        make_nw_header = os.path.join(os.path.dirname(__file__), \
                'make-nw-headers.py')
        print make_nw_header
        res = call(['python', make_nw_header])
        if res == 0:
            print 'nw-headers generated'
            nw_headers_name = 'nw-headers-v' + version + '.tar.gz'
            nw_headers_path = os.path.join(os.path.dirname(__file__), \
                    os.pardir, 'tmp', nw_headers_name)
            if os.path.isfile(os.path.join(binaries_location, nw_headers_name)):
                os.remove(os.path.join(binaries_location, nw_headers_name))

            f = open(nw_headers_path, 'rb')
            checksum_file = open(os.path.join(binaries_location, 'SHASUMS256.txt'), 'w')
            with f, checksum_file:
                checksum_file.write('%s %s' % (sha256(f.read()).hexdigest(), nw_headers_name))
            shutil.move(nw_headers_path, binaries_location)
            target['input'].append(nw_headers_name)
            target['input'].append('SHASUMS256.txt')
        else:
            #TODO, handle err
            print 'nw-headers generate failed'
    elif platform_name == 'win':
        target['input'] = []
    elif platform_name == 'linux':
        target['input'] = []
    else:
        print 'Unsupported platform: ' + platform_name
        exit(-1)
    return target

def generate_target_empty(platform_name, arch, version):
    target = {}
    target['output'] = ''
    target['compress'] = None
    if platform_name == 'win':
        target['input'] = []
    elif platform_name == 'linux' :
        target['input'] = []
    else:
        target['input'] = []
    return target

def generate_target_others(platform_name, arch, version):
    target = {}
    target['output'] = ''
    target['compress'] = None
    if platform_name == 'win':
        target['input'] = ['nw.exp', 'nw.lib', 'node.exp', 'node.lib']
    elif platform_name == 'linux' :
        target['input'] = []
    else:
        target['input'] = []
    return target

################################
# Make packages
def compress(from_dir, to_dir, fname, compress):
    from_dir = os.path.normpath(from_dir)
    to_dir = os.path.normpath(to_dir)
    _from = os.path.join(from_dir, fname)
    _to = os.path.join(to_dir, fname)
    if compress == 'zip':
        z = zipfile.ZipFile(_to + '.zip', 'w', compression=zipfile.ZIP_DEFLATED)
        if os.path.isdir(_from):
            for root, dirs, files in os.walk(_from):
                for f in files:
                    _path = os.path.join(root, f)
                    z.write(_path, _path.replace(from_dir+os.sep, ''))
        else:
            z.write(_from, fname)
        z.close()
    elif compress == 'tar.gz': # only for folders
        if not os.path.isdir(_from):
            print 'Will not create tar.gz for a single file: ' + _from
            exit(-1)
        with tarfile.open(_to + '.tar.gz', 'w:gz') as tar:
            tar.add(_from, arcname=os.path.basename(_from))
    elif compress == 'gz': # only for single file
        if os.path.isdir(_from):
            print 'Will not create gz for a folder: ' + _from
            exit(-1)
        f_in = open(_from, 'rb')
        f_out = gzip.open(_to + '.gz', 'wb')
        f_out.writelines(f_in)
        f_out.close()
        f_in.close()
    else:
        print 'Unsupported compression format: ' + compress
        exit(-1)

def make_packages(targets):
    # check file existance
    for t in targets:
        for f in t['input']:
            src = os.path.join(binaries_location, f)
            if not os.path.exists(src):
                print 'File does not exist: ', src
                exit(-1)

    # clear the output folder
    if os.path.exists(dist_dir):
        if not os.path.isdir(dist_dir):
            print 'Invalid path: ' + dist_dir
            exit(-1)
        else:
            shutil.rmtree(dist_dir)

    # now let's do it
    os.mkdir(dist_dir)
    for t in targets:
        if len(t['input']) == 0:
            continue
        if t['compress'] == None:
            for f in t['input']:
                src = os.path.join(binaries_location, f)
                if t['output'] != '':
                    dest = os.path.join(dist_dir, t['output'])
                else:
                    dest = os.path.join(dist_dir, f)
                print "Copying " + f
                shutil.copy(src, dest)
        elif (t.has_key('folder') and t['folder'] == True) or len(t['input']) > 1:
            print 'Making "' + t['output'] + '.' + t['compress'] + '"'
            # copy files into a folder then pack
            folder = os.path.join(dist_dir, t['output'])
            os.mkdir(folder)
            if platform_name == 'linux':
                os.mkdir(os.path.join(folder, 'lib'))
            for f in t['input']:
                src = os.path.join(binaries_location, f)
                dest = os.path.join(folder, f)
                if os.path.isdir(src): # like nw.app
                    shutil.copytree(src, dest)
                else:
                    shutil.copy(src, dest)
            compress(dist_dir, dist_dir, t['output'], t['compress'])
            # remove temp folders
            if (t.has_key('keep4test')) :
                shutil.copytree(folder, nwfolder)

            shutil.rmtree(folder)
        else:
            # single file
            print 'Making "' + t['output'] + '.' + t['compress'] + '"'
            compress(binaries_location, dist_dir, t['input'][0], t['compress'])

# must be aligned with steps
generators = {}
generators['nw'] = generate_target_nw
generators['chromedriver'] = generate_target_chromedriver
generators['symbol'] = generate_target_symbols
generators['headers'] = generate_target_headers
generators['others'] = generate_target_others
################################
# Process targets
targets = []
for s in steps:
    if (step != None) and (s != step):
            continue
    if (skip != None) and (s == skip):
            continue
    targets.append(generators[s](platform_name, arch, nw_ver))

print 'Creating packages...'
make_packages(targets)

# vim: et:ts=4:sw=4


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>form post</title>
</head>
<body>
  <button id="start-server" onclick="startServer(9999)">Start Server (9999)</button>
  <form method="POST" action="">
    <input type="text" name="myfield" value="myvalue">
    <button id="submit" type="submit">Submit</button>
  </form>
  <script>
  function out(id, msg) {
    var h1 = document.createElement('h1');
    h1.setAttribute('id', id);
    h1.innerHTML = msg;
    document.body.appendChild(h1);
  }

  function startServer(port) {
    chrome.extension.getBackgroundPage().startServer(port, function() {
      document.querySelector('form').setAttribute('action', 'http://localhost:' + port + '/post.html');
      out('server-started', 'success');
    });
  }
  </script>
</body>
</html>


#include "content/nw/src/api/nw_app_api.h"

#include "base/command_line.h"
#include "chrome/browser/browsing_data/browsing_data_helper.h"
#include "chrome/browser/browsing_data/browsing_data_remover_factory.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/extensions/devtools_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "content/nw/src/nw_base.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/error_utils.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service_fixed.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

namespace {
void SetProxyConfigCallback(
    base::WaitableEvent* done,
    const scoped_refptr<net::URLRequestContextGetter>& url_request_context_getter,
    const net::ProxyConfig& proxy_config) {
  net::ProxyService* proxy_service =
      url_request_context_getter->GetURLRequestContext()->proxy_service();
  proxy_service->ResetConfigService(make_scoped_ptr(new net::ProxyConfigServiceFixed(proxy_config)));
  done->Signal();
}
} // namespace

namespace extensions {
NwAppQuitFunction::NwAppQuitFunction() {

}

NwAppQuitFunction::~NwAppQuitFunction() {
}

bool NwAppQuitFunction::RunAsync() {
  ExtensionService* service =
    ExtensionSystem::Get(browser_context())->extension_service();
  base::MessageLoop::current()->PostTask(
        FROM_HERE,
        base::Bind(&ExtensionService::TerminateExtension,
                   service->AsWeakPtr(),
                   extension_id()));
  return true;
}

bool NwAppCloseAllWindowsFunction::RunAsync() {
  AppWindowRegistry* registry = AppWindowRegistry::Get(browser_context());
  if (!registry)
    return false;

  AppWindowRegistry::AppWindowList windows =
    registry->GetAppWindowsForApp(extension()->id());

  for (AppWindow* window : windows) {
    if (window->NWCanClose())
      window->GetBaseWindow()->Close();
  }
  SendResponse(true);
  return true;
}

NwAppGetArgvSyncFunction::NwAppGetArgvSyncFunction() {
}

NwAppGetArgvSyncFunction::~NwAppGetArgvSyncFunction() {
}

bool NwAppGetArgvSyncFunction::RunNWSync(base::ListValue* response, std::string* error) {

  nw::Package* package = nw::package();
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  base::CommandLine::StringVector args = command_line->GetArgs();
  base::CommandLine::StringVector argv = command_line->original_argv();

  // Ignore first non-switch arg if it's not a standalone package.
  bool ignore_arg = !package->self_extract();
  for (unsigned i = 1; i < argv.size(); ++i) {
    if (ignore_arg && args.size() && argv[i] == args[0]) {
      ignore_arg = false;
      continue;
    }

    response->AppendString(argv[i]);
  }
  return true;
}

NwAppClearCacheFunction::NwAppClearCacheFunction() {
}

NwAppClearCacheFunction::~NwAppClearCacheFunction() {
}

bool NwAppClearCacheFunction::RunNWSync(base::ListValue* response, std::string* error) {
  BrowsingDataRemover* remover = BrowsingDataRemoverFactory::GetForBrowserContext(
      Profile::FromBrowserContext(context_));

  remover->AddObserver(this);
  remover->Remove(BrowsingDataRemover::Unbounded(),
                  BrowsingDataRemover::REMOVE_CACHE,
                  BrowsingDataHelper::ALL);
  // BrowsingDataRemover deletes itself.
  base::MessageLoop::ScopedNestableTaskAllower allow(
        base::MessageLoop::current());
  run_loop_.Run();
  remover->RemoveObserver(this);
  return true;
}

void NwAppClearCacheFunction::OnBrowsingDataRemoverDone() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  run_loop_.Quit();
}

NwAppSetProxyConfigFunction::NwAppSetProxyConfigFunction() {
}

NwAppSetProxyConfigFunction::~NwAppSetProxyConfigFunction() {
}

bool NwAppSetProxyConfigFunction::RunNWSync(base::ListValue* response, std::string* error) {
  std::string proxy_config;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &proxy_config));

  base::ThreadRestrictions::ScopedAllowWait allow_wait;

  net::ProxyConfig config;
  config.proxy_rules().ParseFromString(proxy_config);
  content::RenderProcessHost* render_process_host = GetSenderWebContents()->GetRenderProcessHost();
  net::URLRequestContextGetter* context_getter =
    render_process_host->GetStoragePartition()->GetURLRequestContext();

  base::WaitableEvent done(false, false);
  content::BrowserThread::PostTask(
      content::BrowserThread::IO, FROM_HERE,
      base::Bind(&SetProxyConfigCallback, &done,
                 make_scoped_refptr(context_getter), config));
  done.Wait();
  return true;
}

bool NwAppGetDataPathFunction::RunNWSync(base::ListValue* response, std::string* error) {
  response->AppendString(browser_context()->GetPath().value());
  return true;
}

bool NwAppCrashBrowserFunction::RunAsync() {
  int* ptr = nullptr;
  *ptr = 1;
  return true;
}

} // namespace extensions


// Copyright (c) 2012 Intel Corp
// Copyright (c) 2012 The Chromium Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell co
// pies of the Software, and to permit persons to whom the Software is furnished
//  to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in al
// l copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM
// PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES
// S FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
//  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WH
// ETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
//  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "content/nw/src/api/window/window.h"

#include "base/values.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "content/nw/src/api/dispatcher_host.h"
#include "content/nw/src/api/menu/menu.h"
#include "content/nw/src/browser/native_window.h"
#include "content/nw/src/nw_shell.h"
#include "content/nw/src/shell_browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/common/url_constants.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_util.h"
#include "net/url_request/url_request_context.h"
#include "url/gurl.h"

using content::BrowserThread;
using content::ShellBrowserContext;

namespace {

const char kCauseKey[] = "cause";
const char kCookieKey[] = "cookie";
//const char kDomainKey[] = "domain";
//const char kIdKey[] = "id";
const char kRemovedKey[] = "removed";
//const char kTabIdsKey[] = "tabIds";

// Cause Constants
const char kEvictedChangeCause[] = "evicted";
const char kExpiredChangeCause[] = "expired";
const char kExpiredOverwriteChangeCause[] = "expired_overwrite";
const char kExplicitChangeCause[] = "explicit";
const char kOverwriteChangeCause[] = "overwrite";

GURL GetURLFromCanonicalCookie(const net::CanonicalCookie& cookie) {
  const std::string& domain_key = cookie.Domain();
  const std::string scheme =
    cookie.IsSecure() ? "https" : "http";
  const std::string host =
      domain_key.find('.') != 0 ? domain_key : domain_key.substr(1);
  return GURL(scheme + url::kStandardSchemeSeparator + host + "/");
}

void GetCookieListFromStore(
    net::CookieStore* cookie_store, const GURL& url,
    const net::CookieMonster::GetCookieListCallback& callback) {
  DCHECK(cookie_store);
  net::CookieMonster* monster = cookie_store->GetCookieMonster();
  if (!url.is_empty()) {
    DCHECK(url.is_valid());
    monster->GetAllCookiesForURLAsync(url, callback);
  } else {
    monster->GetAllCookiesAsync(callback);
  }
}

bool MatchesDomain(base::DictionaryValue* details, const std::string& domain) {
  std::string val;
  if (!details->GetString("domain", &val))
    return true;

  // Add a leading '.' character to the filter domain if it doesn't exist.
  if (net::cookie_util::DomainIsHostOnly(val))
    val.insert(0, ".");

  std::string sub_domain(domain);
  // Strip any leading '.' character from the input cookie domain.
  if (!net::cookie_util::DomainIsHostOnly(sub_domain))
    sub_domain = sub_domain.substr(1);

  // Now check whether the domain argument is a subdomain of the filter domain.
  for (sub_domain.insert(0, ".");
       sub_domain.length() >= val.length();) {
    if (sub_domain == val)
      return true;
    const size_t next_dot = sub_domain.find('.', 1);  // Skip over leading dot.
    sub_domain.erase(0, next_dot);
  }
  return false;
}

bool MatchesCookie(base::DictionaryValue* details,
                     const net::CanonicalCookie& cookie) {
  std::string val;

  bool flag;
  if (details->GetString("name", &val))
    if (val != cookie.Name())
      return false;

  if (!MatchesDomain(details, cookie.Domain()))
      return false;

  if (details->GetString("path", &val))
    if (val != cookie.Path())
      return false;

  if (details->GetBoolean("secure", &flag))
    if (flag != cookie.IsSecure())
      return false;

  if (details->GetBoolean("session", &flag))
    if (flag != cookie.IsPersistent())
      return false;

  return true;
}

base::DictionaryValue*
PopulateCookieObject(const net::CanonicalCookie& canonical_cookie) {

  base::DictionaryValue* result = new base::DictionaryValue();
  // A cookie is a raw byte sequence. By explicitly parsing it as UTF-8, we
  // apply error correction, so the string can be safely passed to the renderer.
  result->SetString("name", base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Name())));
  result->SetString("value", base::UTF16ToUTF8(base::UTF8ToUTF16(canonical_cookie.Value())));
  result->SetString("domain", canonical_cookie.Domain());
  result->SetBoolean("host_only", net::cookie_util::DomainIsHostOnly(
                                                                      canonical_cookie.Domain()));
  // A non-UTF8 path is invalid, so we just replace it with an empty string.
  result->SetString("path", base::IsStringUTF8(canonical_cookie.Path()) ? canonical_cookie.Path()
                     : std::string());
  result->SetBoolean("secure", canonical_cookie.IsSecure());
  result->SetBoolean("http_only", canonical_cookie.IsHttpOnly());
  result->SetBoolean("session", !canonical_cookie.IsPersistent());
  if (canonical_cookie.IsPersistent()) {
    result->SetDouble("expiration_date",
                       canonical_cookie.ExpiryDate().ToDoubleT());
  }
  return result;
}

} // namespace

namespace nwapi {

CookieAPIContext::~CookieAPIContext() {}

Window::Window(int id,
               const base::WeakPtr<DispatcherHost>& dispatcher_host,
               const base::DictionaryValue& option)
    : Base(id, dispatcher_host, option),
      shell_(content::Shell::FromRenderViewHost(dispatcher_host->
           render_view_host())) {
  DVLOG(1) << "Window::Window(" << id << ")";
  // Set ID for Shell
  shell_->set_id(id);
  CHECK(registrar_.IsEmpty());
  registrar_.Add(this,
                 chrome::NOTIFICATION_COOKIE_CHANGED,
                 content::NotificationService::AllBrowserContextsAndSources());
}

Window::~Window() {
  // Window object got deleted when we launch new render view host and
  // delete the old one; at this time the Shell should be decoupled
  // with the renderer side
  DVLOG(1) << "Window::~Window(" << shell_->id() << ")";
  shell_->set_id(-1);
}

void Window::Call(const std::string& method,
                  const base::ListValue& arguments) {
  if (method == "Show") {
    shell_->window()->Show();
  } else if (method == "Close") {
    bool force = false;
    arguments.GetBoolean(0, &force);
    shell_->set_force_close(force);
    shell_->window()->Close();
  } else if (method == "Hide") {
    shell_->window()->Hide();
  } else if (method == "Maximize") {
    shell_->window()->Maximize();
  } else if (method == "Unmaximize") {
    shell_->window()->Unmaximize();
  } else if (method == "Minimize") {
    shell_->window()->Minimize();
  } else if (method == "Restore") {
    shell_->window()->Restore();
  } else if (method == "Focus") {
    shell_->window()->Focus(true);
  } else if (method == "Blur") {
    shell_->window()->Focus(false);
  } else if (method == "EnterFullscreen") {
    shell_->window()->SetFullscreen(true);
  } else if (method == "LeaveFullscreen") {
    shell_->window()->SetFullscreen(false);
  } else if (method == "ToggleFullscreen") {
    shell_->window()->SetFullscreen(!shell_->window()->IsFullscreen());
  } else if (method == "EnterKioskMode") {
    shell_->window()->SetKiosk(true);
  } else if (method == "LeaveKioskMode") {
    shell_->window()->SetKiosk(false);
  } else if (method == "ToggleKioskMode") {
    shell_->window()->SetKiosk(!shell_->window()->IsKiosk());
  } else if (method == "CloseDevTools") {
    shell_->CloseDevTools();
  } else if (method == "SetPosition") {
    std::string position;
    if (arguments.GetString(0, &position)){
      shell_->window()->SetPosition(position);
    }
  } else if (method == "ResizeTo") {
    int width, height;
    if (arguments.GetInteger(0, &width) &&
        arguments.GetInteger(1, &height))
      shell_->window()->SetSize(gfx::Size(width, height));
  } else if (method == "SetMaximumSize") {
    int width, height;
    if (arguments.GetInteger(0, &width) &&
        arguments.GetInteger(1, &height))
      shell_->window()->SetMaximumSize(width, height);
  } else if (method == "SetMinimumSize") {
    int width, height;
    if (arguments.GetInteger(0, &width) &&
        arguments.GetInteger(1, &height))
      shell_->window()->SetMinimumSize(width, height);
  } else if (method == "SetResizable") {
    bool resizable;
    if (arguments.GetBoolean(0, &resizable))
      shell_->window()->SetResizable(resizable);
  } else if (method == "SetAlwaysOnTop") {
    bool top;
    if (arguments.GetBoolean(0, &top))
      shell_->window()->SetAlwaysOnTop(top);
  } else if (method == "SetShowInTaskbar" ) {
    bool show;
    if (arguments.GetBoolean(0, &show))
      shell_->window()->SetShowInTaskbar(show);
  } else if (method == "SetVisibleOnAllWorkspaces") {
    bool all_workspaces;
    if (arguments.GetBoolean(0, &all_workspaces))
      shell_->window()->SetVisibleOnAllWorkspaces(all_workspaces);
  } else if (method == "MoveTo") {
    int x, y;
    if (arguments.GetInteger(0, &x) &&
        arguments.GetInteger(1, &y))
      shell_->window()->SetPosition(gfx::Point(x, y));
  } else if (method == "RequestAttention") {
    int count;
    if (arguments.GetInteger(0, &count))
      shell_->window()->FlashFrame(count);
  } else if (method == "SetBadgeLabel") {
    std::string label;
    if (arguments.GetString(0, &label))
      shell_->window()->SetBadgeLabel(label);
  } else if (method == "SetTransparent") {
    bool transparent;
    if (arguments.GetBoolean(0, &transparent))
      shell_->window()->SetTransparent(transparent);
  } else if (method == "SetProgressBar") {
    double progress;
    if (arguments.GetDouble(0, &progress))
      shell_->window()->SetProgressBar(progress);
  } else if (method == "SetMenu") {
    int id;
    if (arguments.GetInteger(0, &id))
      shell_->window()->SetMenu(dispatcher_host()->GetApiObject<Menu>(id));
  } else if (method == "ClearMenu") {
    shell_->window()->ClearMenu();
  } else if (method == "Reload") {
    int type;
    if (arguments.GetInteger(0, &type))
      shell_->Reload(static_cast<content::Shell::ReloadType>(type));
  } else if (method == "CapturePage") {
    std::string image_format_str;
    if (arguments.GetString(0, &image_format_str))
      shell_->window()->CapturePage(image_format_str);
  } else if (method == "CookieGet") {
    CookieGet(arguments);
  } else if (method == "CookieGetAll") {
    CookieGet(arguments, true);
  } else if (method == "CookieRemove") {
    CookieRemove(arguments);
  } else if (method == "CookieSet") {
    CookieSet(arguments);
  } else {
    NOTREACHED() << "Invalid call to Window method:" << method
                 << " arguments:" << arguments;
  }
}

void Window::CallSync(const std::string& method,
                      const base::ListValue& arguments,
                      base::ListValue* result) {
  if (method == "IsFullscreen") {
    result->AppendBoolean(shell_->window()->IsFullscreen());
  } else if (method == "IsKioskMode") {
    result->AppendBoolean(shell_->window()->IsKiosk());
  } else if (method == "GetSize") {
    gfx::Size size = shell_->window()->GetSize();
    result->AppendInteger(size.width());
    result->AppendInteger(size.height());
  } else if (method == "GetPosition") {
    gfx::Point position = shell_->window()->GetPosition();
    result->AppendInteger(position.x());
    result->AppendInteger(position.y());
  } else if (method == "IsTransparent") {
    bool transparent = shell_->window()->IsTransparent();
    result->AppendBoolean(transparent);
  } else if (method == "IsDevToolsOpen") {
    result->AppendBoolean(shell_->devToolsOpen());
  } else if (method == "ShowDevTools") {
    std::string jail_id;
    bool headless = false;
    arguments.GetString(0, &jail_id);
    arguments.GetBoolean(1, &headless);
    shell_->ShowDevTools(jail_id.c_str(), headless);
    int object_id = 0;
    if (!headless)
      object_id = shell_->WrapDevToolsWindow();
    result->AppendInteger(object_id);
  } else {
    NOTREACHED() << "Invalid call to Window method:" << method
                 << " arguments:" << arguments;
  }
}

void Window::CookieRemove(const base::ListValue& arguments) {
  if (!dispatcher_host())
    return;
  CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments);
  bool rv = BrowserThread::PostTask(
                                    BrowserThread::IO, FROM_HERE,
                                    base::Bind(&Window::RemoveCookieOnIOThread,
                                               base::Unretained(this),
                                               make_scoped_refptr(api_context)));
  DCHECK(rv);
}

void Window::CookieSet(const base::ListValue& arguments) {
  if (!dispatcher_host())
    return;
  CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments);
  bool rv = BrowserThread::PostTask(
                                    BrowserThread::IO, FROM_HERE,
                                    base::Bind(&Window::SetCookieOnIOThread,
                                               base::Unretained(this),
                                               make_scoped_refptr(api_context)));
  DCHECK(rv);
}

void Window::RemoveCookieOnIOThread(CookieAPIContext* api_context) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Remove the cookie
  net::CookieStore* cookie_store =
    api_context->store_context_->GetURLRequestContext()->cookie_store();
  std::string name;
  api_context->details_->GetString("name", &name);
  cookie_store->DeleteCookieAsync(
      api_context->url_, name,
      base::Bind(&Window::RemoveCookieCallback, base::Unretained(this),
                 make_scoped_refptr(api_context)));
}

void Window::RemoveCookieCallback(CookieAPIContext* api_context) {
  std::string name;
  api_context->details_->GetString("name", &name);

  base::DictionaryValue* result = new base::DictionaryValue();
  result->SetString("name", name);
  result->SetString("url", api_context->url_.spec());
  api_context->result_->Append(result);

  // Return to UI thread
  bool rv = BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Window::RespondOnUIThread, base::Unretained(this),
                 make_scoped_refptr(api_context)));
  DCHECK(rv);
}

CookieAPIContext::CookieAPIContext(DispatcherHost* dispatcher_host,
                                   const base::ListValue& arguments) {
  content::RenderProcessHost* render_process_host =
    dispatcher_host->render_view_host()->GetProcess();
  net::URLRequestContextGetter* context_getter =
    render_process_host->GetBrowserContext()->
    GetRequestContextForRenderProcess(render_process_host->GetID());

  const base::DictionaryValue* details = NULL;
  std::string url;

  store_context_ = context_getter;
  arguments.GetInteger(0, &req_id_);
  arguments.GetDictionary(1, &details);
  if (details) {
    details_.reset(details->DeepCopyWithoutEmptyChildren());
    details->GetString("url", &url);
  }

  url_ = GURL(url);
  result_.reset(new base::ListValue);
}

void Window::CookieGet(const base::ListValue& arguments, bool get_all) {

  if (!dispatcher_host())
    return;
  CookieAPIContext* api_context = new CookieAPIContext(dispatcher_host(), arguments);

  if (get_all) {
    bool rv = BrowserThread::PostTask(
                                      BrowserThread::IO, FROM_HERE,
                                      base::Bind(&Window::GetAllCookieOnIOThread,
                                                 base::Unretained(this),
                                                 make_scoped_refptr(api_context)));
    DCHECK(rv);
  }else{
    bool rv = BrowserThread::PostTask(
                                      BrowserThread::IO, FROM_HERE,
                                      base::Bind(&Window::GetCookieOnIOThread,
                                                 base::Unretained(this),
                                                 make_scoped_refptr(api_context)));
    DCHECK(rv);
  }
}

void Window::GetAllCookieOnIOThread(CookieAPIContext* api_context) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  net::CookieStore* cookie_store =
    api_context->store_context_->GetURLRequestContext()->cookie_store();
  GetCookieListFromStore(
      cookie_store, api_context->url_,
      base::Bind(&Window::GetAllCookieCallback, base::Unretained(this),
                 make_scoped_refptr(api_context)));
}

void Window::GetCookieOnIOThread(CookieAPIContext* api_context) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  net::CookieStore* cookie_store =
    api_context->store_context_->GetURLRequestContext()->cookie_store();
  GetCookieListFromStore(
      cookie_store, api_context->url_,
      base::Bind(&Window::GetCookieCallback, base::Unretained(this),
                 make_scoped_refptr(api_context)));
}

void Window::GetAllCookieCallback(CookieAPIContext* api_context,
                                  const net::CookieList& cookie_list) {
  net::CookieList::const_iterator it;
  api_context->result_->Clear();
  for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
    if (MatchesCookie(api_context->details_.get(), *it)) {
      api_context->result_->Append(PopulateCookieObject(*it));
    }
  }

  bool rv = BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Window::RespondOnUIThread, base::Unretained(this),
                 make_scoped_refptr(api_context)));
  DCHECK(rv);
}

void Window::GetCookieCallback(CookieAPIContext* api_context,
                               const net::CookieList& cookie_list) {
  net::CookieList::const_iterator it;
  std::string name;
  api_context->details_->GetString("name", &name);

  api_context->result_->Clear();

  for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
    // Return the first matching cookie. Relies on the fact that the
    // CookieMonster returns them in canonical order (longest path, then
    // earliest creation time).

    if (it->Name() == name) {
      api_context->result_->Append(PopulateCookieObject(*it));
      break;
    }
  }

  bool rv = BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Window::RespondOnUIThread, base::Unretained(this),
                 make_scoped_refptr(api_context)));
  DCHECK(rv);
}

void Window::RespondOnUIThread(CookieAPIContext* api_context) {
  if (!dispatcher_host())
    return;
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  base::ListValue ret;
  ret.Append(api_context->result_.release());
  dispatcher_host()->SendEvent(this, base::StringPrintf("__nw_gotcookie%d", api_context->req_id_), ret);
}

void Window::SetCookieOnIOThread(CookieAPIContext* api_context) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  net::CookieMonster* cookie_monster =
    api_context->store_context_->GetURLRequestContext()->cookie_store()->
      GetCookieMonster();

  base::Time expiration_time;
  double expiration_date;

  if (api_context->details_->GetDouble("expirationDate", &expiration_date)) {
    // Time::FromDoubleT converts double time 0 to empty Time object. So we need
    // to do special handling here.
    expiration_time = (expiration_date == 0) ?
      base::Time::UnixEpoch() :
      base::Time::FromDoubleT(expiration_date);
  }

  const base::DictionaryValue* details = api_context->details_.get();
  std::string name, value, domain, path;
  details->GetString("name",   &name);
  details->GetString("value",  &value);
  details->GetString("domain", &domain);
  details->GetString("path",   &path);

  bool secure = false, http_only = false;
  details->GetBoolean("httpOnly", &http_only);
  details->GetBoolean("secure",   &secure);

  cookie_monster->SetCookieWithDetailsAsync(
      api_context->url_,
      name, value, domain, path,
      expiration_time,
      secure, http_only,
      net::COOKIE_PRIORITY_DEFAULT,
      base::Bind(&Window::PullCookie, base::Unretained(this),
                 make_scoped_refptr(api_context)));
}

void Window::PullCookie(CookieAPIContext* api_context, bool set_cookie_result) {
  // Pull the newly set cookie.
  net::CookieMonster* cookie_monster =
    api_context->store_context_->GetURLRequestContext()->cookie_store()->
      GetCookieMonster();
  api_context->success_ = set_cookie_result;
  GetCookieListFromStore(
      cookie_monster, api_context->url_,
      base::Bind(&Window::PullCookieCallback,
                 base::Unretained(this),
                 make_scoped_refptr(api_context)));
}

void Window::PullCookieCallback(CookieAPIContext* api_context,
    const net::CookieList& cookie_list) {
  net::CookieList::const_iterator it;
  for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
    // Return the first matching cookie. Relies on the fact that the
    // CookieMonster returns them in canonical order (longest path, then
    // earliest creation time).
    std::string name;
    api_context->details_->GetString("name", &name);
    if (it->Name() == name) {
      api_context->result_->Append(PopulateCookieObject(*it));
      break;
    }
  }

  bool rv = BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&Window::RespondOnUIThread, base::Unretained(this),
                 make_scoped_refptr(api_context)));
  DCHECK(rv);
}

void Window::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {

  ShellBrowserContext* browser_context =
    content::Source<ShellBrowserContext>(source).ptr();

  switch (type) {
    case chrome::NOTIFICATION_COOKIE_CHANGED:
      CookieChanged(
          browser_context,
          content::Details<ChromeCookieDetails>(details).ptr());
      break;

    default:
      NOTREACHED();
  }
}

void Window::CookieChanged(
    ShellBrowserContext* browser_context,
    ChromeCookieDetails* details) {
  if (!dispatcher_host())
    return;
  scoped_ptr<base::ListValue> args(new base::ListValue());
  base::DictionaryValue* dict = new base::DictionaryValue();
  dict->SetBoolean(kRemovedKey, details->removed);
  dict->Set(kCookieKey, PopulateCookieObject(*details->cookie));

  // Map the internal cause to an external string.
  std::string cause;
  switch (details->cause) {
    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPLICIT:
      cause = kExplicitChangeCause;
      break;

    case net::CookieMonster::Delegate::CHANGE_COOKIE_OVERWRITE:
      cause = kOverwriteChangeCause;
      break;

    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED:
      cause = kExpiredChangeCause;
      break;

    case net::CookieMonster::Delegate::CHANGE_COOKIE_EVICTED:
      cause = kEvictedChangeCause;
      break;

    case net::CookieMonster::Delegate::CHANGE_COOKIE_EXPIRED_OVERWRITE:
      cause = kExpiredOverwriteChangeCause;
      break;

    default:
      NOTREACHED();
  }
  dict->SetString(kCauseKey, cause);

  args->Append(dict);

  GURL cookie_domain =
      GetURLFromCanonicalCookie(*details->cookie);

  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  dispatcher_host()->SendEvent(this, "__nw_cookie_changed", *args);

}

}  // namespace nwapi


print 'Unsupported platform: ' + platform_name


// Copyright (c) 2011 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.

#ifndef CHROME_APP_BREAKPAD_MAC_H_
#define CHROME_APP_BREAKPAD_MAC_H_

namespace breakpad {
// This header defines the Chrome entry points for Breakpad integration.

// Initializes Breakpad.
void InitCrashReporter();

// Give Breakpad a chance to store information about the current process.
// Extra information requires a parsed command line, so call this after
// CommandLine::Init has been called.
void InitCrashProcessInfo();

// Is Breakpad enabled?
bool IsCrashReporterEnabled();
}

#endif  // CHROME_APP_BREAKPAD_MAC_H_


#ifndef NWJS_CONTENT_HOOKS_H
#define NWJS_CONTENT_HOOKS_H

#if defined(WIN32)
#include <windows.h>
#endif

#include "nw_package.h"
#include "base/memory/scoped_ptr.h"
#include "third_party/WebKit/public/web/WebNavigationPolicy.h"
#include "third_party/WebKit/public/platform/WebPageVisibilityState.h"

namespace base {
  class DictionaryValue;
  class CommandLine;
}

namespace blink {
  class WebFrame;
  class WebLocalFrame;
  class WebURLRequest;
  class WebString;
}

namespace gfx {
  class ImageSkia;
}

namespace content {
  class RenderWidget;
  class RenderFrame;
  class RenderFrameHost;
  class RenderProcessHost;
  class RenderViewHost;
  class NotificationDetails;
  class RenderView;
  class WebContents;
  struct WebPreferences;
}

namespace extensions {
  class Dispatcher;
  class Extension;
  class ScriptContext;
  class Dispatcher;
  class AppWindow;
}

namespace nw {
int MainPartsPreCreateThreadsHook();
void MainPartsPostDestroyThreadsHook();
void MainPartsPreMainMessageLoopRunHook();
void ContextCreationHook(blink::WebLocalFrame* frame, extensions::ScriptContext* context);
void LoadNWAppAsExtensionHook(base::DictionaryValue* manifest, std::string* error);
void DocumentElementHook(blink::WebLocalFrame* frame,
                         const extensions::Extension* extension,
                         const GURL& effective_document_url);
void DocumentFinishHook(blink::WebLocalFrame* frame,
                         const extensions::Extension* extension,
                         const GURL& effective_document_url);
 void DocumentHook2(bool start, content::RenderFrame* frame, extensions::Dispatcher* dispatcher);
 void RendererProcessTerminatedHook(content::RenderProcessHost* process,
                                    const content::NotificationDetails& details);
 void OnRenderProcessShutdownHook(extensions::ScriptContext* context);
 void willHandleNavigationPolicy(content::RenderView* rv,
                                 blink::WebFrame* frame,
                                 const blink::WebURLRequest& request,
                                 blink::WebNavigationPolicy* policy,
                                 blink::WebString* manifest,
                                 bool new_win);
 void ExtensionDispatcherCreated(extensions::Dispatcher* dispatcher);
 void CalcNewWinParams(content::WebContents* new_contents, void* params,
                       std::string* nw_inject_js_doc_start,
                       std::string* nw_inject_js_doc_end);
 bool GetImage(Package* package, const FilePath& icon_path, gfx::Image* image);
 scoped_ptr<base::DictionaryValue> MergeManifest();
 bool ExecuteAppCommandHook(int command_id, extensions::AppWindow* app_window);
 bool ProcessSingletonNotificationCallbackHook(const base::CommandLine& command_line,
                                               const base::FilePath& current_directory);
 bool GetUserAgentFromManifest(std::string* agent);
 void SetUserAgentOverride(const std::string& agent,
                           const std::string& name,
                           const std::string& version);

 bool GetPackageImage(nw::Package* package,
                      const base::FilePath& icon_path,
                      gfx::Image* image);
#if defined(OS_MACOSX)
 bool ApplicationShouldHandleReopenHook(bool hasVisibleWindows);
 void OSXOpenURLsHook(const std::vector<GURL>& startup_urls);
#endif
 void CreateAppWindowHook(extensions::AppWindow*);
 void ReloadExtensionHook(const extensions::Extension*);
 bool IsReloadingApp();
 void KickNextTick();
 void OverrideWebkitPrefsHook(content::RenderViewHost* rvh, content::WebPreferences* web_prefs);
 bool PinningRenderer();
 void SetPinningRenderer(bool pin);
 void ShowDevtools(bool show, content::WebContents* web_contents, content::WebContents* container = nullptr);
 bool CheckStoragePartitionMatches(int render_process_id, const GURL& url);
 bool RphGuestFilterURLHook(content::RenderProcessHost* rph, const GURL* url);
 bool ShouldServiceRequestHook(int child_id, const GURL& url);
 bool RenderWidgetWasHiddenHook(content::RenderWidget* rw);
 void LoadNodeSymbols();
 gfx::ImageSkia* GetAppIcon();
#if defined(OS_WIN)
 HICON GetAppHIcon();
 HICON GetWindowHIcon();
#endif
 void SetMainExtensionId(const std::string& id);
 const std::string& GetMainExtensionId();
}

#endif


<!DOCTYPE html>

	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title>App.getProxyForURL</title>


import { EVENT_KEYS, CLASS_OR_ID } from '../config'
import { isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, isCursorAtEnd, getBeginPosition, getEndPosition } from '../codeMirror'
import { findNearestParagraph } from '../utils/domManipulate'
import selection from '../selection'

const arrowCtrl = ContentState => {
  ContentState.prototype.findNextRowCell = function (cell) {
    if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`)
    const row = this.getParent(cell)
    const rowContainer = this.getParent(row) // thead or tbody
    const column = row.children.indexOf(cell)
    if (rowContainer.type === 'thead') {
      const tbody = this.getNextSibling(rowContainer)
      if (tbody && tbody.children.length) {
        return tbody.children[0].children[column]
      }
    } else if (rowContainer.type === 'tbody') {
      const nextRow = this.getNextSibling(row)
      if (nextRow) {
        return nextRow.children[column]
      }
    }
    return null
  }

  ContentState.prototype.findPrevRowCell = function (cell) {
    if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`)
    const row = this.getParent(cell)
    const rowContainer = this.getParent(row) // thead or tbody
    const rowIndex = rowContainer.children.indexOf(row)
    const column = row.children.indexOf(cell)
    if (rowContainer.type === 'tbody') {
      if (rowIndex === 0 && rowContainer.preSibling) {
        const thead = this.getPreSibling(rowContainer)
        return thead.children[0].children[column]
      } else if (rowIndex > 0) {
        return this.getPreSibling(row).children[column]
      }
      return null
    }
    return null
  }

  ContentState.prototype.arrowHandler = function (event) {
    // when the float box is show, use up and down to select item.
    const { floatBox } = this
    const { list, index, show } = floatBox
    const node = selection.getSelectionStart()
    const paragraph = findNearestParagraph(node)
    const id = paragraph.id
    const block = this.getBlock(id)
    const preBlock = this.findPreBlockInLocation(block)
    const nextBlock = this.findNextBlockInLocation(block)

    const { left, right } = selection.getCaretOffsets(paragraph)
    const { start, end } = selection.getCursorRange()

    // fix #101
    if (event.key === EVENT_KEYS.ArrowRight && node && node.classList && node.classList.contains(CLASS_OR_ID['AG_MATH_TEXT'])) {
      const { right } = selection.getCaretOffsets(node)
      if (right === 0 && start.key === end.key && start.offset === end.offset) {
        // It's not recommended to use such lower API, but it's work well.
        return selection.select(node.parentNode.nextElementSibling, 0)
      }
    }

    if (
      (start.key === end.key && start.offset !== end.offset) ||
      start.key !== end.key
    ) {
      return
    }

    if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) {
      event.preventDefault()
      event.stopPropagation()
      switch (event.key) {
        case EVENT_KEYS.ArrowDown:
          if (index < list.length - 1) {
            floatBox.setOptions(list, index + 1)
          }
          break
        case EVENT_KEYS.ArrowUp:
          if (index > 0) {
            floatBox.setOptions(list, index - 1)
          }
          break
      }
      return
    }

    // handle `html` and `code` block when press arrow key
    if (block.type === 'pre' && /code|html/.test(block.functionType)) {
      // handle cursor in code block. the case at firstline or lastline.
      const cm = this.codeBlocks.get(id)
      const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block
      let activeBlock
      event.preventDefault()
      event.stopPropagation()
      switch (event.key) {
        case EVENT_KEYS.ArrowLeft: // fallthrough
        case EVENT_KEYS.ArrowUp:
          if (
            (event.key === EVENT_KEYS.ArrowUp && isCursorAtFirstLine(cm) && preBlock) ||
            (event.key === EVENT_KEYS.ArrowLeft && isCursorAtBegin(cm) && preBlock)
          ) {
            activeBlock = preBlock
          }
          break
        case EVENT_KEYS.ArrowRight: // fallthrough
        case EVENT_KEYS.ArrowDown:
          if (
            (event.key === EVENT_KEYS.ArrowDown && isCursorAtLastLine(cm)) ||
            (event.key === EVENT_KEYS.ArrowRight && isCursorAtEnd(cm))
          ) {
            if (nextBlock) {
              activeBlock = nextBlock
            } else {
              activeBlock = this.createBlockP()
              this.insertAfter(activeBlock, anchorBlock)
            }
          }
          break
      }

      if (activeBlock) {
        const cursorBlock = activeBlock.type === 'p' ? activeBlock.children[0] : activeBlock
        const offset = cursorBlock.text.length
        const key = cursorBlock.key
        this.cursor = {
          start: {
            key,
            offset
          },
          end: {
            key,
            offset
          }
        }

        return this.partialRender()
      }
      return
    }

    if (/th|td/.test(block.type)) {
      let activeBlock
      const cellInNextRow = this.findNextRowCell(block)
      const cellInPrevRow = this.findPrevRowCell(block)

      if (event.key === EVENT_KEYS.ArrowUp && cellInPrevRow) {
        activeBlock = cellInPrevRow
      }

      if (event.key === EVENT_KEYS.ArrowDown && cellInNextRow) {
        activeBlock = cellInNextRow
      }

      if (activeBlock) {
        event.preventDefault()
        event.stopPropagation()
        const offset = activeBlock.type === 'p'
          ? 0
          : (event.key === EVENT_KEYS.ArrowUp
            ? activeBlock.text.length
            : 0)

        const key = activeBlock.type === 'p'
          ? activeBlock.children[0].key
          : activeBlock.key

        this.cursor = {
          start: {
            key,
            offset
          },
          end: {
            key,
            offset
          }
        }

        return this.partialRender()
      }
    }

    if (
      (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowUp) ||
      (preBlock && preBlock.type === 'pre' && /code|html/.test(preBlock.functionType) && event.key === EVENT_KEYS.ArrowLeft && left === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      const key = preBlock.key
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      const cm = this.codeBlocks.get(preBlock.key)
      preBlock.selection = getEndPosition(cm)

      return this.partialRender()
    } else if (
      (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowDown) ||
      (nextBlock && nextBlock.type === 'pre' && /code|html/.test(nextBlock.functionType) && event.key === EVENT_KEYS.ArrowRight && right === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      const key = nextBlock.key
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      nextBlock.selection = getBeginPosition()

      return this.partialRender()
    } else if (
      (event.key === EVENT_KEYS.ArrowUp) ||
      (event.key === EVENT_KEYS.ArrowLeft && start.offset === 0)
    ) {
      event.preventDefault()
      event.stopPropagation()
      if (!preBlock) return
      const key = preBlock.key
      const offset = preBlock.text.length
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      return this.partialRender()
    } else if (
      (event.key === EVENT_KEYS.ArrowDown) ||
      (event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length)
    ) {
      event.preventDefault()
      event.stopPropagation()
      let key
      let newBlock
      if (nextBlock) {
        key = nextBlock.key
      } else {
        newBlock = this.createBlockP()
        const lastBlock = this.blocks[this.blocks.length - 1]
        this.insertAfter(newBlock, lastBlock)
        key = newBlock.children[0].key
      }
      const offset = 0
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }

      return this.partialRender()
    }
  }
}

export default arrowCtrl


<template>
  <div class="bottom-bar" :class="theme">
    <search
      v-if="!sourceCode"
      :theme="theme"
    ></search>
    <status></status>
  </div>
</template>

<script>
  import Search from './search'
  import Status from './status'

  export default {
    components: {
      Search,
      Status
    },
    props: {
      sourceCode: Boolean,
      theme: String
    }
  }
</script>

<style scoped>
  .bottom-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgb(245, 245, 245);
  }
  /* dark theme */
  .dark {
    background: rgb(43, 43, 43);
  }
</style>


.CodeMirror {
  background: transparent;
  height: auto;
}
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection {
  background: rgb(178, 215, 254);
}


import Vue from 'vue'
import Vuex from 'vuex'
import { ipcRenderer, remote } from 'electron'

import listenForMain from './listenForMain'
import project from './project'
import editor from './editor'
import aidou from './aidou'
import layout from './layout'
import preferences from './preferences'
import autoUpdates from './autoUpdates'
import notification from './notification'
import tweet from './tweet'

Vue.use(Vuex)

// global states
const state = {
  platform: process.platform, // platform of system `darwin` | `win32` | `linux`
  appVersion: remote.app.getVersion(), // electron version in develop and Mark Text version in production
  windowActive: true, // weather current window is active or focused
  init: process.env.NODE_ENV === 'development' // weather Mark Text is inited
}

const getters = {}

const mutations = {
  SET_WIN_STATUS (state, status) {
    state.windowActive = status
  },
  SET_INIT_STATUS (state, status) {
    state.init = status
  }
}

const actions = {
  LINTEN_WIN_STATUS ({ commit, state }) {
    ipcRenderer.on('AGANI::window-active-status', (e, { status }) => {
      commit('SET_WIN_STATUS', status)
    })
  },
  INIT_STATUS ({ commit }, status) {
    commit('SET_INIT_STATUS', status)
  }
}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
    // have no states
    listenForMain,
    autoUpdates,
    notification,
    tweet,
    // have states
    project,
    aidou,
    preferences,
    editor,
    layout
  }
})

export default store


const { left, right } = selection.getCaretOffsets(paragraph)

const { start, end } = selection.getCursorRange()

if (event.key === EVENT_KEYS.ArrowRight && node && node.classList && node.classList.contains(CLASS_OR_ID['AG_MATH_TEXT'])) {

const { right } = selection.getCaretOffsets(node)

if (right === 0 && start.key === end.key && start.offset === end.offset) {

const { topOffset, bottomOffset } = selection.getCursorYOffset(paragraph)


# Commented sections below can be used to run tests on the CI server
# https://simulatedgreg.gitbooks.io/electron-vue/content/en/testing.html#on-the-subject-of-ci-testing
osx_image: xcode9.2
sudo: required
dist: trusty

language: node_js
node_js:
  - 8

matrix:
  include:
  - os: osx
    env: CC=clang CXX=clang++ npm_config_clang=1
    compiler: clang
  - os: linux
    env: CC=clang CXX=clang++ npm_config_clang=1
    compiler: clang

cache:
  directories:
  - node_modules
  - "$HOME/.electron"
  - "$HOME/.cache"

addons:
  apt:
    packages:
    - libgnome-keyring-dev
    - icnsutils
    - graphicsmagick
    - xz-utils
    #- xvfb

before_install:
  #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
  #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
  #    wget -qO - https://dl.winehq.org/wine-builds/Release.key | sudo apt-key add -;
  #    sudo apt-add-repository https://dl.winehq.org/wine-builds/ubuntu/;
  #  fi
  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get -qq update ; fi
  #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --install-recommends winehq-stable ; fi

  #- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi

install:
  #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=':99.0' ; fi
  #- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & ; fi
  - curl -o- -L https://yarnpkg.com/install.sh | bash
  - source ~/.bashrc
  - npm install -g xvfb-maybe

  - $CC --version
  - $CXX --version
  - npm --version
  - yarn --version

  - yarn

script:
  #- xvfb-maybe node_modules/.bin/karma start test/unit/karma.conf.js
  #- yarn run pack && xvfb-maybe node_modules/.bin/mocha test/e2e
  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then yarn run release:linux ; fi
  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then yarn run release ; fi

branches:
  only:
  - master


import { normal, gfm, tables } from './blockRules'
/**
 * Block Lexer
 */

function Lexer (options) {
  this.tokens = []
  this.tokens.links = {}
  this.options = options
  this.rules = normal

  if (this.options.gfm) {
    if (this.options.tables) {
      this.rules = tables
    } else {
      this.rules = gfm
    }
  }
}

/**
 * Preprocessing
 */

Lexer.prototype.lex = function (src) {
  src = src
    .replace(/\r\n|\r/g, '\n')
    .replace(/\t/g, '    ')
    .replace(/\u00a0/g, ' ')
    .replace(/\u2424/g, '\n')

  return this.token(src, true)
}

/**
 * Lexing
 */

Lexer.prototype.token = function (src, top, bq) {
  src = src.replace(/^ +$/gm, '')
  let loose
  let cap
  let bull
  let b
  let item
  let space
  let i
  let l
  let checked
  // Only check front matter at the begining of markdown file
  cap = this.rules.frontmatter.exec(src)
  if (!bq && top && cap) {
    src = src.substring(cap[0].length)
    this.tokens.push({
      type: 'frontmatter',
      text: cap[1]
    })
  }

  while (src) {
    // newline
    cap = this.rules.newline.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      if (cap[0].length > 1) {
        this.tokens.push({
          type: 'space'
        })
      }
    }

    // code
    cap = this.rules.code.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      cap = cap[0].replace(/^ {4}/gm, '')
      this.tokens.push({
        type: 'code',
        codeBlockStyle: 'indented',
        text: !this.options.pedantic ? cap.replace(/\n+$/, '') : cap
      })
      continue
    }

    // multiple line math
    cap = this.rules.multiplemath.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'multiplemath',
        text: cap[1]
      })
    }

    // fences (gfm)
    cap = this.rules.fences.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'code',
        codeBlockStyle: 'fenced',
        lang: cap[2],
        text: cap[3]
      })
      continue
    }

    // heading
    cap = this.rules.heading.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'heading',
        headingStyle: 'atx',
        depth: cap[1].length,
        text: cap[2]
      })
      continue
    }

    // table no leading pipe (gfm)
    cap = this.rules.nptable.exec(src)
    if (top && cap) {
      src = src.substring(cap[0].length)

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      }

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right'
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center'
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left'
        } else {
          item.align[i] = null
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i].split(/ *\| */)
      }

      this.tokens.push(item)

      continue
    }

    // lheading
    cap = this.rules.lheading.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'heading',
        headingStyle: 'setext',
        depth: cap[2] === '=' ? 1 : 2,
        text: cap[1]
      })
      continue
    }

    // hr
    cap = this.rules.hr.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'hr'
      })
      continue
    }

    // blockquote
    cap = this.rules.blockquote.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)

      this.tokens.push({
        type: 'blockquote_start'
      })

      cap = cap[0].replace(/^ *> ?/gm, '')

      // Pass `top` to keep the current
      // "toplevel" state. This is exactly
      // how markdown.pl works.
      this.token(cap, top, true)

      this.tokens.push({
        type: 'blockquote_end'
      })

      continue
    }

    // list
    cap = this.rules.tasklist.exec(src) || this.rules.orderlist.exec(src) || this.rules.bulletlist.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      bull = cap[2]
      const ordered = bull.length > 1 && /\d/.test(bull)

      this.tokens.push({
        type: 'list_start',
        ordered,
        listType: bull.length > 1 ? (/\d/.test(bull) ? 'order' : 'task') : 'bullet',
        start: ordered ? +bull : ''
      })

      let next = false
      let prevNext = true
      let listItemIndices = []

      // Get each top-level item.
      cap = cap[0].match(this.rules.item)
      l = cap.length
      i = 0

      for (; i < l; i++) {
        const itemWithBullet = cap[i]
        item = itemWithBullet

        // Remove the list item's bullet
        // so it is seen as the next token.
        space = item.length
        item = item.replace(/^ *([*+-]|\d+\.) +/, '')

        if (this.options.gfm) {
          checked = this.rules.checkbox.exec(item)
          if (checked) {
            checked = checked[1] === 'x'
            item = item.replace(this.rules.checkbox, '')
          } else {
            checked = undefined
          }
        }

        // Outdent whatever the
        // list item contains. Hacky.
        if (~item.indexOf('\n ')) {
          space -= item.length
          item = !this.options.pedantic
            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
            : item.replace(/^ {1,4}/gm, '')
        }

        // Determine whether the next list item belongs here.
        // Backpedal if it does not belong in this list.
        if (this.options.smartLists && i !== l - 1) {
          b = this.rules.bullet.exec(cap[i + 1])[0]
          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
            src = cap.slice(i + 1).join('\n') + src
            i = l - 1
          }
        }

        let prevItem = ''
        if (i === 0) {
          prevItem = item
        } else {
          prevItem = cap[i - 1]
        }

        // Determine whether item is loose or not. If previous item is loose
        // this item is also loose.
        loose = next = next || /^ *([*+-]|\d+\.) +\S+\n\n(?!\s*$)/.test(itemWithBullet)

        // Check if previous line ends with a new line.
        if (!loose && (i !== 0 || l > 1) && prevItem.length !== 0 && prevItem.charAt(prevItem.length - 1) === '\n') {
          loose = next = true
        }

        // A list is either loose or tight, so update previous list items.
        if (next && prevNext !== next) {
          for (const index of listItemIndices) {
            this.tokens[index].type = 'loose_item_start'
          }
          listItemIndices = []
        }
        prevNext = next

        if (!loose) {
          listItemIndices.push(this.tokens.length)
        }

        this.tokens.push({
          checked: checked,
          listItemType: bull.length > 1 ? (/\d/.test(bull) ? 'order' : 'task') : 'bullet',
          bulletListItemMarker: /\d/.test(bull) ? '' : bull.charAt(0),
          type: loose ? 'loose_item_start' : 'list_item_start'
        })

        // Recurse.
        this.token(item, false, bq)

        this.tokens.push({
          type: 'list_item_end'
        })
      }

      this.tokens.push({
        type: 'list_end'
      })

      continue
    }

    // html
    cap = this.rules.html.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: this.options.sanitize ? 'paragraph' : 'html',
        pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
        text: cap[0]
      })
      continue
    }

    // def
    cap = this.rules.def.exec(src)
    if (!bq && top && cap) {
      let text = ''
      do {
        src = src.substring(cap[0].length)
        this.tokens.links[cap[1].toLowerCase()] = {
          href: cap[2],
          title: cap[3]
        }
        text += cap[0]
        if (cap[0].endsWith('\n\n')) break
        cap = this.rules.def.exec(src)
      } while (cap)

      if (this.options.disableInline) {
        this.tokens.push({
          type: 'paragraph',
          text: text.replace(/\n*$/, '')
        })
      }
      continue
    }

    // table (gfm)
    cap = this.rules.table.exec(src)
    if (cap) {
      src = src.substring(cap[0].length)

      item = {
        type: 'table',
        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
        cells: cap[3].replace(/\n$/, '').split('\n')
      }

      for (i = 0; i < item.align.length; i++) {
        if (/^ *-+: *$/.test(item.align[i])) {
          item.align[i] = 'right'
        } else if (/^ *:-+: *$/.test(item.align[i])) {
          item.align[i] = 'center'
        } else if (/^ *:-+ *$/.test(item.align[i])) {
          item.align[i] = 'left'
        } else {
          item.align[i] = null
        }
      }

      for (i = 0; i < item.cells.length; i++) {
        item.cells[i] = item.cells[i]
          .replace(/^ *\| *| *\| *$/g, '')
          .split(/ *\| */)
      }

      this.tokens.push(item)

      continue
    }

    // top-level paragraph
    cap = this.rules.paragraph.exec(src)
    if (top && cap) {
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'paragraph',
        text: cap[1].charAt(cap[1].length - 1) === '\n'
          ? cap[1].slice(0, -1)
          : cap[1]
      })
      continue
    }

    // text
    cap = this.rules.text.exec(src)
    if (cap) {
      // Top-level should never reach here.
      src = src.substring(cap[0].length)
      this.tokens.push({
        type: 'text',
        text: cap[0]
      })
      continue
    }

    if (src) {
      throw new Error('Infinite loop on byte: ' + src.charCodeAt(0))
    }
  }

  return this.tokens
}

export default Lexer


import { sanitize } from '../utils'
import { VOID_HTML_TAGS, HTML_TAGS, HTML_TOOLS, PREVIEW_DOMPURIFY_CONFIG } from '../config'

const HTML_BLOCK_REG = /^<([a-zA-Z\d-]+)(?=\s|>)[^<>]*?>$/

const htmlBlock = ContentState => {
  ContentState.prototype.createToolBar = function (tools, toolBarType) {
    const toolBar = this.createBlock('div', '', false)
    toolBar.toolBarType = toolBarType
    const ul = this.createBlock('ul')

    tools.forEach(tool => {
      const toolBlock = this.createBlock('li')
      const svgBlock = this.createBlock('svg')
      svgBlock.icon = tool.icon
      toolBlock.label = tool.label
      toolBlock.title = tool.title
      this.appendChild(toolBlock, svgBlock)
      this.appendChild(ul, toolBlock)
    })
    this.appendChild(toolBar, ul)
    return toolBar
  }

  ContentState.prototype.createCodeInHtml = function (code, selection) {
    const codeContainer = this.createBlock('div')
    codeContainer.functionType = 'html'
    const preview = this.createBlock('div', '', false)
    preview.htmlContent = sanitize(code, PREVIEW_DOMPURIFY_CONFIG)
    preview.functionType = 'preview'
    const codePre = this.createBlock('pre')
    codePre.lang = 'html'
    codePre.functionType = 'html'
    codePre.text = code
    if (selection) {
      codePre.selection = selection
    }
    this.appendChild(codeContainer, codePre)
    this.appendChild(codeContainer, preview)
    return codeContainer
  }

  ContentState.prototype.htmlToolBarClick = function (type) {
    const { start: { key } } = this.cursor
    const block = this.getBlock(key)
    const codeBlockContainer = this.getParent(block)
    const htmlBlock = this.getParent(codeBlockContainer)

    switch (type) {
      case 'delete':
        htmlBlock.type = 'p'
        htmlBlock.text = ''
        htmlBlock.children = []
        const emptyLine = this.createBlock('span')
        this.appendChild(htmlBlock, emptyLine)
        const key = emptyLine.key
        const offset = 0
        this.cursor = {
          start: { key, offset },
          end: { key, offset }
        }
        this.partialRender()
        break
    }
  }

  ContentState.prototype.handleHtmlBlockClick = function (codeWrapper) {
    const id = codeWrapper.id
    const codeBlock = this.getBlock(id).children[0]
    const key = codeBlock.key
    const offset = 0
    this.cursor = {
      start: { key, offset },
      end: { key, offset }
    }
    this.partialRender()
  }

  ContentState.prototype.createHtmlBlock = function (code) {
    const block = this.createBlock('figure')
    block.functionType = 'html'
    block.text = code
    const toolBar = this.createToolBar(HTML_TOOLS, 'html')
    const htmlBlock = this.createCodeInHtml(code)
    this.appendChild(block, toolBar)
    this.appendChild(block, htmlBlock)
    return block
  }

  ContentState.prototype.initHtmlBlock = function (block, tagName) {
    const isVoidTag = VOID_HTML_TAGS.indexOf(tagName) > -1
    const { text } = block.children[0]
    const htmlContent = isVoidTag ? text : `${text}\n\n</${tagName}>`

    const pos = {
      line: isVoidTag ? 0 : 1,
      ch: isVoidTag ? text.length : 0
    }
    const range = {
      anchor: pos,
      head: pos
    }
    block.type = 'figure'
    block.functionType = 'html'
    block.text = htmlContent
    block.children = []
    const toolBar = this.createToolBar(HTML_TOOLS, 'html')
    const codeContainer = this.createCodeInHtml(htmlContent, range)
    this.appendChild(block, toolBar)
    this.appendChild(block, codeContainer)
    return codeContainer.children[0]
  }

  ContentState.prototype.updateHtmlBlock = function (block) {
    const { type } = block
    if (type !== 'li' && type !== 'p') return false
    const { text } = block.children[0]
    const match = HTML_BLOCK_REG.exec(text)
    const tagName = match && match[1] && HTML_TAGS.find(t => t === match[1])
    return VOID_HTML_TAGS.indexOf(tagName) === -1 && tagName ? this.initHtmlBlock(block, tagName) : false
  }
}

export default htmlBlock


// It's not recommended to use such lower API, but it's work well.

(start.key === end.key && start.offset !== end.offset) ||

// Just do nothing if the cursor is not collapsed


/**
 * translate markdown format to content state used by Mark Text
 * there is some difference when parse loose list item and tight lsit item.
 * Both of them add a p block in li block, use the CSS style to distinguish loose and tight.
 */
import parse5 from 'parse5'
import marked from '../parser/marked'
import ExportMarkdown from './exportMarkdown'
import TurndownService, { usePluginAddRules } from './turndownService'

// To be disabled rules when parse markdown, Because content state don't need to parse inline rules
import { CURSOR_DNA, TABLE_TOOLS, BLOCK_TYPE7 } from '../config'

const LINE_BREAKS_REG = /\n/

const checkIsHTML = value => {
  const trimedValue = value.trim()
  const match = /^<([a-zA-Z\d-]+)(?=\s|>).*>/.exec(trimedValue)
  if (match && match[1]) {
    const tag = match[1]
    if (BLOCK_TYPE7.indexOf(tag) > -1) {
      return /^<([a-zA-Z\d-]+)(?=\s|>).*>\n/.test(trimedValue)
    }
    return true
  }
  return false
}

const chopHTML = value => {
  return value.trim().split(/\n{2,}/)
}

const importRegister = ContentState => {
  // turn markdown to blocks
  ContentState.prototype.markdownToState = function (markdown) {
    // mock a root block...
    const rootState = {
      key: null,
      type: 'root',
      text: '',
      parent: null,
      preSibling: null,
      nextSibling: null,
      children: []
    }

    const htmlText = marked(markdown, { disableInline: true })
    const domAst = parse5.parseFragment(htmlText)
    // console.log(markdown)
    // console.log(htmlText)
    // console.log(domAst)
    const childNodes = domAst.childNodes

    const getLangAndType = node => {
      let lang = ''
      let codeBlockStyle = ''
      if (node.nodeName === 'code') {
        const classAttr = node.attrs.filter(attr => attr.name === 'class')[0]
        if (classAttr && classAttr.value) {
          const { value } = classAttr
          const token = /lang-([^\s]+)/.exec(value)
          codeBlockStyle = /fenced-code-block/.test(value) ? 'fenced' : 'indented'
          lang = token ? token[1] : ''
        }
      }
      return { lang, codeBlockStyle }
    }

    const getPreFunctionType = node => {
      let type = 'code'
      const classAttr = node.attrs.filter(attr => attr.name === 'class')[0]
      if (classAttr && classAttr.value) {
        const { value } = classAttr
        if (/front-matter/.test(value)) type = 'frontmatter'
        if (/multiple-math/.test(value)) type = 'multiplemath'
      }
      return type
    }

    const getRowColumnCount = childNodes => {
      const THEAD_ROW_COUNT = 1
      const tbodyNode = childNodes.find(child => child.nodeName === 'tbody')
      const row = tbodyNode.childNodes.filter(child => child.nodeName === 'tr').length + THEAD_ROW_COUNT - 1
      const column = tbodyNode.childNodes
        .find(child => child.nodeName === 'tr').childNodes
        .filter(td => td.nodeName === 'td')
        .length - 1
      return { row, column } // zero base
    }

    const travel = (parent, childNodes) => {
      for (const child of childNodes) {
        let block
        let value

        switch (child.nodeName) {
          case 'th':
          case 'td':
          case 'h1':
          case 'h2':
          case 'h3':
          case 'h4':
          case 'h5':
          case 'h6':
            const textValue = child.childNodes.length ? child.childNodes[0].value : ''
            const match = /\d/.exec(child.nodeName)
            value = match ? '#'.repeat(+match[0]) + ` ${textValue}` : textValue
            block = this.createBlock(child.nodeName, value)
            // handle heading
            if (match) {
              const headingStyle = child.attrs.find(attr => attr.name === 'class').value
              if (typeof headingStyle === 'string' && /atx|setext/.test(headingStyle)) {
                block.headingStyle = headingStyle
              }
            }
            // handle `th` and `td`
            if (child.nodeName === 'th' || child.nodeName === 'td') {
              const column = childNodes.filter(child => /th|td/.test(child.nodeName)).indexOf(child)
              let align = ''
              const styleAttr = child.attrs.filter(attr => attr.name === 'style')
              if (styleAttr.length) {
                const styleValue = styleAttr[0].value
                if (/text-align/.test(styleValue)) {
                  align = styleValue.split(':')[1]
                }
              }
              Object.assign(block, { column, align })
            }
            this.appendChild(parent, block)
            break

          case 'p':
            value = child.childNodes.length ? child.childNodes[0].value : ''
            if (checkIsHTML(value)) {
              travel(parent, child.childNodes)
            } else {
              block = this.createBlock('p')
              travel(block, child.childNodes)
              this.appendChild(parent, block)
            }
            break

          case 'table':
            const toolBar = this.createToolBar(TABLE_TOOLS, 'table')
            const table = this.createBlock('table')
            Object.assign(table, getRowColumnCount(child.childNodes)) // set row and column
            block = this.createBlock('figure')
            block.functionType = 'table'
            this.appendChild(block, toolBar)
            this.appendChild(block, table)
            this.appendChild(parent, block)
            travel(table, child.childNodes)
            break

          case 'tr':
          case 'tbody':
          case 'thead':
            block = this.createBlock(child.nodeName)
            this.appendChild(parent, block)
            travel(block, child.childNodes)
            break

          case 'hr':
            const initValue = '---'
            block = this.createBlock(child.nodeName, initValue)
            this.appendChild(parent, block)
            break

          case 'input':
            const isTaskListItemCheckbox = child.attrs.some(attr => attr.name === 'class' && attr.value === 'task-list-item-checkbox')
            const checked = child.attrs.some(attr => attr.name === 'checked' && attr.value === '')

            if (isTaskListItemCheckbox) {
              parent.listItemType = 'task' // double check
              block = this.createBlock('input')
              block.checked = checked
              this.appendChild(parent, block)
            }
            break

          case 'li':
            const isTask = child.attrs.some(attr => attr.name === 'class' && attr.value.includes('task-list-item'))
            const isLoose = child.attrs.some(attr => attr.name === 'class' && attr.value.includes('loose-list-item'))

            block = this.createBlock('li')
            block.listItemType = parent.type === 'ul' ? (isTask ? 'task' : 'bullet') : 'order'
            block.isLooseListItem = isLoose

            if (/task|bullet/.test(block.listItemType)) {
              const bulletListItemMarker = child.attrs.find(attr => attr.name === 'data-marker').value
              if (bulletListItemMarker) block.bulletListItemMarker = bulletListItemMarker
            }

            this.appendChild(parent, block)
            travel(block, child.childNodes)
            break

          case 'ul':
            const isTaskList = child.attrs.some(attr => attr.name === 'class' && attr.value === 'task-list')
            block = this.createBlock('ul')
            block.listType = isTaskList ? 'task' : 'bullet'
            travel(block, child.childNodes)
            this.appendChild(parent, block)
            break

          case 'ol':
            block = this.createBlock('ol')
            block.listType = 'order'
            child.attrs.forEach(attr => {
              block[attr.name] = attr.value
            })
            if (!block.start) {
              block.start = 1
            }
            travel(block, child.childNodes)
            this.appendChild(parent, block)
            break

          case 'blockquote':
            block = this.createBlock('blockquote')
            travel(block, child.childNodes)
            this.appendChild(parent, block)
            break

          case 'pre':
            const functionType = getPreFunctionType(child)
            if (functionType === 'frontmatter') {
              value = child.childNodes[0].value
              block = this.createBlock('pre')
              const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
              for (const line of lines) {
                line.functionType = functionType
                this.appendChild(block, line)
              }
              block.functionType = functionType
            } else if (functionType === 'multiplemath') {
              value = child.childNodes[0].value
              block = this.createMathBlock(value)
            } else if (functionType === 'code') {
              const codeNode = child.childNodes[0]
              const { lang, codeBlockStyle } = getLangAndType(codeNode)
              value = codeNode.childNodes[0].value

              if (value.endsWith('\n')) {
                value = value.replace(/\n+$/, '')
              }
              block = this.createBlock('pre', value)
              block.functionType = 'code'
              Object.assign(block, { lang, codeBlockStyle })
            }
            this.appendChild(parent, block)
            break

          case 'script':
            const code = child.childNodes.length ? child.childNodes[0].value : ''
            const fullCode = `<script>${code}</script>`
            block = this.createHtmlBlock(fullCode)
            this.appendChild(parent, block)
            break

          case '#text':
            const { parentNode } = child
            value = child.value

            if (/\S/.test(value)) {
              if (checkIsHTML(value) && /^(#document-fragment|pre|p)$/.test(parentNode.nodeName)) {
                const fragments = chopHTML(value)
                fragments.forEach(fragment => {
                  if (checkIsHTML(fragment)) {
                    // is html block
                    block = this.createHtmlBlock(fragment)
                    this.appendChild(parent, block)
                  } else {
                    // not html block
                    block = this.createBlockP(fragment)
                    this.appendChild(parent, block)
                  }
                })
              } else if (parentNode.nodeName === 'li') {
                block = this.createBlock('p')
                // fix: #153
                const lines = value.replace(/^\s+/, '').split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
                for (const line of lines) {
                  this.appendChild(block, line)
                }
                this.appendChild(parent, block)
              } else if (parentNode.nodeName === 'p') {
                const lines = value.split(LINE_BREAKS_REG).map(line => this.createBlock('span', line))
                for (const line of lines) {
                  this.appendChild(parent, line)
                }
              }
            }
            break

          default:
            if (child.tagName) {
              throw new Error(`unHandle node type ${child.tagName}`)
            }
            break
        }
      }
    }

    travel(rootState, childNodes)
    return rootState.children.length ? rootState.children : [this.createBlockP()]
  }

  ContentState.prototype.htmlToMarkdown = function (html) {
    // turn html to markdown
    const { turndownConfig } = this
    const turndownService = new TurndownService(turndownConfig)
    usePluginAddRules(turndownService)
    // remove double `\\` in Math but I dont know why there are two '\' when paste. @jocs
    const markdown = turndownService.turndown(html).replace(/(\\)\\/g, '$1')
    return markdown
  }

  // turn html to blocks
  ContentState.prototype.html2State = function (html) {
    const markdown = this.htmlToMarkdown(html)
    return this.markdownToState(markdown)
  }

  ContentState.prototype.addCursorToMarkdown = function (markdown, cursor) {
    const { ch, line } = cursor
    const lines = markdown.split('\n')
    const rawText = lines[line]
    lines[line] = rawText.substring(0, ch) + CURSOR_DNA + rawText.substring(ch)
    return lines.join('\n')
  }

  ContentState.prototype.getCodeMirrorCursor = function () {
    const blocks = this.getBlocks()
    const { start: { key, offset } } = this.cursor
    const block = this.getBlock(key)
    const { text } = block
    block.text = text.substring(0, offset) + CURSOR_DNA + text.substring(offset)
    const markdown = new ExportMarkdown(blocks).generate()
    const cursor = markdown.split('\n').reduce((acc, line, index) => {
      const ch = line.indexOf(CURSOR_DNA)
      if (ch > -1) {
        Object.assign(acc, { line: index, ch })
      }
      return acc
    }, {
      line: 0,
      ch: 0
    })
    // remove CURSOR_DNA
    block.text = text
    return cursor
  }

  ContentState.prototype.importCursor = function (cursor) {
    // set cursor
    const travel = blocks => {
      for (const block of blocks) {
        const { key, text, children, editable, type, functionType } = block
        if (text) {
          const offset = text.indexOf(CURSOR_DNA)
          if (offset > -1) {
            block.text = text.substring(0, offset) + text.substring(offset + CURSOR_DNA.length)
            if (editable) {
              this.cursor = {
                start: { key, offset },
                end: { key, offset }
              }
              // handle cursor in Math block, need to remove `CURSOR_DNA` in preview block
              if (type === 'span' && functionType === 'multiplemath') {
                const mathPreview = this.getNextSibling(this.getParent(block))
                const { math } = mathPreview
                const offset = math.indexOf(CURSOR_DNA)
                if (offset > -1) {
                  mathPreview.math = math.substring(0, offset) + math.substring(offset + CURSOR_DNA.length)
                }
              }
              return
            }
          }
        } else if (children.length) {
          travel(children)
        }
      }
    }
    if (cursor) {
      travel(this.blocks)
    } else {
      const lastBlock = this.getLastBlock()
      const key = lastBlock.key
      const offset = lastBlock.text.length
      this.cursor = {
        start: { key, offset },
        end: { key, offset }
      }
    }
  }

  ContentState.prototype.importMarkdown = function (markdown) {
    // empty the blocks and codeBlocks
    this.codeBlocks = new Map()
    this.blocks = this.markdownToState(markdown)
  }
}

export default importRegister


import { CLASS_OR_ID } from '../../../config'
import { getImageInfo } from '../../../utils'

// reference_image
export default function htmlImage (h, cursor, block, token, outerClass) {
  const className = this.getClassName(outerClass, block, token, cursor)
  const imageClass = CLASS_OR_ID['AG_IMAGE_MARKED_TEXT']
  const { start, end } = token.range
  const tag = this.highlight(h, block, start, end, token)
  const { label, backlash, alt } = token
  const rawSrc = label + backlash.second
  let href = ''
  let title = ''
  if (this.labels.has((rawSrc).toLowerCase())) {
    ({ href, title } = this.labels.get(rawSrc.toLowerCase()))
  }
  const imageInfo = getImageInfo(href)
  const { src } = imageInfo
  let id
  let isSuccess
  let selector
  if (src) {
    ({ id, isSuccess } = this.loadImageAsync(imageInfo, alt, className, CLASS_OR_ID['AG_COPY_REMOVE']))
  }
  selector = id ? `span#${id}.${imageClass}` : `span.${imageClass}`
  selector += `.${CLASS_OR_ID['AG_OUTPUT_REMOVE']}`
  if (isSuccess) {
    selector += `.${className}`
  } else {
    selector += `.${CLASS_OR_ID['AG_IMAGE_FAIL']}`
  }

  return isSuccess
    ? [
      h(selector, tag),
      h(`img.${CLASS_OR_ID['AG_COPY_REMOVE']}`, { props: { alt, src, title } })
    ]
    : [h(selector, tag)]
}


if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) {

if ((event.key === EVENT_KEYS.ArrowUp && topOffset > 0) || (event.key === EVENT_KEYS.ArrowDown && bottomOffset > 0)) {