• Skip to main content
  • Select language
  • Skip to search
MDN Web Docs
  • Technologies
    • HTML
    • CSS
    • JavaScript
    • Graphics
    • HTTP
    • APIs / DOM
    • WebExtensions
    • MathML
  • References & Guides
    • Learn web development
    • Tutorials
    • References
    • Developer Guides
    • Accessibility
    • Game development
    • ...more docs
Add-ons
  1. MDN
  2. Mozilla
  3. Add-ons
  4. Add-on SDK
  5. Guides
  6. Contributor's Guide
  7. Private Properties

Private Properties

In This Article
  1. Using Prefixes
  2. Using Closures
  3. Using WeakMaps
  4. Namespaces in the Add-on SDK

Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.

From Firefox 53 onwards, no new legacy add-ons will be accepted on addons.mozilla.org (AMO).

From Firefox 57 onwards, WebExtensions will be the only supported extension type, and Firefox will not load other types.

Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to WebExtensions if they can. See the "Compatibility Milestones" document for more.

A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.

A private property is a property that is only accessible to member functions of instances of the same class. Unlike other languages, JavaScript does not have native support for private properties. People have come up with several ways to emulate private properties using existing language features. This article discusses two common techniques: one using prefixes, the other closures.

Both approaches have drawbacks, they are either not restrictive enough or too restrictive, respectively. A better alternative is to use WeakMap objects, which solve both these problems. However, note that WeakMaps might not be supported by all implementations yet. Also shown here is how to generalize the idea of using WeakMaps, from associating one or more private properties with an object, to associating one or more namespaces with each object. A namespace is simply an object on which one or more private properties are defined.

The SDK uses namespaces internally to implement private properties. The final section explains how to work with the particular namespace implementation used by the SDK. It is possible to read this section on its own, but to fully appreciate how namespaces work, and the problem they set out to solve, it is recommended to read the entire article.

Using Prefixes

A common technique to implement private properties is to prefix each private property name with an underscore. Consider the following example:

function Point(x, y) {
    this._x = x;
    this._y = y;
}

The properties _x and _y are intended to be private, and should only be accessed by member functions.

To make a private property readable/writable from any function, it's common to define getter/setter functions for the property, respectively:

Point.prototype.getX = function () {
    return this._x;
};
Point.prototype.setX = function (x) {
    this._x = x;
};
Point.prototype.getY = function () {
    return this._y;
};
Point.prototype.setY = function (y) {
    this._y = y;
};

The above technique is simple and clearly expresses intent. However, the use of an underscore prefix is just a coding convention and is not enforced by the language: there is nothing to prevent a user from directly accessing a property that is supposed to be private.

Using Closures

Another common technique, is to define private properties as variables and their getter and setter functions as a closure over these variables:

function Point(_x, _y) {
    this.getX = function () {
        return _x;
    };
    this.setX = function (x) {
        _x = x;
    };
    this.getY = function () {
        return _y;
    };
    this.setY = function (y) {
        _y = y;
    };
}

Note that this technique requires member functions that need access to private properties to be defined on the object itself, instead of its prototype. This is slightly less efficient than using the underscore convention, but not significantly for most applications.

The advantage of this technique is that it offers more protection: there is no way for the user to access a private property, except by using its getter or setter function. However, the use of closures makes private properties too restrictive: since there is no way to access variables in one closure from within another closure, there is no way for objects of the same class to access each other's private properties.

Using WeakMaps

The techniques above are either not restrictive enough (prefixes) or too restrictive (closures), however the recent introduction of WeakMaps provides a solution. WeakMaps were introduced to JavaScript in ECMAScript 2015 and have recently been implemented in SpiderMonkey. Before explaining how WeakMaps work, the following looks at how ordinary objects can be used as hash maps, by creating a simple image cache:

let images = {};
function getImage(name) {
    let image = images[name];
    if (!image) {
        image = loadImage(name);
        images[name] = image;
    }
    return image;
}

Now suppose there's a need to associate a thumbnail with each image. Moreover, to create each thumbnail only when it's first required:

function getThumbnail(image) {
    let thumbnail = image._thumbnail;
    if (!thumbnail) {
        thumbnail = createThumbnail(image);
        image._thumbnail = thumbnail;
    }
    return thumbnail;
}

This approach is straightforward, but relies on the use of prefixes. A better approach would be to store thumbnails in their own, separate hash map:

let thumbnails = {};
function getThumbnail(image) {
    let thumbnail = thumbnails[image];
    if (!thumbnail) {
        thumbnail = createThumbnail(image);
        thumbnails[image] = thumbnail;
    }
    return thumbnail;
}

