Merge pull request #4570 from atom/cleanup-remote

Cleanup code of remote module
This commit is contained in:
Cheng Zhao 2016-02-22 16:10:55 +08:00
commit 537ead8917
5 changed files with 78 additions and 192 deletions

View file

@ -296,6 +296,7 @@ WebContents::~WebContents() {
// The WebContentsDestroyed will not be called automatically because we // The WebContentsDestroyed will not be called automatically because we
// unsubscribe from webContents before destroying it. So we have to manually // unsubscribe from webContents before destroying it. So we have to manually
// call it here to make sure "destroyed" event is emitted. // call it here to make sure "destroyed" event is emitted.
RenderViewDeleted(web_contents()->GetRenderViewHost());
WebContentsDestroyed(); WebContentsDestroyed();
} }
} }
@ -485,17 +486,7 @@ void WebContents::BeforeUnloadFired(const base::TimeTicks& proceed_time) {
} }
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
int process_id = render_view_host->GetProcess()->GetID(); Emit("render-view-deleted", render_view_host->GetProcess()->GetID());
Emit("render-view-deleted", process_id);
// process.emit('ATOM_BROWSER_RELEASE_RENDER_VIEW', processId);
// Tell the rpc server that a render view has been deleted and we need to
// release all objects owned by it.
v8::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());
node::Environment* env = node::Environment::GetCurrent(isolate());
mate::EmitEvent(isolate(), env->process_object(),
"ATOM_BROWSER_RELEASE_RENDER_VIEW", process_id);
} }
void WebContents::RenderProcessGone(base::TerminationStatus status) { void WebContents::RenderProcessGone(base::TerminationStatus status) {
@ -675,9 +666,6 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) {
// be destroyed on close, and WebContentsDestroyed would be called for it, so // be destroyed on close, and WebContentsDestroyed would be called for it, so
// we need to make sure the api::WebContents is also deleted. // we need to make sure the api::WebContents is also deleted.
void WebContents::WebContentsDestroyed() { void WebContents::WebContentsDestroyed() {
// The RenderViewDeleted was not called when the WebContents is destroyed.
RenderViewDeleted(web_contents()->GetRenderViewHost());
// This event is only for internal use, which is emitted when WebContents is // This event is only for internal use, which is emitted when WebContents is
// being destroyed. // being destroyed.
Emit("will-destroy"); Emit("will-destroy");

View file

@ -1,13 +1,9 @@
'use strict'; 'use strict';
const EventEmitter = require('events').EventEmitter;
const v8Util = process.atomBinding('v8_util'); const v8Util = process.atomBinding('v8_util');
class ObjectsRegistry extends EventEmitter { class ObjectsRegistry {
constructor() { constructor() {
super();
this.setMaxListeners(Number.MAX_VALUE);
this.nextId = 0; this.nextId = 0;
// Stores all objects by ref-counting. // Stores all objects by ref-counting.
@ -15,70 +11,61 @@ class ObjectsRegistry extends EventEmitter {
this.storage = {}; this.storage = {};
// Stores the IDs of objects referenced by WebContents. // Stores the IDs of objects referenced by WebContents.
// (webContentsId) => {(id) => (count)} // (webContentsId) => [id]
this.owners = {}; this.owners = {};
} }
// Register a new object, the object would be kept referenced until you release // Register a new object and return its assigned ID. If the object is already
// it explicitly. // registered then the already assigned ID would be returned.
add(webContentsId, obj) { add(webContents, obj) {
var base, base1, id; // Get or assign an ID to the object.
id = this.saveToStorage(obj); let id = this.saveToStorage(obj);
// Remember the owner. // Add object to the set of referenced objects.
if ((base = this.owners)[webContentsId] == null) { let webContentsId = webContents.getId();
base[webContentsId] = {}; let owner = this.owners[webContentsId];
if (!owner) {
owner = this.owners[webContentsId] = new Set();
// Clear the storage when webContents is reloaded/navigated.
webContents.once('render-view-deleted', (event, id) => {
this.clear(id);
});
} }
if ((base1 = this.owners[webContentsId])[id] == null) { if (!owner.has(id)) {
base1[id] = 0; owner.add(id);
// Increase reference count if not referenced before.
this.storage[id].count++;
} }
this.owners[webContentsId][id]++;
// Returns object's id
return id; return id;
} }
// Get an object according to its ID. // Get an object according to its ID.
get(id) { get(id) {
var ref; return this.storage[id].object;
return (ref = this.storage[id]) != null ? ref.object : void 0;
} }
// Dereference an object according to its ID. // Dereference an object according to its ID.
remove(webContentsId, id) { remove(webContentsId, id) {
var pointer; // Dereference from the storage.
this.dereference(id, 1); this.dereference(id);
// Also reduce the count in owner. // Also remove the reference in owner.
pointer = this.owners[webContentsId]; this.owners[webContentsId].delete(id);
if (pointer == null) {
return;
}
--pointer[id];
if (pointer[id] === 0) {
return delete pointer[id];
}
} }
// Clear all references to objects refrenced by the WebContents. // Clear all references to objects refrenced by the WebContents.
clear(webContentsId) { clear(webContentsId) {
var count, id, ref; let owner = this.owners[webContentsId];
this.emit("clear-" + webContentsId); if (!owner)
if (this.owners[webContentsId] == null) {
return; return;
} for (let id of owner)
ref = this.owners[webContentsId]; this.dereference(id);
for (id in ref) { delete this.owners[webContentsId];
count = ref[id];
this.dereference(id, count);
}
return delete this.owners[webContentsId];
} }
// Private: Saves the object into storage and assigns an ID for it. // Private: Saves the object into storage and assigns an ID for it.
saveToStorage(object) { saveToStorage(object) {
var id; let id = v8Util.getHiddenValue(object, 'atomId');
id = v8Util.getHiddenValue(object, 'atomId');
if (!id) { if (!id) {
id = ++this.nextId; id = ++this.nextId;
this.storage[id] = { this.storage[id] = {
@ -87,18 +74,16 @@ class ObjectsRegistry extends EventEmitter {
}; };
v8Util.setHiddenValue(object, 'atomId', id); v8Util.setHiddenValue(object, 'atomId', id);
} }
++this.storage[id].count;
return id; return id;
} }
// Private: Dereference the object from store. // Private: Dereference the object from store.
dereference(id, count) { dereference(id) {
var pointer; let pointer = this.storage[id];
pointer = this.storage[id];
if (pointer == null) { if (pointer == null) {
return; return;
} }
pointer.count -= count; pointer.count -= 1;
if (pointer.count === 0) { if (pointer.count === 0) {
v8Util.deleteHiddenValue(pointer.object, 'atomId'); v8Util.deleteHiddenValue(pointer.object, 'atomId');
return delete this.storage[id]; return delete this.storage[id];

View file

@ -11,7 +11,9 @@ const FUNCTION_PROPERTIES = [
'length', 'name', 'arguments', 'caller', 'prototype', 'length', 'name', 'arguments', 'caller', 'prototype',
]; ];
var slice = [].slice; // The remote functions in renderer processes.
// (webContentsId) => {id: Function}
let rendererFunctions = {};
// Return the description of object's members: // Return the description of object's members:
let getObjectMemebers = function(object) { let getObjectMemebers = function(object) {
@ -98,7 +100,7 @@ var valueToMeta = function(sender, value, optimizeSimpleObject) {
// Reference the original value if it's an object, because when it's // Reference the original value if it's an object, because when it's
// passed to renderer we would assume the renderer keeps a reference of // passed to renderer we would assume the renderer keeps a reference of
// it. // it.
meta.id = objectsRegistry.add(sender.getId(), value); meta.id = objectsRegistry.add(sender, value);
meta.members = getObjectMemebers(value); meta.members = getObjectMemebers(value);
meta.proto = getObjectPrototype(value); meta.proto = getObjectPrototype(value);
} else if (meta.type === 'buffer') { } else if (meta.type === 'buffer') {
@ -145,7 +147,7 @@ var exceptionToMeta = function(error) {
var unwrapArgs = function(sender, args) { var unwrapArgs = function(sender, args) {
var metaToValue; var metaToValue;
metaToValue = function(meta) { metaToValue = function(meta) {
var i, len, member, ref, rendererReleased, returnValue; var i, len, member, ref, returnValue;
switch (meta.type) { switch (meta.type) {
case 'value': case 'value':
return meta.value; return meta.value;
@ -162,7 +164,9 @@ var unwrapArgs = function(sender, args) {
then: metaToValue(meta.then) then: metaToValue(meta.then)
}); });
case 'object': { case 'object': {
let ret = v8Util.createObjectWithName(meta.name); let ret = {};
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
ref = meta.members; ref = meta.members;
for (i = 0, len = ref.length; i < len; i++) { for (i = 0, len = ref.length; i < len; i++) {
member = ref[i]; member = ref[i];
@ -177,32 +181,30 @@ var unwrapArgs = function(sender, args) {
}; };
case 'function': { case 'function': {
// Cache the callbacks in renderer. // Cache the callbacks in renderer.
if (!sender.callbacks) { let webContentsId = sender.getId();
sender.callbacks = new IDWeakMap; let callbacks = rendererFunctions[webContentsId];
sender.on('render-view-deleted', function() { if (!callbacks) {
return this.callbacks.clear(); callbacks = rendererFunctions[webContentsId] = new IDWeakMap;
sender.once('render-view-deleted', function(event, id) {
callbacks.clear();
delete rendererFunctions[id];
}); });
} }
if (sender.callbacks.has(meta.id)) if (callbacks.has(meta.id))
return sender.callbacks.get(meta.id); return callbacks.get(meta.id);
// Prevent the callback from being called when its page is gone.
rendererReleased = false;
sender.once('render-view-deleted', function() {
rendererReleased = true;
});
let callIntoRenderer = function(...args) { let callIntoRenderer = function(...args) {
if (rendererReleased || sender.isDestroyed()) if ((webContentsId in rendererFunctions) && !sender.isDestroyed())
sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args));
else
throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`); throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`);
sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args));
}; };
v8Util.setDestructor(callIntoRenderer, function() { v8Util.setDestructor(callIntoRenderer, function() {
if (!rendererReleased && !sender.isDestroyed()) if ((webContentsId in rendererFunctions) && !sender.isDestroyed())
sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id); sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id);
}); });
sender.callbacks.set(meta.id, callIntoRenderer); callbacks.set(meta.id, callIntoRenderer);
return callIntoRenderer; return callIntoRenderer;
} }
default: default:
@ -237,11 +239,6 @@ var callFunction = function(event, func, caller, args) {
} }
}; };
// Send by BrowserWindow when its render view is deleted.
process.on('ATOM_BROWSER_RELEASE_RENDER_VIEW', function(id) {
return objectsRegistry.clear(id);
});
ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) { ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) {
try { try {
return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module)); return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module));
@ -279,14 +276,13 @@ ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) {
}); });
ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) { ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) {
var constructor, obj;
try { try {
args = unwrapArgs(event.sender, args); args = unwrapArgs(event.sender, args);
constructor = objectsRegistry.get(id); let constructor = objectsRegistry.get(id);
// Call new with array of arguments. // Call new with array of arguments.
// http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
return event.returnValue = valueToMeta(event.sender, obj); return event.returnValue = valueToMeta(event.sender, obj);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
@ -294,10 +290,9 @@ ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) {
}); });
ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) { ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) {
var func;
try { try {
args = unwrapArgs(event.sender, args); args = unwrapArgs(event.sender, args);
func = objectsRegistry.get(id); let func = objectsRegistry.get(id);
return callFunction(event, func, global, args); return callFunction(event, func, global, args);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
@ -305,13 +300,12 @@ ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) {
}); });
ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) { ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) {
var constructor, obj;
try { try {
args = unwrapArgs(event.sender, args); args = unwrapArgs(event.sender, args);
constructor = objectsRegistry.get(id)[method]; let constructor = objectsRegistry.get(id)[method];
// Call new with array of arguments. // Call new with array of arguments.
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args))); let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
return event.returnValue = valueToMeta(event.sender, obj); return event.returnValue = valueToMeta(event.sender, obj);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
@ -319,10 +313,9 @@ ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args)
}); });
ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) { ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) {
var obj;
try { try {
args = unwrapArgs(event.sender, args); args = unwrapArgs(event.sender, args);
obj = objectsRegistry.get(id); let obj = objectsRegistry.get(id);
return callFunction(event, obj[method], obj, args); return callFunction(event, obj[method], obj, args);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
@ -330,9 +323,8 @@ ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) {
}); });
ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) { ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) {
var obj;
try { try {
obj = objectsRegistry.get(id); let obj = objectsRegistry.get(id);
obj[name] = value; obj[name] = value;
return event.returnValue = null; return event.returnValue = null;
} catch (error) { } catch (error) {
@ -341,9 +333,8 @@ ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) {
}); });
ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) { ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) {
var obj;
try { try {
obj = objectsRegistry.get(id); let obj = objectsRegistry.get(id);
return event.returnValue = valueToMeta(event.sender, obj[name]); return event.returnValue = valueToMeta(event.sender, obj[name]);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
@ -355,21 +346,18 @@ ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) {
}); });
ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) { ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) {
var guestViewManager;
try { try {
guestViewManager = require('./guest-view-manager'); let guestViewManager = require('./guest-view-manager');
return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId)); return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId));
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);
} }
}); });
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function() { ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function(event, guestInstanceId, method, ...args) {
var args, event, guest, guestInstanceId, guestViewManager, method;
event = arguments[0], guestInstanceId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : [];
try { try {
guestViewManager = require('./guest-view-manager'); let guestViewManager = require('./guest-view-manager');
guest = guestViewManager.getGuest(guestInstanceId); let guest = guestViewManager.getGuest(guestInstanceId);
return guest[method].apply(guest, args); return guest[method].apply(guest, args);
} catch (error) { } catch (error) {
return event.returnValue = exceptionToMeta(error); return event.returnValue = exceptionToMeta(error);

View file

@ -2,49 +2,15 @@
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include <map>
#include <string> #include <string>
#include "atom/common/api/object_life_monitor.h" #include "atom/common/api/object_life_monitor.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
#include "base/stl_util.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "v8/include/v8-profiler.h" #include "v8/include/v8-profiler.h"
namespace { namespace {
// A Persistent that can be copied and will not free itself.
template<class T>
struct LeakedPersistentTraits {
typedef v8::Persistent<T, LeakedPersistentTraits<T> > LeakedPersistent;
static const bool kResetInDestructor = false;
template<class S, class M>
static V8_INLINE void Copy(const v8::Persistent<S, M>& source,
LeakedPersistent* dest) {
// do nothing, just allow copy
}
};
// The handles are leaked on purpose.
using FunctionTemplateHandle =
LeakedPersistentTraits<v8::FunctionTemplate>::LeakedPersistent;
std::map<std::string, FunctionTemplateHandle> function_templates_;
v8::Local<v8::Object> CreateObjectWithName(v8::Isolate* isolate,
const std::string& name) {
if (name == "Object")
return v8::Object::New(isolate);
if (ContainsKey(function_templates_, name))
return v8::Local<v8::FunctionTemplate>::New(
isolate, function_templates_[name])->GetFunction()->NewInstance();
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate);
t->SetClassName(mate::StringToV8(isolate, name));
function_templates_[name] = FunctionTemplateHandle(isolate, t);
return t->GetFunction()->NewInstance();
}
v8::Local<v8::Value> GetHiddenValue(v8::Local<v8::Object> object, v8::Local<v8::Value> GetHiddenValue(v8::Local<v8::Object> object,
v8::Local<v8::String> key) { v8::Local<v8::String> key) {
return object->GetHiddenValue(key); return object->GetHiddenValue(key);
@ -78,7 +44,6 @@ void TakeHeapSnapshot(v8::Isolate* isolate) {
void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused, void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
v8::Local<v8::Context> context, void* priv) { v8::Local<v8::Context> context, void* priv) {
mate::Dictionary dict(context->GetIsolate(), exports); mate::Dictionary dict(context->GetIsolate(), exports);
dict.SetMethod("createObjectWithName", &CreateObjectWithName);
dict.SetMethod("getHiddenValue", &GetHiddenValue); dict.SetMethod("getHiddenValue", &GetHiddenValue);
dict.SetMethod("setHiddenValue", &SetHiddenValue); dict.SetMethod("setHiddenValue", &SetHiddenValue);
dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue); dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue);

View file

@ -141,7 +141,7 @@ let setObjectPrototype = function(object, metaId, descriptor) {
}; };
// Convert meta data from browser into real value. // Convert meta data from browser into real value.
var metaToValue = function(meta) { let metaToValue = function(meta) {
var el, i, len, ref1, results, ret; var el, i, len, ref1, results, ret;
switch (meta.type) { switch (meta.type) {
case 'value': case 'value':
@ -257,73 +257,33 @@ for (var name in browserModules) {
} }
// Get remote module. // Get remote module.
// (Just like node's require, the modules are cached permanently, note that this
// is safe leak since the object is not expected to get freed in browser)
var moduleCache = {};
exports.require = function(module) { exports.require = function(module) {
var meta; return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module));
if (moduleCache[module] != null) {
return moduleCache[module];
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_REQUIRE', module);
return moduleCache[module] = metaToValue(meta);
}; };
// Optimize require('electron').
moduleCache.electron = exports;
// Alias to remote.require('electron').xxx. // Alias to remote.require('electron').xxx.
var builtinCache = {};
exports.getBuiltin = function(module) { exports.getBuiltin = function(module) {
var meta; return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module));
if (builtinCache[module] != null) {
return builtinCache[module];
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_GET_BUILTIN', module);
return builtinCache[module] = metaToValue(meta);
}; };
// Get current BrowserWindow object. // Get current BrowserWindow.
var windowCache = null;
exports.getCurrentWindow = function() { exports.getCurrentWindow = function() {
var meta; return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW'));
if (windowCache != null) {
return windowCache;
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WINDOW');
return windowCache = metaToValue(meta);
}; };
// Get current WebContents object. // Get current WebContents object.
var webContentsCache = null;
exports.getCurrentWebContents = function() { exports.getCurrentWebContents = function() {
var meta; return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS'));
if (webContentsCache != null) {
return webContentsCache;
}
meta = ipcRenderer.sendSync('ATOM_BROWSER_CURRENT_WEB_CONTENTS');
return webContentsCache = metaToValue(meta);
}; };
// Get a global object in browser. // Get a global object in browser.
exports.getGlobal = function(name) { exports.getGlobal = function(name) {
var meta; return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name));
meta = ipcRenderer.sendSync('ATOM_BROWSER_GLOBAL', name);
return metaToValue(meta);
}; };
// Get the process object in browser. // Get the process object in browser.
var processCache = null;
exports.__defineGetter__('process', function() { exports.__defineGetter__('process', function() {
if (processCache == null) { return exports.getGlobal('process');
processCache = exports.getGlobal('process');
}
return processCache;
}); });
// Create a funtion that will return the specifed value when called in browser. // Create a funtion that will return the specifed value when called in browser.