diff options
Diffstat (limited to 'toolkit/components/commandlines')
19 files changed, 1179 insertions, 0 deletions
diff --git a/toolkit/components/commandlines/moz.build b/toolkit/components/commandlines/moz.build new file mode 100644 index 0000000000..6169726f2b --- /dev/null +++ b/toolkit/components/commandlines/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.toml"] + +if CONFIG["OS_ARCH"] == "WINNT": + XPCSHELL_TESTS_MANIFESTS += ["test/unit_win/xpcshell.toml"] +elif CONFIG["OS_ARCH"] != "Darwin": + XPCSHELL_TESTS_MANIFESTS += ["test/unit_unix/xpcshell.toml"] + +XPIDL_SOURCES += [ + "nsICommandLine.idl", + "nsICommandLineHandler.idl", + "nsICommandLineRunner.idl", + "nsICommandLineValidator.idl", +] + +XPIDL_MODULE = "commandlines" + +EXPORTS += [ + "nsCommandLine.h", +] + +SOURCES += [ + "nsCommandLine.cpp", +] + +FINAL_LIBRARY = "xul" + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Startup and Profile System") diff --git a/toolkit/components/commandlines/nsCommandLine.cpp b/toolkit/components/commandlines/nsCommandLine.cpp new file mode 100644 index 0000000000..68023089f4 --- /dev/null +++ b/toolkit/components/commandlines/nsCommandLine.cpp @@ -0,0 +1,546 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCommandLine.h" + +#include "nsComponentManagerUtils.h" +#include "nsICategoryManager.h" +#include "nsICommandLineHandler.h" +#include "nsICommandLineValidator.h" +#include "nsIConsoleService.h" +#include "nsIClassInfoImpl.h" +#include "nsIFile.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/SimpleEnumerator.h" + +#include "nsNativeCharsetUtils.h" +#include "nsNetUtil.h" +#include "nsIFileProtocolHandler.h" +#include "nsIURI.h" +#include "nsUnicharUtils.h" +#include "nsTextFormatter.h" +#include "nsXPCOMCID.h" + +#ifdef MOZ_WIDGET_COCOA +# include <CoreFoundation/CoreFoundation.h> +# include "nsILocalFileMac.h" +#elif defined(XP_WIN) +# include <windows.h> +# include <shlobj.h> +#endif + +#ifdef DEBUG_bsmedberg +# define DEBUG_COMMANDLINE +#endif + +#define NS_COMMANDLINE_CID \ + { \ + 0x23bcc750, 0xdc20, 0x460b, { \ + 0xb2, 0xd4, 0x74, 0xd8, 0xf5, 0x8d, 0x36, 0x15 \ + } \ + } + +using mozilla::SimpleEnumerator; + +nsCommandLine::nsCommandLine() + : mState(STATE_INITIAL_LAUNCH), mPreventDefault(false) {} + +NS_IMPL_CLASSINFO(nsCommandLine, nullptr, 0, NS_COMMANDLINE_CID) +NS_IMPL_ISUPPORTS_CI(nsCommandLine, nsICommandLine, nsICommandLineRunner) + +NS_IMETHODIMP +nsCommandLine::GetLength(int32_t* aResult) { + *aResult = int32_t(mArgs.Length()); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::GetArgument(int32_t aIndex, nsAString& aResult) { + NS_ENSURE_ARG_MIN(aIndex, 0); + NS_ENSURE_ARG_MAX(aIndex, int32_t(mArgs.Length() - 1)); + + aResult = mArgs[aIndex]; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::FindFlag(const nsAString& aFlag, bool aCaseSensitive, + int32_t* aResult) { + NS_ENSURE_ARG(!aFlag.IsEmpty()); + + auto c = aCaseSensitive ? nsTDefaultStringComparator<char16_t> + : nsCaseInsensitiveStringComparator; + + for (uint32_t f = 0; f < mArgs.Length(); f++) { + const nsString& arg = mArgs[f]; + + if (arg.Length() >= 2 && arg.First() == char16_t('-')) { + if (aFlag.Equals(Substring(arg, 1), c)) { + *aResult = f; + return NS_OK; + } + } + } + + *aResult = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::RemoveArguments(int32_t aStart, int32_t aEnd) { + NS_ENSURE_ARG_MIN(aStart, 0); + NS_ENSURE_ARG_MAX(uint32_t(aEnd) + 1, mArgs.Length()); + + mArgs.RemoveElementsRange(mArgs.begin() + aStart, mArgs.begin() + aEnd + 1); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::HandleFlag(const nsAString& aFlag, bool aCaseSensitive, + bool* aResult) { + nsresult rv; + + int32_t found; + rv = FindFlag(aFlag, aCaseSensitive, &found); + NS_ENSURE_SUCCESS(rv, rv); + + if (found == -1) { + *aResult = false; + return NS_OK; + } + + *aResult = true; + RemoveArguments(found, found); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::HandleFlagWithParam(const nsAString& aFlag, bool aCaseSensitive, + nsAString& aResult) { + nsresult rv; + + int32_t found; + rv = FindFlag(aFlag, aCaseSensitive, &found); + NS_ENSURE_SUCCESS(rv, rv); + + if (found == -1) { + aResult.SetIsVoid(true); + return NS_OK; + } + + if (found == int32_t(mArgs.Length()) - 1) { + return NS_ERROR_INVALID_ARG; + } + + ++found; + + { // scope for validity of |param|, which RemoveArguments call invalidates + const nsString& param = mArgs[found]; + if (!param.IsEmpty() && param.First() == '-') { + return NS_ERROR_INVALID_ARG; + } + + aResult = param; + } + + RemoveArguments(found - 1, found); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::GetState(uint32_t* aResult) { + *aResult = mState; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::GetPreventDefault(bool* aResult) { + *aResult = mPreventDefault; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::SetPreventDefault(bool aValue) { + mPreventDefault = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::GetWorkingDirectory(nsIFile** aResult) { + NS_ENSURE_TRUE(mWorkingDir, NS_ERROR_NOT_INITIALIZED); + + NS_ADDREF(*aResult = mWorkingDir); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::ResolveFile(const nsAString& aArgument, nsIFile** aResult) { + // First try to resolve as an absolute path if we can. + // This will work even if we have no mWorkingDir, which happens if e.g. + // the dir from which we were started was deleted before we started, +#if defined(XP_UNIX) + if (aArgument.First() == '/') { + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY); + nsresult rv = lf->InitWithPath(aArgument); + if (NS_FAILED(rv)) { + return rv; + } + + lf.forget(aResult); + return NS_OK; + } +#elif defined(XP_WIN) + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY); + + // Just try creating the file with the absolute path; if it fails, + // we'll keep going and try it as a relative path. + if (NS_SUCCEEDED(lf->InitWithPath(aArgument))) { + lf.forget(aResult); + return NS_OK; + } +#endif + // If that fails + return ResolveRelativeFile(aArgument, aResult); +} + +nsresult nsCommandLine::ResolveRelativeFile(const nsAString& aArgument, + nsIFile** aResult) { + if (!mWorkingDir) { + *aResult = nullptr; + return NS_OK; + } + + // This is some seriously screwed-up code. nsIFile.appendRelativeNativePath + // explicitly does not accept .. or . path parts, but that is exactly what we + // need here. So we hack around it. + + nsresult rv; + +#if defined(MOZ_WIDGET_COCOA) + nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(mWorkingDir)); + NS_ENSURE_TRUE(lfm, NS_ERROR_NO_INTERFACE); + + nsCOMPtr<nsILocalFileMac> newfile( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + NS_ENSURE_TRUE(newfile, NS_ERROR_OUT_OF_MEMORY); + + CFURLRef baseurl; + rv = lfm->GetCFURL(&baseurl); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString path; + NS_CopyUnicodeToNative(aArgument, path); + + CFURLRef newurl = CFURLCreateFromFileSystemRepresentationRelativeToBase( + nullptr, (const UInt8*)path.get(), path.Length(), true, baseurl); + + CFRelease(baseurl); + + rv = newfile->InitWithCFURL(newurl); + CFRelease(newurl); + if (NS_FAILED(rv)) return rv; + + newfile.forget(aResult); + return NS_OK; + +#elif defined(XP_UNIX) + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY); + + nsAutoCString nativeArg; + NS_CopyUnicodeToNative(aArgument, nativeArg); + + nsAutoCString newpath; + mWorkingDir->GetNativePath(newpath); + + newpath.Append('/'); + newpath.Append(nativeArg); + + rv = lf->InitWithNativePath(newpath); + if (NS_FAILED(rv)) return rv; + + rv = lf->Normalize(); + if (NS_FAILED(rv)) return rv; + + lf.forget(aResult); + return NS_OK; + +#elif defined(XP_WIN) + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + NS_ENSURE_TRUE(lf, NS_ERROR_OUT_OF_MEMORY); + + // This is a relative path. We use string magic + // and win32 _fullpath. Note that paths of the form "\Relative\To\CurDrive" + // are going to fail, and I haven't figured out a way to work around this + // without the PathCombine() function, which is not available before + // Windows 8; see https://bugzilla.mozilla.org/show_bug.cgi?id=1672814 + + nsAutoString fullPath; + mWorkingDir->GetPath(fullPath); + + fullPath.Append('\\'); + fullPath.Append(aArgument); + + WCHAR pathBuf[MAX_PATH]; + if (!_wfullpath(pathBuf, fullPath.get(), MAX_PATH)) return NS_ERROR_FAILURE; + + rv = lf->InitWithPath(nsDependentString(pathBuf)); + if (NS_FAILED(rv)) return rv; + lf.forget(aResult); + return NS_OK; + +#else +# error Need platform-specific logic here. +#endif +} + +NS_IMETHODIMP +nsCommandLine::ResolveURI(const nsAString& aArgument, nsIURI** aResult) { + nsresult rv; + + // First, we try to init the argument as an absolute file path. If this + // doesn't work, it is an absolute or relative URI. + + nsCOMPtr<nsIIOService> io = do_GetIOService(); + NS_ENSURE_TRUE(io, NS_ERROR_OUT_OF_MEMORY); + + nsCOMPtr<nsIURI> workingDirURI; + if (mWorkingDir) { + io->NewFileURI(mWorkingDir, getter_AddRefs(workingDirURI)); + } + + nsCOMPtr<nsIFile> lf(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID)); + rv = lf->InitWithPath(aArgument); + if (NS_SUCCEEDED(rv)) { + lf->Normalize(); + nsAutoCString url; + // Try to resolve the url for .url files. + rv = resolveShortcutURL(lf, url); + if (NS_SUCCEEDED(rv) && !url.IsEmpty()) { + return io->NewURI(url, nullptr, workingDirURI, aResult); + } + + return io->NewFileURI(lf, aResult); + } + + return io->NewURI(NS_ConvertUTF16toUTF8(aArgument), nullptr, workingDirURI, + aResult); +} + +void nsCommandLine::appendArg(const char* arg) { +#ifdef DEBUG_COMMANDLINE + printf("Adding XP arg: %s\n", arg); +#endif + + nsAutoString warg; +#ifdef XP_WIN + CopyUTF8toUTF16(nsDependentCString(arg), warg); +#else + NS_CopyNativeToUnicode(nsDependentCString(arg), warg); +#endif + + mArgs.AppendElement(warg); +} + +nsresult nsCommandLine::resolveShortcutURL(nsIFile* aFile, nsACString& outURL) { + nsCOMPtr<nsIFileProtocolHandler> fph; + nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURI> uri; + rv = fph->ReadURLFile(aFile, getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + return uri->GetSpec(outURL); +} + +NS_IMETHODIMP +nsCommandLine::Init(int32_t argc, const char* const* argv, nsIFile* aWorkingDir, + uint32_t aState) { + NS_ENSURE_ARG_MAX(aState, 2); + + int32_t i; + + mWorkingDir = aWorkingDir; + + // skip argv[0], we don't want it + for (i = 1; i < argc; ++i) { + const char* curarg = argv[i]; + +#ifdef DEBUG_COMMANDLINE + printf("Testing native arg %i: '%s'\n", i, curarg); +#endif +#if defined(XP_WIN) + if (*curarg == '/') { + char* dup = strdup(curarg); + if (!dup) return NS_ERROR_OUT_OF_MEMORY; + + *dup = '-'; + char* colon = strchr(dup, ':'); + if (colon) { + *colon = '\0'; + appendArg(dup); + appendArg(colon + 1); + } else { + appendArg(dup); + } + free(dup); + continue; + } +#endif + if (*curarg == '-') { + if (*(curarg + 1) == '-') ++curarg; + + char* dup = strdup(curarg); + if (!dup) return NS_ERROR_OUT_OF_MEMORY; + + char* eq = strchr(dup, '='); + if (eq) { + *eq = '\0'; + appendArg(dup); + appendArg(eq + 1); + } else { + appendArg(dup); + } + free(dup); + continue; + } + + appendArg(curarg); + } + + mState = aState; + + return NS_OK; +} + +template <typename... T> +static void LogConsoleMessage(const char16_t* fmt, T... args) { + nsString msg; + nsTextFormatter::ssprintf(msg, fmt, args...); + + nsCOMPtr<nsIConsoleService> cs = + do_GetService("@mozilla.org/consoleservice;1"); + if (cs) cs->LogStringMessage(msg.get()); +} + +nsresult nsCommandLine::EnumerateHandlers(EnumerateHandlersCallback aCallback, + void* aClosure) { + nsresult rv; + + nsCOMPtr<nsICategoryManager> catman( + do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsISimpleEnumerator> entenum; + rv = catman->EnumerateCategory("command-line-handler", + getter_AddRefs(entenum)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) { + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr<nsICommandLineHandler> clh(do_GetService(contractID.get())); + if (!clh) { + nsCString entry; + categoryEntry->GetEntry(entry); + + LogConsoleMessage( + u"Contract ID '%s' was registered as a command line handler for " + u"entry '%s', but could not be created.", + contractID.get(), entry.get()); + continue; + } + + rv = (aCallback)(clh, this, aClosure); + if (rv == NS_ERROR_ABORT) break; + + rv = NS_OK; + } + + return rv; +} + +nsresult nsCommandLine::EnumerateValidators( + EnumerateValidatorsCallback aCallback, void* aClosure) { + nsresult rv; + + nsCOMPtr<nsICategoryManager> catman( + do_GetService(NS_CATEGORYMANAGER_CONTRACTID)); + NS_ENSURE_TRUE(catman, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsISimpleEnumerator> entenum; + rv = catman->EnumerateCategory("command-line-validator", + getter_AddRefs(entenum)); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(entenum)) { + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr<nsICommandLineValidator> clv(do_GetService(contractID.get())); + if (!clv) continue; + + rv = (aCallback)(clv, this, aClosure); + if (rv == NS_ERROR_ABORT) break; + + rv = NS_OK; + } + + return rv; +} + +static nsresult EnumValidate(nsICommandLineValidator* aValidator, + nsICommandLine* aThis, void*) { + return aValidator->Validate(aThis); +} + +static nsresult EnumRun(nsICommandLineHandler* aHandler, nsICommandLine* aThis, + void*) { + return aHandler->Handle(aThis); +} + +NS_IMETHODIMP +nsCommandLine::Run() { + nsresult rv; + + rv = EnumerateValidators(EnumValidate, nullptr); + if (rv == NS_ERROR_ABORT) return rv; + + rv = EnumerateHandlers(EnumRun, nullptr); + if (rv == NS_ERROR_ABORT) return rv; + + return NS_OK; +} + +static nsresult EnumHelp(nsICommandLineHandler* aHandler, nsICommandLine* aThis, + void* aClosure) { + nsresult rv; + + nsCString text; + rv = aHandler->GetHelpInfo(text); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION( + text.Length() == 0 || text.Last() == '\n', + "Help text from command line handlers should end in a newline."); + + nsACString* totalText = reinterpret_cast<nsACString*>(aClosure); + totalText->Append(text); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandLine::GetHelpText(nsACString& aResult) { + EnumerateHandlers(EnumHelp, &aResult); + + return NS_OK; +} diff --git a/toolkit/components/commandlines/nsCommandLine.h b/toolkit/components/commandlines/nsCommandLine.h new file mode 100644 index 0000000000..d4200885d3 --- /dev/null +++ b/toolkit/components/commandlines/nsCommandLine.h @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandLine_h +#define nsCommandLine_h + +#include "nsICommandLineRunner.h" +#include "nsCOMPtr.h" +#include "nsISupportsImpl.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsICommandLineHandler; +class nsICommandLineValidator; +class nsIDOMWindow; +class nsIFile; + +class nsCommandLine final : public nsICommandLineRunner { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICOMMANDLINE + NS_DECL_NSICOMMANDLINERUNNER + + nsCommandLine(); + + protected: + ~nsCommandLine() = default; + + typedef nsresult (*EnumerateHandlersCallback)(nsICommandLineHandler* aHandler, + nsICommandLine* aThis, + void* aClosure); + typedef nsresult (*EnumerateValidatorsCallback)( + nsICommandLineValidator* aValidator, nsICommandLine* aThis, + void* aClosure); + + nsresult ResolveRelativeFile(const nsAString& aArgument, nsIFile** aResult); + + void appendArg(const char* arg); + [[nodiscard]] nsresult resolveShortcutURL(nsIFile* aFile, nsACString& outURL); + nsresult EnumerateHandlers(EnumerateHandlersCallback aCallback, + void* aClosure); + nsresult EnumerateValidators(EnumerateValidatorsCallback aCallback, + void* aClosure); + + nsTArray<nsString> mArgs; + uint32_t mState; + nsCOMPtr<nsIFile> mWorkingDir; + bool mPreventDefault; +}; + +#endif diff --git a/toolkit/components/commandlines/nsICommandLine.idl b/toolkit/components/commandlines/nsICommandLine.idl new file mode 100644 index 0000000000..798a90aaf7 --- /dev/null +++ b/toolkit/components/commandlines/nsICommandLine.idl @@ -0,0 +1,140 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIURI; + +/** + * Represents the command line used to invoke a XUL application. This may be the + * original command-line of this instance, or a command line remoted from another + * instance of the application. + * + * DEFINITIONS: + * "arguments" are any values found on the command line. + * "flags" are switches. In normalized form they are preceded by a single dash. + * Some flags may take "parameters", e.g. "--url <param>". + */ + +[scriptable, uuid(bc3173bd-aa46-46a0-9d25-d9867a9659b6)] +interface nsICommandLine : nsISupports +{ + /** + * Number of arguments in the command line. The application name is not + * part of the command line. + */ + readonly attribute long length; + + /** + * Get an argument from the array of command-line arguments. + * + * On windows, flags of the form /flag are normalized to -flag. /flag:param + * are normalized to -flag param. + * + * On *nix and mac flags of the form --flag are normalized to -flag. --flag=param + * are normalized to the form -flag param. + * + * @param aIndex The argument to retrieve. This index is 0-based, and does + * not include the application name. + * @return The indexth argument. + * @throws NS_ERROR_ILLEGAL_VALUE if aIndex is out of bounds. + */ + AString getArgument(in long aIndex); + + /** + * Find a command-line flag. + * + * @param aFlag The flag name to locate. Do not include the initial + * hyphen. + * @param aCaseSensitive Whether to do case-sensitive comparisons. + * @return The position of the flag in the command line, or -1 if + * not found. + */ + long findFlag(in AString aFlag, in boolean aCaseSensitive); + + /** + * Remove arguments from the command line. This normally occurs after + * a handler has processed the arguments. + * + * @param aStart Index to begin removing. + * @param aEnd Index to end removing, inclusive. + */ + void removeArguments(in long aStart, in long aEnd); + + /** + * A helper method which will find a flag and remove it in one step. + * + * @param aFlag The flag name to find and remove. + * @param aCaseSensitive Whether to do case-sensitive comparisons. + * @return Whether the flag was found. + */ + boolean handleFlag(in AString aFlag, in boolean aCaseSensitive); + + /** + * Find a flag with a parameter and remove both. This is a helper + * method that combines "findFlag" and "removeArguments" in one step. + * + * @return null (a void astring) if the flag is not found. The parameter value + * if found. Note that null and the empty string are not the same. + * @throws NS_ERROR_INVALID_ARG if the flag exists without a parameter + * + * @param aFlag The flag name to find and remove. + * @param aCaseSensitive Whether to do case-sensitive flag search. + */ + AString handleFlagWithParam(in AString aFlag, in boolean aCaseSensitive); + + /** + * The type of command line being processed. + * + * STATE_INITIAL_LAUNCH is the first launch of the application instance. + * STATE_REMOTE_AUTO is a remote command line automatically redirected to + * this instance. + * STATE_REMOTE_EXPLICIT is a remote command line explicitly redirected to + * this instance using xremote/windde/appleevents. + */ + readonly attribute unsigned long state; + + const unsigned long STATE_INITIAL_LAUNCH = 0; + const unsigned long STATE_REMOTE_AUTO = 1; + const unsigned long STATE_REMOTE_EXPLICIT = 2; + + /** + * There may be a command-line handler which performs a default action if + * there was no explicit action on the command line (open a default browser + * window, for example). This flag allows the default action to be prevented. + */ + attribute boolean preventDefault; + + /** + * The working directory for this command line. Use this property instead + * of the working directory for the current process, since a redirected + * command line may have had a different working directory. + * + * @throws NS_ERROR_NOT_INITIALIZED if the working directory was not specified. + */ + readonly attribute nsIFile workingDirectory; + + /** + * Resolve a file-path argument into an nsIFile. This method gracefully + * handles relative or absolute file paths, according to the working + * directory of this command line. + * If the path is relative and there is no working directory available, + * this may return null. + * + * @param aArgument The path to resolve. + * + */ + nsIFile resolveFile(in AString aArgument); + + /** + * Resolves a URI argument into a URI. This method has platform-specific + * logic for converting an absolute URI or a relative file-path into the + * appropriate URI object; it gracefully handles win32 C:\ paths which would + * confuse the ioservice if passed directly. + * + * @param aArgument The command-line argument to resolve. + */ + nsIURI resolveURI(in AString aArgument); +}; diff --git a/toolkit/components/commandlines/nsICommandLineHandler.idl b/toolkit/components/commandlines/nsICommandLineHandler.idl new file mode 100644 index 0000000000..cd042d6a53 --- /dev/null +++ b/toolkit/components/commandlines/nsICommandLineHandler.idl @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsICommandLine; + +/** + * Handles arguments on the command line of an XUL application. + * + * Each handler is registered in the category "command-line-handler". + * The entries in this category are read in alphabetical order, and each + * category value is treated as a service contractid implementing this + * interface. + * + * By convention, handler with ordinary priority should begin with "m". + * + * Example: + * Category Entry Value + * command-line-handler c-extensions @mozilla.org/extension-manager/clh;1 + * command-line-handler m-edit @mozilla.org/composer/clh;1 + * command-line-handler m-irc @mozilla.org/chatzilla/clh;1 + * command-line-handler y-final @mozilla.org/browser/clh-final;1 + * + * @note What do we do about localizing helpInfo? Do we make each handler do it, + * or provide a generic solution of some sort? Don't freeze this interface + * without thinking about this! + */ + +[scriptable, uuid(d4b123df-51ee-48b1-a663-002180e60d3b)] +interface nsICommandLineHandler : nsISupports +{ + /** + * Process a command line. If this handler finds arguments that it + * understands, it should perform the appropriate actions (such as opening + * a window), and remove the arguments from the command-line array. + * + * @throw NS_ERROR_ABORT to immediately cease command-line handling + * (if this is STATE_INITIAL_LAUNCH, quits the app). + * All other exceptions are silently ignored. + */ + void handle(in nsICommandLine aCommandLine); + + /** + * When the app is launched with the --help argument, this attribute + * is retrieved and displayed to the user (on stdout). The text should + * have embedded newlines which wrap at 76 columns, and should include + * a newline at the end. By convention, the right column which contains flag + * descriptions begins at the 24th character. + */ + readonly attribute AUTF8String helpInfo; +}; diff --git a/toolkit/components/commandlines/nsICommandLineRunner.idl b/toolkit/components/commandlines/nsICommandLineRunner.idl new file mode 100644 index 0000000000..3598403151 --- /dev/null +++ b/toolkit/components/commandlines/nsICommandLineRunner.idl @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsICommandLine.idl" + +[ptr] native nsArgvArray(const char* const); + +/** + * Extension of nsICommandLine that allows for initialization of new command lines + * and running the command line actions by processing the command line handlers. + * + * @status INTERNAL - This interface is not meant for use by embedders, and is + * not intended to be frozen. If you are an embedder and need + * functionality provided by this interface, talk to Benjamin + * Smedberg <benjamin@smedbergs.us>. + */ + +[uuid(c9f2996c-b25a-4d3d-821f-4cd0c4bc8afb)] +interface nsICommandLineRunner : nsICommandLine +{ + /** + * This method assumes a native character set, and is meant to be called + * with the argc/argv passed to main(). Talk to bsmedberg if you need to + * create a command line using other data. argv will not be altered in any + * way. + * + * On Windows, the "native" character set is UTF-8, not the native codepage. + * + * @param workingDir The working directory for resolving file and URI paths. + * Can be null, in which case resolving files will not + * work, and only absolute URIs will be resolvable. + * @param state The nsICommandLine.state flag. + */ + void init(in long argc, in nsArgvArray argv, + in nsIFile workingDir, in unsigned long state); + + /** + * Process the command-line handlers in the proper order, calling "handle()" on + * each. + * + * @throws NS_ERROR_ABORT if any handler throws NS_ERROR_ABORT. All other errors + * thrown by handlers will be silently ignored. + */ + void run(); + + /** + * Process and combine the help text provided by each command-line handler. + */ + readonly attribute AUTF8String helpText; +}; diff --git a/toolkit/components/commandlines/nsICommandLineValidator.idl b/toolkit/components/commandlines/nsICommandLineValidator.idl new file mode 100644 index 0000000000..eba949bf30 --- /dev/null +++ b/toolkit/components/commandlines/nsICommandLineValidator.idl @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsICommandLine; + +/** + * Validates arguments on the command line of an XUL application. + * + * Each validator is registered in the category "command-line-validator". + * The entries in this category are read in alphabetical order, and each + * category value is treated as a service contractid implementing this + * interface. + * + * By convention, validator with ordinary priority should begin with "m". + * + * Example: + * Category Entry Value + * command-line-validator b-browser @mozilla.org/browser/clh;1 + * command-line-validator m-edit @mozilla.org/composer/clh;1 + * command-line-validator m-irc @mozilla.org/chatzilla/clh;1 + * + */ + +[scriptable, uuid(5ecaa593-7660-4a3a-957a-92d5770671c7)] +interface nsICommandLineValidator : nsISupports +{ + /** + * Process the command-line validators in the proper order, calling + * "validate()" on each. + * + * @throws NS_ERROR_ABORT if any validator throws NS_ERROR_ABORT. All other + * errors thrown by validators will be silently ignored. + */ + void validate(in nsICommandLine aCommandLine); +}; diff --git a/toolkit/components/commandlines/test/unit/data/test_bug410156.desktop b/toolkit/components/commandlines/test/unit/data/test_bug410156.desktop new file mode 100644 index 0000000000..1847cdd98e --- /dev/null +++ b/toolkit/components/commandlines/test/unit/data/test_bug410156.desktop @@ -0,0 +1,7 @@ +[Desktop Entry]
+Version=1.0
+Encoding=UTF-8
+Name=test_bug410156
+Type=Link
+URL=http://www.bug410156.com/
+Icon=gnome-fs-bookmark
diff --git a/toolkit/components/commandlines/test/unit/data/test_bug410156.url b/toolkit/components/commandlines/test/unit/data/test_bug410156.url new file mode 100644 index 0000000000..6920e1f774 --- /dev/null +++ b/toolkit/components/commandlines/test/unit/data/test_bug410156.url @@ -0,0 +1,9 @@ +[InternetShortcut]
+URL=http://www.bug410156.com/
+IDList=
+HotKey=0
+[{000214A0-0000-0000-C000-000000000046}]
+Prop3=19,2
+[InternetShortcut.A]
+[InternetShortcut.W]
+URL=http://www.bug410156.com/
diff --git a/toolkit/components/commandlines/test/unit/test_bug666224.js b/toolkit/components/commandlines/test/unit/test_bug666224.js new file mode 100644 index 0000000000..acb162dd25 --- /dev/null +++ b/toolkit/components/commandlines/test/unit/test_bug666224.js @@ -0,0 +1,10 @@ +function run_test() { + var cmdLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_INITIAL_LAUNCH + ); + try { + cmdLine.getArgument(cmdLine.length); + } catch (e) {} +} diff --git a/toolkit/components/commandlines/test/unit/test_classinfo.js b/toolkit/components/commandlines/test/unit/test_classinfo.js new file mode 100644 index 0000000000..985b00d58c --- /dev/null +++ b/toolkit/components/commandlines/test/unit/test_classinfo.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var commandLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_INITIAL_LAUNCH + ); + Assert.ok("length" in commandLine); +} diff --git a/toolkit/components/commandlines/test/unit/test_createCommandLine.js b/toolkit/components/commandlines/test/unit/test_createCommandLine.js new file mode 100644 index 0000000000..08c02981d9 --- /dev/null +++ b/toolkit/components/commandlines/test/unit/test_createCommandLine.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_createCommandLine() { + const EXISTING_FILE = do_get_file("xpcshell.toml"); + + // Test `arguments`. + let cmdLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal(cmdLine.length, 0); + Assert.throws(() => cmdLine.workingDirectory, /NS_ERROR_NOT_INITIALIZED/); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_EXPLICIT); + + cmdLine = Cu.createCommandLine( + ["test"], + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal(cmdLine.length, 1); + Assert.equal(cmdLine.getArgument(0), "test"); + Assert.throws(() => cmdLine.getArgument(1), /NS_ERROR_ILLEGAL_VALUE/); + Assert.throws(() => cmdLine.workingDirectory, /NS_ERROR_NOT_INITIALIZED/); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_EXPLICIT); + + cmdLine = Cu.createCommandLine( + ["test1", "test2"], + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal(cmdLine.length, 2); + Assert.equal(cmdLine.getArgument(0), "test1"); + Assert.equal(cmdLine.getArgument(1), "test2"); + Assert.throws(() => cmdLine.getArgument(2), /NS_ERROR_ILLEGAL_VALUE/); + Assert.throws(() => cmdLine.workingDirectory, /NS_ERROR_NOT_INITIALIZED/); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_EXPLICIT); + + // Test `workingDirectory`. + cmdLine = Cu.createCommandLine( + [], + EXISTING_FILE.parent, + Ci.nsICommandLine.STATE_REMOTE_AUTO + ); + Assert.equal(cmdLine.length, 0); + Assert.equal(cmdLine.workingDirectory.path, EXISTING_FILE.parent.path); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_AUTO); + + // Test `state`. + cmdLine = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_REMOTE_AUTO); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_AUTO); + + cmdLine = Cu.createCommandLine( + [], + EXISTING_FILE.parent, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal(cmdLine.state, Ci.nsICommandLine.STATE_REMOTE_EXPLICIT); +}); diff --git a/toolkit/components/commandlines/test/unit/test_handleFlagWithParam.js b/toolkit/components/commandlines/test/unit/test_handleFlagWithParam.js new file mode 100644 index 0000000000..539ac1a819 --- /dev/null +++ b/toolkit/components/commandlines/test/unit/test_handleFlagWithParam.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +add_task(async function test_handleFlagWithParam() { + let goodInputs = [ + ["-arg", "value"], + ["--arg", "value"], + ["-arg=value"], + ["--arg=value"], + ]; + let badInputs = [["-arg", "-value"]]; + // Accepted only on Windows. Perhaps surprisingly, "/arg=value" is not accepted. + let windowsInputs = [["/arg", "value"], ["/arg:value"]]; + + if (AppConstants.platform == "win") { + goodInputs.push(...windowsInputs); + } + + for (let args of goodInputs) { + let cmdLine = Cu.createCommandLine( + args, + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal( + cmdLine.handleFlagWithParam("arg", false), + "value", + `${JSON.stringify(args)} yields 'value' for 'arg'` + ); + } + + for (let args of badInputs) { + let cmdLine = Cu.createCommandLine( + args, + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.throws( + () => cmdLine.handleFlagWithParam("arg", false), + /NS_ERROR_ILLEGAL_VALUE/, + `${JSON.stringify(args)} throws for 'arg'` + ); + } + + if (AppConstants.platform != "win") { + // No special meaning on non-Windows platforms. + for (let args of windowsInputs) { + let cmdLine = Cu.createCommandLine( + args, + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + Assert.equal( + cmdLine.handleFlagWithParam("arg", false), + null, + `${JSON.stringify(args)} yields null for 'arg'` + ); + } + } +}); diff --git a/toolkit/components/commandlines/test/unit/test_resolvefile.js b/toolkit/components/commandlines/test/unit/test_resolvefile.js new file mode 100644 index 0000000000..2c1d01b049 --- /dev/null +++ b/toolkit/components/commandlines/test/unit/test_resolvefile.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function test_resolveFile() { + const EXISTING_FILE = do_get_file("xpcshell.toml"); + // We explicitly do not initialize this with a working dir. + let cmdLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + let fileByPath = cmdLine.resolveFile(EXISTING_FILE.path); + info("Resolved: " + fileByPath.path); + Assert.ok(EXISTING_FILE.equals(fileByPath), "Should find the same file"); + + Assert.ok( + !cmdLine.resolveFile("xpcshell.toml"), + "Should get null for relative files." + ); + + // Now create a commandline with a working dir: + cmdLine = Cu.createCommandLine( + [], + EXISTING_FILE.parent, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + let resolvedTxtFile = cmdLine.resolveFile("xpcshell.toml"); + + info("Resolved: " + resolvedTxtFile.path); + Assert.ok( + EXISTING_FILE.equals(resolvedTxtFile), + "Should resolve relative file." + ); +}); diff --git a/toolkit/components/commandlines/test/unit/xpcshell.toml b/toolkit/components/commandlines/test/unit/xpcshell.toml new file mode 100644 index 0000000000..ecb076c7af --- /dev/null +++ b/toolkit/components/commandlines/test/unit/xpcshell.toml @@ -0,0 +1,17 @@ +[DEFAULT] +head = "" +skip-if = ["os == 'android'"] +support-files = [ + "data/test_bug410156.desktop", + "data/test_bug410156.url", +] + +["test_bug666224.js"] + +["test_classinfo.js"] + +["test_createCommandLine.js"] + +["test_handleFlagWithParam.js"] + +["test_resolvefile.js"] diff --git a/toolkit/components/commandlines/test/unit_unix/test_bug410156.js b/toolkit/components/commandlines/test/unit_unix/test_bug410156.js new file mode 100644 index 0000000000..71c45b8638 --- /dev/null +++ b/toolkit/components/commandlines/test/unit_unix/test_bug410156.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var commandLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_INITIAL_LAUNCH + ); + var urlFile = do_get_file("../unit/data/test_bug410156.desktop"); + var uri = commandLine.resolveURI(urlFile.path); + Assert.equal(uri.spec, "http://www.bug410156.com/"); +} diff --git a/toolkit/components/commandlines/test/unit_unix/xpcshell.toml b/toolkit/components/commandlines/test/unit_unix/xpcshell.toml new file mode 100644 index 0000000000..63fc54a2d7 --- /dev/null +++ b/toolkit/components/commandlines/test/unit_unix/xpcshell.toml @@ -0,0 +1,9 @@ +[DEFAULT] +head = "" +skip-if = ["os == 'android'"] +support-files = [ + "!/toolkit/components/commandlines/test/unit/data/test_bug410156.desktop", + "!/toolkit/components/commandlines/test/unit/data/test_bug410156.url", +] + +["test_bug410156.js"] diff --git a/toolkit/components/commandlines/test/unit_win/test_bug410156.js b/toolkit/components/commandlines/test/unit_win/test_bug410156.js new file mode 100644 index 0000000000..61a3c2ef40 --- /dev/null +++ b/toolkit/components/commandlines/test/unit_win/test_bug410156.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var commandLine = Cu.createCommandLine( + [], + null, + Ci.nsICommandLine.STATE_INITIAL_LAUNCH + ); + var urlFile = do_get_file("../unit/data/test_bug410156.url"); + var uri = commandLine.resolveURI(urlFile.path); + Assert.equal(uri.spec, "http://www.bug410156.com/"); +} diff --git a/toolkit/components/commandlines/test/unit_win/xpcshell.toml b/toolkit/components/commandlines/test/unit_win/xpcshell.toml new file mode 100644 index 0000000000..ab4b493e82 --- /dev/null +++ b/toolkit/components/commandlines/test/unit_win/xpcshell.toml @@ -0,0 +1,8 @@ +[DEFAULT] +head = "" +support-files = [ + "!/toolkit/components/commandlines/test/unit/data/test_bug410156.desktop", + "!/toolkit/components/commandlines/test/unit/data/test_bug410156.url", +] + +["test_bug410156.js"] |