There are two problems with the above approach. First, it's not possible to use objects as keys. When an object is used as a key, it's converted to a string using its toString method. To make the above code work, a unique identifier must be associated with each image and override its toString method. The second problem is more severe: the thumbnail cache maintains a strong reference to each thumbnail object, so they will never be freed, even when their corresponding image has gone out of scope. This is a memory leak waiting to happen.

The above two problems are exactly what WeakMaps were designed to solve. A WeakMap is very similar to an ordinary hash map, but differs from it in two crucial ways:

  1. It can use ordinary objects as keys
  2. It does not maintain a strong reference to its values

To understand how WeakMaps are used in practice, the following rewrites the thumbnail cache using a WeakMap:

let thumbnails = new WeakMap();
function getThumbnail(image) {
    let thumbnail = thumbnails.get(image);
    if (!thumbnail) {
        thumbnail = createThumbnail(image);
        thumbnails.set(image, thumbnail);
    }
    return thumbnail;
}

This version suffers from none of the problems we mentioned earlier. When a thumbnail's image goes out of scope, the WeakMap ensures that its entry in the thumbnail cache will eventually be garbage collected. As a final caveat: the image cache created earlier suffers from the same problem, so for the above code to work properly, the image cache must be rewritten using WeakMaps too.

From WeakMap to Namespace

In the previous section, a separate WeakMap was used to associate each private property with an object. This is cumbersome if the number of private properties becomes large. A better solution would be to store all private properties on a single object, called a namespace, and then store the namespace as a private property on the original object. Using namespaces, the earlier example can be rewritten as:

let map = new WeakMap();
let internal = function (object) {
    if (!map.has(object))
        map.set(object, {});
    return map.get(object);
}
function Point(x, y) {
    internal(this).x = x;
    internal(this).y = y;
}
Point.prototype.getX = function () {
    return internal(this).x;
};
Point.prototype.setX = function (x) {
    internal(this).x = x;
};
Point.prototype.getY = function () {
    return internal(this).y;
};
Point.prototype.setY = function () {
    internal(this).y = y;
};

The only way for a function to access the properties x and y, is if it has a reference to an instance of Point and its internal namespace. Keeping the namespace hidden from all functions, except members of Point, effectively implements private properties. Moreover, because members of Point have a reference to the internal namespace, they can access private properties on other instances of Point.

Namespaces in the Add-on SDK

The Add-on SDK is built on top of XPCOM, the interface between JavaScript and C++ code. Since XPCOM allows the user to do virtually anything, security is very important. Among other things, add-ons should not be able to access variables that are supposed to be private. The SDK uses namespaces internally to ensure this. As always with code that is heavily reused, the SDK defines a helper function to create namespaces. It's defined in the module "core/namespace", and it's use is straightforward. To illustrate this, the following reimplements the class Point using namespaces:

const { ns } = require("sdk/core/namespace");
var internal = ns();
function Point(x, y) {
    internal(this).x = x;
    internal(this).y = y;
}
Point.prototype.getX = function () {
    return internal(this).x;
};
Point.prototype.setX = function (x) {
    internal(this).x = x;
};
Point.prototype.getY = function () {
    return internal(this).y;
};
Point.prototype.setY = function () {
    internal(this).y = y;
};

As a final note, the function ns returns a namespace that uses the namespace associated with the prototype of the object as its prototype.

 

Document Tags and Contributors

 Contributors to this page: bunnybooboo, wbamberg, EliasJorgensen, LinusU, mikejune, Haobin.Cui, Robg1, evold
 Last updated by: bunnybooboo, Apr 15, 2017, 11:47:19 AM
