From 7139b93a8b4e9115167954fbb507feaa01d79129 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 26 Mar 2017 19:49:33 -0700 Subject: [PATCH] url: add ToObject method to native URL class Provides a factory method to convert a native URL class into a JS URL object. ```c++ Environment* env = ... URL url("http://example.org/a/b/c?query#fragment"); MaybeLocal val = url.ToObject(env); ``` PR-URL: https://github.com/nodejs/node/pull/12056 Reviewed-By: Anna Henningsen --- lib/internal/bootstrap_node.js | 4 ++ lib/internal/url.js | 25 +++++++++ src/env.h | 1 + src/node_url.cc | 96 ++++++++++++++++++++++++++++------ src/node_url.h | 9 ++++ 5 files changed, 118 insertions(+), 17 deletions(-) diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index d7aa89a5ee7338..479f06e5db0d4c 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -54,6 +54,10 @@ _process.setupRawDebug(); + // Ensure setURLConstructor() is called before the native + // URL::ToObject() method is used. + NativeModule.require('internal/url'); + Object.defineProperty(process, 'argv0', { enumerable: true, configurable: false, diff --git a/lib/internal/url.js b/lib/internal/url.js index 64156803d8d30e..5fcabb803ef473 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -1400,6 +1400,31 @@ function getPathFromURL(path) { return isWindows ? getPathFromURLWin32(path) : getPathFromURLPosix(path); } +function NativeURL(ctx) { + this[context] = ctx; +} +NativeURL.prototype = URL.prototype; + +function constructUrl(flags, protocol, username, password, + host, port, path, query, fragment) { + var ctx = new URLContext(); + ctx.flags = flags; + ctx.scheme = protocol; + ctx.username = username; + ctx.password = password; + ctx.port = port; + ctx.path = path; + ctx.query = query; + ctx.fragment = fragment; + ctx.host = host; + const url = new NativeURL(ctx); + url[searchParams] = new URLSearchParams(); + url[searchParams][context] = url; + initSearchParams(url[searchParams], query); + return url; +} +binding.setURLConstructor(constructUrl); + module.exports = { toUSVString, getPathFromURL, diff --git a/src/env.h b/src/env.h index 6fd1051a21d772..66032c999f84de 100644 --- a/src/env.h +++ b/src/env.h @@ -273,6 +273,7 @@ namespace node { V(tls_wrap_constructor_template, v8::FunctionTemplate) \ V(tty_constructor_template, v8::FunctionTemplate) \ V(udp_constructor_function, v8::Function) \ + V(url_constructor_function, v8::Function) \ V(write_wrap_constructor_function, v8::Function) \ class Environment; diff --git a/src/node_url.cc b/src/node_url.cc index 6cd78c2c6c04c8..4f3525332ebd94 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -27,9 +27,11 @@ using v8::HandleScope; using v8::Integer; using v8::Isolate; using v8::Local; +using v8::MaybeLocal; using v8::Null; using v8::Object; using v8::String; +using v8::TryCatch; using v8::Undefined; using v8::Value; @@ -1226,6 +1228,29 @@ namespace url { } } + static inline void SetArgs(Environment* env, + Local argv[], + const struct url_data* url) { + Isolate* isolate = env->isolate(); + argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url->flags); + if (url->flags & URL_FLAGS_HAS_SCHEME) + argv[ARG_PROTOCOL] = OneByteString(isolate, url->scheme.c_str()); + if (url->flags & URL_FLAGS_HAS_USERNAME) + argv[ARG_USERNAME] = UTF8STRING(isolate, url->username); + if (url->flags & URL_FLAGS_HAS_PASSWORD) + argv[ARG_PASSWORD] = UTF8STRING(isolate, url->password); + if (url->flags & URL_FLAGS_HAS_HOST) + argv[ARG_HOST] = UTF8STRING(isolate, url->host); + if (url->flags & URL_FLAGS_HAS_QUERY) + argv[ARG_QUERY] = UTF8STRING(isolate, url->query); + if (url->flags & URL_FLAGS_HAS_FRAGMENT) + argv[ARG_FRAGMENT] = UTF8STRING(isolate, url->fragment); + if (url->port > -1) + argv[ARG_PORT] = Integer::New(isolate, url->port); + if (url->flags & URL_FLAGS_HAS_PATH) + argv[ARG_PATH] = Copy(env, url->path); + } + static void Parse(Environment* env, Local recv, const char* input, @@ -1267,23 +1292,7 @@ namespace url { undef, undef, }; - argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url.flags); - if (url.flags & URL_FLAGS_HAS_SCHEME) - argv[ARG_PROTOCOL] = OneByteString(isolate, url.scheme.c_str()); - if (url.flags & URL_FLAGS_HAS_USERNAME) - argv[ARG_USERNAME] = UTF8STRING(isolate, url.username); - if (url.flags & URL_FLAGS_HAS_PASSWORD) - argv[ARG_PASSWORD] = UTF8STRING(isolate, url.password); - if (url.flags & URL_FLAGS_HAS_HOST) - argv[ARG_HOST] = UTF8STRING(isolate, url.host); - if (url.flags & URL_FLAGS_HAS_QUERY) - argv[ARG_QUERY] = UTF8STRING(isolate, url.query); - if (url.flags & URL_FLAGS_HAS_FRAGMENT) - argv[ARG_FRAGMENT] = UTF8STRING(isolate, url.fragment); - if (url.port > -1) - argv[ARG_PORT] = Integer::New(isolate, url.port); - if (url.flags & URL_FLAGS_HAS_PATH) - argv[ARG_PATH] = Copy(env, url.path); + SetArgs(env, argv, &url); (void)cb->Call(context, recv, arraysize(argv), argv); } else if (error_cb->IsFunction()) { Local argv[2] = { undef, undef }; @@ -1418,6 +1427,58 @@ namespace url { v8::NewStringType::kNormal).ToLocalChecked()); } + // This function works by calling out to a JS function that creates and + // returns the JS URL object. Be mindful of the JS<->Native boundary + // crossing that is required. + const Local URL::ToObject(Environment* env) const { + Isolate* isolate = env->isolate(); + Local context = env->context(); + HandleScope handle_scope(isolate); + Context::Scope context_scope(context); + + const Local undef = Undefined(isolate); + + if (context_.flags & URL_FLAGS_FAILED) + return Local(); + + Local argv[9] = { + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, + undef, + }; + SetArgs(env, argv, &context_); + + TryCatch try_catch(isolate); + + // The SetURLConstructor method must have been called already to + // set the constructor function used below. SetURLConstructor is + // called automatically when the internal/url.js module is loaded + // during the internal/bootstrap_node.js processing. + MaybeLocal ret = + env->url_constructor_function() + ->Call(env->context(), undef, 9, argv); + + if (ret.IsEmpty()) { + ClearFatalExceptionHandlers(env); + FatalException(isolate, try_catch); + } + + return ret.ToLocalChecked(); + } + + static void SetURLConstructor(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + env->set_url_constructor_function(args[0].As()); + } + static void Init(Local target, Local unused, Local context, @@ -1428,6 +1489,7 @@ namespace url { env->SetMethod(target, "toUSVString", ToUSVString); env->SetMethod(target, "domainToASCII", DomainToASCII); env->SetMethod(target, "domainToUnicode", DomainToUnicode); + env->SetMethod(target, "setURLConstructor", SetURLConstructor); #define XX(name, _) NODE_DEFINE_CONSTANT(target, name); FLAGS(XX) diff --git a/src/node_url.h b/src/node_url.h index 21eabd046a9d01..809bf557be1e00 100644 --- a/src/node_url.h +++ b/src/node_url.h @@ -4,11 +4,18 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "node.h" +#include "env.h" +#include "env-inl.h" + #include namespace node { namespace url { +using v8::Local; +using v8::Value; + + #define BIT_AT(a, i) \ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ (1 << ((unsigned int) (i) & 7)))) @@ -619,6 +626,8 @@ class URL { return ret; } + const Local ToObject(Environment* env) const; + private: struct url_data context_; };