See also
  1. WebExtensions
  2. Getting started
    1. What are WebExtensions?
    2. Your first WebExtension
    3. Your second WebExtension
    4. Anatomy of a WebExtension
    5. Example WebExtensions
  3. How to
    1. Intercept HTTP requests
    2. Modify a web page
    3. Add a button to the toolbar
    4. Implement a settings page
  4. Concepts
    1. Using the JavaScript APIs
    2. User interface components
    3. Content scripts
    4. Match patterns
    5. Internationalization
    6. Content Security Policy
    7. Native messaging
  5. Porting
    1. Porting a Google Chrome extension
    2. Porting a legacy Firefox add-on
    3. Embedded WebExtensions
    4. Comparison with the Add-on SDK
    5. Comparison with XUL/XPCOM extensions
    6. Chrome incompatibilities
  6. Firefox workflow
    1. Temporary Installation in Firefox
    2. Debugging
    3. Getting started with web-ext
    4. web-ext command reference
    5. WebExtensions and the Add-on ID
    6. Publishing your WebExtension
  7. JavaScript APIs
    1. Browser support for JavaScript APIs
    2. alarms
    3. bookmarks
    4. browserAction
    5. browsingData
    6. commands
    7. contextMenus
    8. contextualIdentities
    9. cookies
    10. devtools.inspectedWindow
    11. devtools.panels
    12. downloads
    13. events
    14. extension
    15. extensionTypes
    16. history
    17. i18n
    18. identity
    19. idle
    20. management
    21. notifications
    22. omnibox
    23. pageAction
    24. runtime
    25. sessions
    26. sidebarAction
    27. storage
    28. tabs
    29. topSites
    30. webNavigation
    31. webRequest
    32. windows
  8. Manifest keys
    1. applications
    2. author
    3. background
    4. browser_action
    5. chrome_url_overrides
    6. commands
    7. content_scripts
    8. content_security_policy
    9. default_locale
    10. description
    11. developer
    12. devtools_page
    13. homepage_url
    14. icons
    15. manifest_version
    16. name
    17. omnibox
    18. options_ui
    19. page_action
    20. permissions
    21. short_name
    22. sidebar_action
    23. version
    24. web_accessible_resources
  9. Add-on SDK
  10. Getting started
    1. Installation
    2. Getting started
    3. Troubleshooting
  11. High-Level APIs
    1. addon-page
    2. base64
    3. clipboard
    4. context-menu
    5. hotkeys
    6. indexed-db
    7. l10n
    8. notifications
    9. page-mod
    10. page-worker
    11. panel
    12. passwords
    13. private-browsing
    14. querystring
    15. request
    16. selection
    17. self
    18. simple-prefs
    19. simple-storage
    20. system
    21. tabs
    22. timers
    23. ui
    24. url
    25. webextension
    26. widget
    27. windows
  12. Low-Level APIs
    1. /loader
    2. chrome
    3. console/plain-text
    4. console/traceback
    5. content/content
    6. content/loader
    7. content/mod
    8. content/symbiont
    9. content/worker
    10. core/heritage
    11. core/namespace
    12. core/promise
    13. dev/panel
    14. event/core
    15. event/target
    16. frame/hidden-frame
    17. frame/utils
    18. fs/path
    19. io/byte-streams
    20. io/file
    21. io/text-streams
    22. lang/functional
    23. lang/type
    24. loader/cuddlefish
    25. loader/sandbox
    26. net/url
    27. net/xhr
    28. places/bookmarks
    29. places/favicon
    30. places/history
    31. platform/xpcom
    32. preferences/event-target
    33. preferences/service
    34. remote/child
    35. remote/parent
    36. stylesheet/style
    37. stylesheet/utils
    38. system/child_process
    39. system/environment
    40. system/events
    41. system/runtime
    42. system/unload
    43. system/xul-app
    44. tabs/utils
    45. test/assert
    46. test/harness
    47. test/httpd
    48. test/runner
    49. test/utils
    50. ui/button/action
    51. ui/button/toggle
    52. ui/frame
    53. ui/id
    54. ui/sidebar
    55. ui/toolbar
    56. util/array
    57. util/collection
    58. util/deprecate
    59. util/list
    60. util/match-pattern
    61. util/object
    62. util/uuid
    63. window/utils
  13. Firefox for Android
  14. Getting started
    1. Walkthrough
    2. Debugging
    3. Code snippets
  15. APIs
    1. Accounts.jsm
    2. BrowserApp
    3. HelperApps.jsm
    4. Home.jsm
    5. HomeProvider.jsm
    6. JavaAddonManager.jsm
    7. NativeWindow
    8. Notifications.jsm
    9. PageActions.jsm
    10. Prompt.jsm
    11. RuntimePermissions.jsm
    12. Snackbars.jsm
    13. Sound.jsm
    14. Tab
  16. Legacy
  17. Restartless extensions
    1. Overview
  18. Overlay extensions
    1. Overview
  19. Themes
  20. Lightweight themes
    1. Overview
  21. Complete themes
    1. Overview
  22. Publishing add-ons
  23. Guides
    1. Signing and distribution overview
    2. Submit an add-on
    3. Review policies
    4. Developer agreement
    5. Featured add-ons
    6. Contact addons.mozilla.org
  24. Community and support
  25. Channels
    1. Add-ons blog
    2. Add-on forums
    3. Stack Overflow
    4. Development newsgroup
    5. IRC Channel