Bug 1037374 - Uplift Add-on SDK to Firefox r=me
authorErik Vold <evold@mozilla.com>
Tue, 15 Jul 2014 06:19:40 -0700
changeset 216117 62c58a6ce205799eb0044df3944e1c7dc2205fb4
parent 216116 4089f731a406d902fd9a911cd3e4785f4962ba80
child 216118 37c80d91bed8ba3426049e90eeaabc981a5c31de
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs1037374
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1037374 - Uplift Add-on SDK to Firefox r=me
addon-sdk/source/lib/sdk/deprecated/unit-test.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/panel/utils.js
addon-sdk/source/lib/sdk/test/harness.js
addon-sdk/source/lib/sdk/test/loader.js
addon-sdk/source/lib/toolkit/loader.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/python-lib/cuddlefish/manifest.py
addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
addon-sdk/source/test/addons/e10s-tabs/lib/main.js
addon-sdk/source/test/addons/packaging/main.js
addon-sdk/source/test/addons/packaging/package.json
addon-sdk/source/test/addons/self/main.js
addon-sdk/source/test/event/helpers.js
addon-sdk/source/test/test-content-script.js
addon-sdk/source/test/test-packaging.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-plain-text-console.js
addon-sdk/source/test/test-require.js
addon-sdk/source/test/test-self.js
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -446,17 +446,17 @@ TestRunner.prototype = {
     if (ms === undefined)
       ms = this.DEFAULT_PAUSE_TIMEOUT;
 
     var self = this;
 
     function tiredOfWaiting() {
       self._logTestFailed("timed out");
       if ("testMessage" in self.console) {
-        self.console.testMessage(false, false, self.test.name, "Timed out");
+        self.console.testMessage(false, false, self.test.name, "Test timed out");
       }
       else {
         self.console.error("fail:", "Timed out")
       }
       if (self.waitUntilCallback) {
         self.waitUntilCallback(true);
         self.waitUntilCallback = null;
       }
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -68,17 +68,18 @@ let displayContract = contract({
 let panelContract = contract(merge({
   // contentStyle* / contentScript* are sharing the same validation constraints,
   // so they can be mostly reused, except for the messages.
   contentStyle: merge(Object.create(loaderContract.rules.contentScript), {
     msg: 'The `contentStyle` option must be a string or an array of strings.'
   }),
   contentStyleFile: merge(Object.create(loaderContract.rules.contentScriptFile), {
     msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
-  })
+  }),
+  contextMenu: boolean
 }, displayContract.rules, loaderContract.rules));
 
 
 function isDisposed(panel) !views.has(panel);
 
 let panels = new WeakMap();
 let models = new WeakMap();
 let views = new WeakMap();
@@ -129,16 +130,17 @@ const Panel = Class({
   ],
   extends: WorkerHost(workerFor),
   setup: function setup(options) {
     let model = merge({
       defaultWidth: 320,
       defaultHeight: 240,
       focus: true,
       position: Object.freeze({}),
+      contextMenu: false
     }, panelContract(options));
     models.set(this, model);
 
     if (model.contentStyle || model.contentStyleFile) {
       styles.set(this, Style({
         uri: model.contentStyleFile,
         source: model.contentStyle
       }));
@@ -146,16 +148,19 @@ const Panel = Class({
 
     // Setup view
     let view = domPanel.make();
     panels.set(view, this);
     views.set(this, view);
 
     // Load panel content.
     domPanel.setURL(view, model.contentURL);
+    
+    // Allow context menu
+    domPanel.allowContextMenu(view, model.contextMenu);
 
     setupAutoHide(this);
 
     // Setup listeners.
     setListeners(this, options);
     let worker = new Worker(stripListeners(options));
     workers.set(this, worker);
 
@@ -183,17 +188,25 @@ const Panel = Class({
   get height() modelFor(this).height,
   set height(value) this.resize(this.width, value),
 
   /* Public API: Panel.focus */
   get focus() modelFor(this).focus,
 
   /* Public API: Panel.position */
   get position() modelFor(this).position,
-
+  
+  /* Public API: Panel.contextMenu */
+  get contextMenu() modelFor(this).contextMenu,
+  set contextMenu(allow) {
+    let model = modelFor(this);
+    model.contextMenu = panelContract({ contextMenu: allow }).contextMenu;
+    domPanel.allowContextMenu(viewFor(this), model.contextMenu);
+  },
+    
   get contentURL() modelFor(this).contentURL,
   set contentURL(value) {
     let model = modelFor(this);
     model.contentURL = panelContract({ contentURL: value }).contentURL;
     domPanel.setURL(viewFor(this), model.contentURL);
     // Detach worker so that messages send will be queued until it's
     // reatached once panel content is ready.
     workerFor(this).detach();
@@ -221,17 +234,18 @@ const Panel = Class({
     let anchorView = getNodeView(anchor || options.position || model.position);
 
     options = merge({
       position: model.position,
       width: model.width,
       height: model.height,
       defaultWidth: model.defaultWidth,
       defaultHeight: model.defaultHeight,
-      focus: model.focus
+      focus: model.focus,
+      contextMenu: model.contextMenu
     }, displayContract(options));
 
     if (!isDisposed(this))
       domPanel.show(view, options, anchorView);
 
     return this;
   },
 
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -206,17 +206,17 @@ function shimDefaultStyle(panel) {
       if (node) node.style.padding = 0;
   });
 }
 
 function show(panel, options, anchor) {
   // Prevent the panel from getting focus when showing up
   // if focus is set to false
   panel.setAttribute("noautofocus", !options.focus);
-
+  
   let window = anchor && getOwnerBrowserWindow(anchor);
   let { document } = window ? window : getMostRecentBrowserWindow();
   attach(panel, document);
 
   open(panel, options, anchor);
 }
 exports.show = show
 
@@ -399,8 +399,18 @@ let getContentFrame = panel =>
     panel.backgroundFrame
 exports.getContentFrame = getContentFrame;
 
 function getContentDocument(panel) getContentFrame(panel).contentDocument
 exports.getContentDocument = getContentDocument;
 
 function setURL(panel, url) getContentFrame(panel).setAttribute("src", url)
 exports.setURL = setURL;
+
+function allowContextMenu(panel, allow) {
+  if(allow) {
+    panel.setAttribute("context", "contentAreaContextMenu");
+  } 
+  else {
+    panel.removeAttribute("context");
+  }
+}
+exports.allowContextMenu = allowContextMenu;
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -13,16 +13,17 @@ const { serializeStack, parseStack  } = 
 const { setTimeout } = require('../timers');
 const { PlainTextConsole } = require("../console/plain-text");
 const { when: unload } = require("../system/unload");
 const { format, fromException }  = require("../console/traceback");
 const system = require("../system");
 const memory = require('../deprecated/memory');
 const { gc: gcPromise } = require('./memory');
 const { defer } = require('../core/promise');
+const { extend } = require('../core/heritage');
 
 // Trick manifest builder to make it think we need these modules ?
 const unit = require("../deprecated/unit-test");
 const test = require("../../test");
 const url = require("../url");
 
 function emptyPromise() {
   let { promise, resolve } = defer();
@@ -448,35 +449,35 @@ var consoleListener = {
     var pointless = [err for each (err in POINTLESS_ERRORS)
                          if (message.indexOf(err) >= 0)];
     if (pointless.length == 0 && message)
       testConsole.log(message);
   }
 };
 
 function TestRunnerConsole(base, options) {
-  this.__proto__ = {
+  let proto = extend(base, {
     errorsLogged: 0,
     warn: function warn() {
       this.errorsLogged++;
       base.warn.apply(base, arguments);
     },
     error: function error() {
       this.errorsLogged++;
       base.error.apply(base, arguments);
     },
     info: function info(first) {
       if (options.verbose)
         base.info.apply(base, arguments);
       else
         if (first == "pass:")
           print(".");
     },
-    __proto__: base
-  };
+  });
+  return Object.create(proto);
 }
 
 function stringify(arg) {
   try {
     return String(arg);
   }
   catch(ex) {
     return "<toString() error>";
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -14,28 +14,30 @@ let defaultGlobals = override(require('.
   console: console
 });
 
 function CustomLoader(module, globals, packaging, overrides={}) {
   let options = packaging || require("@loader/options");
   options = override(options, {
     id: overrides.id || options.id,
     globals: override(defaultGlobals, globals || {}),
-    modules: override(options.modules || {}, {
+    modules: override(override(options.modules || {}, overrides.modules || {}), {
       'sdk/addon/window': addonWindow
     })
   });
 
   let loaderModule = options.isNative ? '../../toolkit/loader' : '../loader/cuddlefish';
   let { Loader } = require(loaderModule);
   let loader = Loader(options);
   let wrapper = Object.create(loader, descriptor({
     require: Require(loader, module),
     sandbox: function(id) {
       let requirement = loader.resolve(id, module.id);
+      if (!requirement)
+        requirement = id;
       let uri = resolveURI(requirement, loader.mapping);
       return loader.sandboxes[uri];
     },
     unload: function(reason) {
       unload(loader, reason);
     }
   }));
   ensure(wrapper);
@@ -68,23 +70,23 @@ exports.LoaderWithHookedConsole = functi
     messages.push({ type: type, msg: msg, innerID: innerID });
     if (callback)
       callback(type, msg, innerID);
   }
 
   return {
     loader: CustomLoader(module, {
       console: new HookedPlainTextConsole(hook, null, null)
-    }, override(require("@loader/options"), {
+    }, null, {
       modules: {
         'sdk/console/plain-text': {
           PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
         }
       }
-    })),
+    }),
     messages: messages
   };
 }
 
 // Same than LoaderWithHookedConsole with lower level, instead we get what is
 // actually printed to the command line console
 exports.LoaderWithHookedConsole2 = function (module, callback) {
   let messages = [];
@@ -107,16 +109,16 @@ exports.LoaderWithFilteredConsole = func
   function hook(type, innerID, msg) {
     if (callback && callback(type, msg, innerID) == false)
       return;
     console[type](msg);
   }
 
   return CustomLoader(module, {
     console: new HookedPlainTextConsole(hook, null, null)
-  }, override(require("@loader/options"), {
+  }, null, {
     modules: {
       'sdk/console/plain-text': {
         PlainTextConsole: HookedPlainTextConsole.bind(null, hook)
       }
     }
-  }));
+  });
 }
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -709,17 +709,18 @@ const Loader = iced(function Loader(opti
     metadata, sharedGlobal, sharedGlobalBlacklist
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
     resolve: options.isNative ?
-      exports.nodeResolve :
+      // Make the returned resolve function have the same signature
+      (id, requirer) => exports.nodeResolve(id, requirer, { rootURI: rootURI }) :
       exports.resolve,
     sharedGlobalBlacklist: ["sdk/indexed-db"]
   }, options);
 
   // We create an identity object that will be dispatched on an unload
   // event as subject. This way unload listeners will be able to assert
   // which loader is unloaded. Please note that we intentionally don't
   // use `loader` as subject to prevent a loader access leakage through
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -229,16 +229,21 @@ parser_groups = (
         (("", "--output-file",), dict(dest="output_file",
                                       help="Where to put the finished .xpi",
                                       default=None,
                                       cmds=['xpi'])),
         (("", "--manifest-overload",), dict(dest="manifest_overload",
                                       help="JSON file to overload package.json properties",
                                       default=None,
                                       cmds=['xpi'])),
+        (("", "--abort-on-missing-module",), dict(dest="abort_on_missing",
+                                      help="Abort if required module is missing",
+                                      action="store_true",
+                                      default=False,
+                                      cmds=['test', 'run', 'xpi', 'testpkgs'])),
         ]
      ),
 
     ("Internal Command-Specific Options", [
         (("", "--addons",), dict(dest="addons",
                                  help=("paths of addons to install, "
                                        "comma-separated"),
                                  metavar=None,
@@ -646,17 +651,18 @@ def run(arguments=sys.argv[1:], target_c
     if options.manifest_overload:
         for k, v in packaging.load_json_file(options.manifest_overload).items():
             target_cfg[k] = v
 
     # At this point, we're either building an XPI or running Jetpack code in
     # a Mozilla application (which includes running tests).
 
     use_main = False
-    inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
+    inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory',
+                         'abort_on_missing']
     enforce_timeouts = False
 
     if command == "xpi":
         use_main = True
     elif command == "test":
         if 'tests' not in target_cfg:
             target_cfg['tests'] = []
         inherited_options.extend(['iterations', 'filter', 'profileMemory',
@@ -741,19 +747,19 @@ def run(arguments=sys.argv[1:], target_c
     loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)]
     scan_tests = command == "test"
     test_filter_re = None
     if scan_tests and options.filter:
         test_filter_re = options.filter
         if ":" in options.filter:
             test_filter_re = options.filter.split(":")[0]
     try:
-        manifest = build_manifest(target_cfg, pkg_cfg, deps,
-                                  scan_tests, test_filter_re,
-                                  loader_modules)
+        manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
+                                  test_filter_re, loader_modules,
+                                  abort_on_missing=options.abort_on_missing)
     except ModuleNotFoundError, e:
         print str(e)
         sys.exit(1)
     except BadChromeMarkerError, e:
         # An error had already been displayed on stderr in manifest code
         sys.exit(1)
     used_deps = manifest.get_used_packages()
     if command == "test":
@@ -783,16 +789,20 @@ def run(arguments=sys.argv[1:], target_c
     # When cfx is run from sdk root directory, we will strip sdk modules and
     # override them with local modules.
     # So that integration tools will continue to work and use local modules
     if os.getcwd() == env_root:
         options.bundle_sdk = True
         options.force_use_bundled_sdk = False
         options.overload_modules = True
 
+    if options.pkgdir == env_root:
+        options.bundle_sdk = True
+        options.overload_modules = True
+
     extra_environment = {}
     if command == "test":
         # This should be contained in the test runner package.
         harness_options['main'] = 'sdk/test/runner'
         harness_options['mainPath'] = 'sdk/test/runner'
     else:
         harness_options['main'] = target_cfg.get('main')
         harness_options['mainPath'] = manifest.top_path
--- a/addon-sdk/source/python-lib/cuddlefish/manifest.py
+++ b/addon-sdk/source/python-lib/cuddlefish/manifest.py
@@ -176,28 +176,29 @@ class ModuleInfo:
     def __repr__(self):
         return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name,
                                                    self.section,
                                                    self.name,
                                                    self.js, self.docs)
 
 class ManifestBuilder:
     def __init__(self, target_cfg, pkg_cfg, deps, extra_modules,
-                 stderr=sys.stderr):
+                 stderr=sys.stderr, abort_on_missing=False):
         self.manifest = {} # maps (package,section,module) to ManifestEntry
         self.target_cfg = target_cfg # the entry point
         self.pkg_cfg = pkg_cfg # all known packages
         self.deps = deps # list of package names to search
         self.used_packagenames = set()
         self.stderr = stderr
         self.extra_modules = extra_modules
         self.modules = {} # maps ModuleInfo to URI in self.manifest
         self.datamaps = {} # maps package name to DataMap instance
         self.files = [] # maps manifest index to (absfn,absfn) js/docs pair
         self.test_modules = [] # for runtime
+        self.abort_on_missing = abort_on_missing # cfx eol
 
     def build(self, scan_tests, test_filter_re):
         """
         process the top module, which recurses to process everything it reaches
         """
         if "main" in self.target_cfg:
             top_mi = self.find_top(self.target_cfg)
             top_me = self.process_module(top_mi)
@@ -411,16 +412,22 @@ class ManifestBuilder:
                 looked_in = [] # populated by subroutines
                 them_me = self.find_req_for(mi, reqname, looked_in, locations)
                 if them_me is None:
                     if mi.section == "tests":
                         # tolerate missing modules in tests, because
                         # test-securable-module.js, and the modules/red.js
                         # that it imports, both do that intentionally
                         continue
+                    if not self.abort_on_missing:
+                        # print a warning, but tolerate missing modules
+                        # unless cfx --abort-on-missing-module flag was set
+                        print >>self.stderr, "Warning: missing module: %s" % reqname
+                        me.add_requirement(reqname, reqname)
+                        continue
                     lineno = locations.get(reqname) # None means define()
                     if lineno is None:
                         reqtype = "define"
                     else:
                         reqtype = "require"
                     err = ModuleNotFoundError(reqtype, reqname,
                                               mi.js, lineno, looked_in)
                     raise err
@@ -628,17 +635,17 @@ class ManifestBuilder:
                     maybe_docs = os.path.join(pkg.root_dir, "docs",
                                               basename+".md")
                     if section == "lib" and os.path.exists(maybe_docs):
                         docs = maybe_docs
                     return ModuleInfo(pkg, section, name, js, docs)
         return None
 
 def build_manifest(target_cfg, pkg_cfg, deps, scan_tests,
-                   test_filter_re=None, extra_modules=[]):
+                   test_filter_re=None, extra_modules=[], abort_on_missing=False):
     """
     Perform recursive dependency analysis starting from entry_point,
     building up a manifest of modules that need to be included in the XPI.
     Each entry will map require() names to the URL of the module that will
     be used to satisfy that dependency. The manifest will be used by the
     runtime's require() code.
 
     This returns a ManifestBuilder object, with two public methods. The
@@ -654,17 +661,18 @@ def build_manifest(target_cfg, pkg_cfg, 
 
      * local disk name
      * name in the XPI
 
     note: we don't build the XPI here, but our manifest is passed to the
     code which does, so it knows what to copy into the XPI.
     """
 
-    mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules)
+    mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules,
+                          abort_on_missing=abort_on_missing)
     mxt.build(scan_tests, test_filter_re)
     return mxt
 
 
 
 COMMENT_PREFIXES = ["//", "/*", "*", "dump("]
 
 REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)"
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py
@@ -43,30 +43,46 @@ class Basic(unittest.TestCase):
         # target_cfg.dependencies is not provided, so we'll search through
         # all known packages (everything in 'deps').
         m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
         m = m.get_harness_options_manifest(False)
 
         def assertReqIs(modname, reqname, path):
             reqs = m["one/%s" % modname]["requirements"]
             self.failUnlessEqual(reqs[reqname], path)
+
         assertReqIs("main", "sdk/panel", "sdk/panel")
         assertReqIs("main", "two.js", "one/two")
         assertReqIs("main", "./two", "one/two")
         assertReqIs("main", "sdk/tabs.js", "sdk/tabs")
         assertReqIs("main", "./subdir/three", "one/subdir/three")
         assertReqIs("two", "main", "one/main")
         assertReqIs("subdir/three", "../main", "one/main")
 
         target_cfg.dependencies = []
+
+        try:
+            # this should now work, as we ignore missing modules by default
+            m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False)
+            m = m.get_harness_options_manifest(False)
+
+            assertReqIs("main", "sdk/panel", "sdk/panel")
+            # note that with "addon-sdk" dependency present,
+            # "sdk/tabs.js" mapped to "sdk/tabs", but without,
+            # we just get the default (identity) mapping
+            assertReqIs("main", "sdk/tabs.js", "sdk/tabs.js")
+        except Exception, e:
+            self.fail("Must not throw from build_manifest() if modules are missing")
+
         # now, because .dependencies *is* provided, we won't search 'deps',
-        # so we'll get a link error
+        # and stop_on_missing is True, we'll get a link error
         self.assertRaises(manifest.ModuleNotFoundError,
                           manifest.build_manifest,
-                          target_cfg, pkg_cfg, deps, scan_tests=False)
+                          target_cfg, pkg_cfg, deps, scan_tests=False,
+                          abort_on_missing=True)
 
     def test_main_in_deps(self):
         target_cfg = self.get_pkg("three")
         package_path = [get_linker_files_dir("three-deps")]
         pkg_cfg = packaging.build_config(ROOT, target_cfg,
                                          packagepath=package_path)
         deps = packaging.get_deps_for_targets(pkg_cfg,
                                               [target_cfg.name, "addon-sdk"])
--- a/addon-sdk/source/test/addons/e10s-tabs/lib/main.js
+++ b/addon-sdk/source/test/addons/e10s-tabs/lib/main.js
@@ -1,13 +1,19 @@
 /* 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/. */
 'use strict';
 
 const { merge } = require('sdk/util/object');
+const { get } = require('sdk/preferences/service');
 
 merge(module.exports, require('./test-tab'));
 merge(module.exports, require('./test-tab-events'));
 merge(module.exports, require('./test-tab-observer'));
 merge(module.exports, require('./test-tab-utils'));
 
+// e10s tests should not ride the train to aurora
+if (get('app.update.channel') !== 'nightly') {
+  module.exports = {};
+}
+
 require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/packaging/main.js
@@ -0,0 +1,46 @@
+/* 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/. */
+"use strict";
+
+var options = require("@loader/options");
+
+exports.testPackaging = function(assert) {
+  assert.equal(options.metadata.description,
+                   "Add-on development made easy.",
+                   "packaging metadata should be available");
+  try {
+    options.metadata.description = 'new description';
+    assert.fail('should not have been able to set options.metadata property');
+  }
+  catch (e) {}
+
+  assert.equal(options.metadata.description,
+                   "Add-on development made easy.",
+                   "packaging metadata should be frozen");
+
+  assert.equal(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be undefined");
+  assert.equal(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+  assert.equal(options['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata['private-browsing'] = true;
+    assert.fail('should not have been able to set options.metadata property');
+  }
+  catch(e) {}
+  assert.equal(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata.permissions['private-browsing'] = true;
+    assert.fail('should not have been able to set options.metadata.permissions property');
+  }
+  catch (e) {}
+  assert.equal(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+};
+
+require("sdk/test/runner").runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/packaging/package.json
@@ -0,0 +1,4 @@
+{
+  "id": "test-packaging",
+  "description": "Add-on development made easy."
+}
--- a/addon-sdk/source/test/addons/self/main.js
+++ b/addon-sdk/source/test/addons/self/main.js
@@ -12,9 +12,13 @@ exports["test self.data.load"] = assert 
                "# hello world",
                "paths work");
 
   assert.equal(self.data.load("./data.md").trim(),
                "# hello world",
                "relative paths work");
 };
 
+exports["test self.id"] = assert => {
+  assert.equal(self.id, "test-self@jetpack", "self.id should be correct.");
+};
+
 require("sdk/test/runner").runTestsFromModule(module);
--- a/addon-sdk/source/test/event/helpers.js
+++ b/addon-sdk/source/test/event/helpers.js
@@ -1,16 +1,65 @@
 /* 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/. */
 
 "use strict";
 
 const { on, once, off, emit, count } = require("sdk/event/core");
 
+const { setImmediate, setTimeout } = require("sdk/timers");
+const { defer } = require("sdk/core/promise");
+
+/**
+ * Utility function that returns a promise once the specified event's `type`
+ * is emitted on the given `target`, or the delay specified is passed.
+ *
+ * @param {Object|Number} [target]
+ *    The delay to wait, or the object that receives the event.
+ *    If not given, the function returns a promise that will be resolved
+ *    as soon as possible.
+ * @param {String} [type]
+ *    A string representing the event type to waiting for.
+ * @param {Boolean} [capture]
+ *    If `true`, `capture` indicates that the user wishes to initiate capture.
+ *
+ * @returns {Promise}
+ *    A promise resolved once the delay given is passed, or the object
+ *    receives the event specified
+ */
+const wait = (target, type, capture) => {
+  let { promise, resolve, reject } = defer();
+
+  if (!arguments.length) {
+    setImmediate(resolve);
+  }
+  else if (typeof(target) === "number") {
+    setTimeout(resolve, target);
+  }
+  else if (typeof(target.once) === "function") {
+    target.once(type, resolve);
+  }
+  else if (typeof(target.addEventListener) === "function") {
+    target.addEventListener(type, function listener(...args) {
+      this.removeEventListener(type, listener, capture);
+      resolve(...args);
+    }, capture);
+  }
+  else if (typeof(target) === "object" && target !== null) {
+    once(target, type, resolve);
+  }
+  else {
+    reject('Invalid target given.');
+  }
+
+  return promise;
+};
+exports.wait = wait;
+
 function scenario(setup) {
   return function(unit) {
     return function(assert) {
       let actual = [];
       let input = {};
       unit(input, function(output, events, expected, message) {
         let result = setup(output, expected, actual);
 
@@ -51,9 +100,9 @@ exports.ignoreNew = scenario(function(ou
 exports.FIFO = scenario(function(target, expected, actual) {
   on(target, "data", function($) actual.push($ + "#1"));
   on(target, "data", function($) actual.push($ + "#2"));
   on(target, "data", function($) actual.push($ + "#3"));
 
   return expected.reduce(function(result, value) {
     return result.concat(value + "#1", value + "#2", value + "#3");
   }, []);
-});
+});
\ No newline at end of file
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -131,16 +131,17 @@ exports["test Create Proxy Test With Eve
   worker.port.on("foo", function () {
     assert.pass("You can use events");
     // And terminate your test with helper.done:
     helper.done();
   });
 
 });
 
+/* Disabled due to bug 1038432
 // Bug 714778: There was some issue around `toString` functions
 //             that ended up being shared between content scripts
 exports["test Shared To String Proxies"] = createProxyTest("", function(helper) {
 
   let worker = helper.createWorker(
     'new ' + function ContentScriptScope() {
       // We ensure that `toString` can't be modified so that nothing could
       // leak to/from the document and between content scripts
@@ -160,17 +161,17 @@ exports["test Shared To String Proxies"]
                "document.location.toString is different for each content script");
         assert(document.location.toString() == "data:text/html;charset=utf-8,",
                "Second document.location.toString()");
         done();
       }
     );
   });
 });
-
+*/
 
 // Ensure that postMessage is working correctly across documents with an iframe
 let html = '<iframe id="iframe" name="test" src="data:text/html;charset=utf-8," />';
 exports["test postMessage"] = createProxyTest(html, function (helper, assert) {
   let ifWindow = helper.xrayWindow.document.getElementById("iframe").contentWindow;
   // Listen without proxies, to check that it will work in regular case
   // simulate listening from a web document.
   ifWindow.addEventListener("message", function listener(event) {
deleted file mode 100644
--- a/addon-sdk/source/test/test-packaging.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* 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/. */
- "use strict";
-
-var options = require("@loader/options");
-
-exports.testPackaging = function(assert) {
-  assert.equal(options.metadata.description,
-                   "Add-on development made easy.",
-                   "packaging metadata should be available");
-  try {
-    options.metadata.description = 'new description';
-    assert.fail('should not have been able to set options.metadata property');
-  }
-  catch (e) {}
-
-  assert.equal(options.metadata.description,
-                   "Add-on development made easy.",
-                   "packaging metadata should be frozen");
-
-  assert.equal(options.metadata.permissions['private-browsing'], undefined,
-                   "private browsing metadata should be undefined");
-  assert.equal(options.metadata['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-  assert.equal(options['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-
-  try {
-    options.metadata['private-browsing'] = true;
-    assert.fail('should not have been able to set options.metadata property');
-  }
-  catch(e) {}
-  assert.equal(options.metadata['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-
-  try {
-    options.metadata.permissions['private-browsing'] = true;
-    assert.fail('should not have been able to set options.metadata.permissions property');
-  }
-  catch (e) {}
-  assert.equal(options.metadata.permissions['private-browsing'], undefined,
-                   "private browsing metadata should be be frozen");
-};
-
-require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -7,26 +7,28 @@ module.metadata = {
   'engines': {
     'Firefox': '*'
   }
 };
 
 const { Cc, Ci } = require("chrome");
 const { Loader } = require('sdk/test/loader');
 const { LoaderWithHookedConsole } = require("sdk/test/loader");
-const timer = require("sdk/timers");
+const { setTimeout } = require("sdk/timers");
 const self = require('sdk/self');
 const { open, close, focus, ready } = require('sdk/window/helpers');
 const { isPrivate } = require('sdk/private-browsing');
 const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
 const { defer, all } = require('sdk/core/promise');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { getWindow } = require('sdk/panel/window');
 const { pb } = require('./private-browsing/helper');
 const { URL } = require('sdk/url');
+const { wait } = require('./event/helpers');
+
 const fixtures = require('./fixtures')
 
 const SVG_URL = fixtures.url('mofo_logo.SVG');
 const CSS_URL = fixtures.url('css-include-file.css');
 
 const Isolate = fn => '(' + fn + ')()';
 
 function ignorePassingDOMNodeWarning(type, message) {
@@ -539,17 +541,17 @@ function makeEventOrderTest(options) {
 
     let panel = Panel({ contentURL: "about:buildconfig" });
 
     function expect(event, cb) {
       expectedEvents.push(event);
       panel.on(event, function() {
         assert.equal(event, expectedEvents.shift());
         if (cb)
-          timer.setTimeout(cb, 1);
+          setTimeout(cb, 1);
       });
       return {then: expect};
     }
 
     options.test(assert, done, expect, panel);
   }
 }
 
@@ -1051,16 +1053,146 @@ exports['test panel CSS list'] = functio
         loader.unload();
       }).then(done, assert.fail);
     }
   });
 
   panel.show();
 };
 
+exports['test panel contextmenu validation'] = function(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+
+  let panel = Panel({});
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option is `false` by default');
+
+  panel.destroy();
+
+  panel = Panel({
+    contextMenu: false
+  });
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option is `false`');
+
+  panel.contextMenu = true;
+
+  assert.equal(panel.contextMenu, true,
+    'contextMenu option accepts boolean values');
+
+  panel.destroy();
+
+  panel = Panel({
+    contextMenu: true
+  });
+
+  assert.equal(panel.contextMenu, true,
+    'contextMenu option is `true`');
+
+  panel.contextMenu = false;
+
+  assert.equal(panel.contextMenu, false,
+    'contextMenu option accepts boolean values');
+  
+  assert.throws(() =>
+    Panel({contextMenu: 1}),
+    /The option "contextMenu" must be one of the following types: boolean, undefined, null/,
+    'contextMenu only accepts boolean or nil values');
+
+  panel = Panel();
+
+  assert.throws(() =>
+    panel.contextMenu = 1,
+    /The option "contextMenu" must be one of the following types: boolean, undefined, null/,
+    'contextMenu only accepts boolean or nil values');
+
+  loader.unload();
+}
+
+exports['test panel contextmenu enabled'] = function*(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+  const { getActiveView } = loader.require('sdk/view/core');
+  const { getContentDocument } = loader.require('sdk/panel/utils');
+
+  let contextmenu = getMostRecentBrowserWindow().
+                      document.getElementById("contentAreaContextMenu");
+
+  let panel = Panel({contextMenu: true});
+
+  panel.show();
+
+  yield wait(panel, 'show');
+
+  let view = getActiveView(panel);
+  let window = getContentDocument(view).defaultView;
+
+  let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                                    getInterface(Ci.nsIDOMWindowUtils);
+
+  yield ready(window);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu must be closed');
+
+  sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
+
+  yield wait(contextmenu, 'popupshown');
+
+  assert.equal(contextmenu.state, 'open',
+    'contextmenu is opened');
+
+  contextmenu.hidePopup();
+
+  loader.unload();
+}
+
+exports['test panel contextmenu disabled'] = function*(assert) {
+  const loader = Loader(module);
+  const { Panel } = loader.require('sdk/panel');
+  const { getActiveView } = loader.require('sdk/view/core');
+  const { getContentDocument } = loader.require('sdk/panel/utils');
+
+  let contextmenu = getMostRecentBrowserWindow().
+                      document.getElementById("contentAreaContextMenu");
+  let listener = () => assert.fail('popupshown should never be called');
+
+  let panel = Panel();
+
+  panel.show();
+
+  yield wait(panel, 'show');
+
+  let view = getActiveView(panel);
+  let window = getContentDocument(view).defaultView;
+
+  let { sendMouseEvent } = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                                    getInterface(Ci.nsIDOMWindowUtils);
+
+  yield ready(window);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu must be closed');
+  
+  sendMouseEvent('contextmenu', 20, 20, 2, 1, 0);
+
+  contextmenu.addEventListener('popupshown', listener);
+
+  yield wait(1000);
+
+  contextmenu.removeEventListener('popupshown', listener);
+
+  assert.equal(contextmenu.state, 'closed',
+    'contextmenu was never open');
+
+  loader.unload();
+}
 
 if (isWindowPBSupported) {
   exports.testGetWindow = function(assert, done) {
     let activeWindow = getMostRecentBrowserWindow();
     open(null, { features: {
       toolbar: true,
       chrome: true,
       private: true
--- a/addon-sdk/source/test/test-plain-text-console.js
+++ b/addon-sdk/source/test/test-plain-text-console.js
@@ -229,16 +229,18 @@ exports.testPlainTextConsoleBoundMethods
 
   restorePrefs();
 };
 
 exports.testConsoleInnerID = function(assert) {
   let Console = require("sdk/console/plain-text").PlainTextConsole;
   let { log, info, warn, error, debug, exception, trace } = new Console(function() {}, "test ID");
 
+  prefs.set(SDK_LOG_LEVEL_PREF, "all");
+
   let messages = [];
   function onMessage({ subject }) {
     let message = subject.wrappedJSObject;
     messages.push({ msg: message.arguments[0], type: message.level, innerID: message.innerID });
   }
 
   const system = require("sdk/system/events");
   system.on("console-api-log-event", onMessage);
@@ -248,16 +250,18 @@ exports.testConsoleInnerID = function(as
   error("Test error");
 
   assert.equal(messages.length, 3, "Should see 3 log events");
   assert.deepEqual(messages[0], { msg: "Test log", type: "log", innerID: "test ID" }, "Should see the right event");
   assert.deepEqual(messages[1], { msg: "Test warning", type: "warn", innerID: "test ID" }, "Should see the right event");
   assert.deepEqual(messages[2], { msg: "Test error", type: "error", innerID: "test ID" }, "Should see the right event");
 
   system.off("console-api-log-event", onMessage);
+
+  restorePrefs();
 };
 
 function restorePrefs() {
   if (HAS_ORIGINAL_ADDON_LOG_LEVEL)
     prefs.set(ADDON_LOG_LEVEL_PREF, ORIGINAL_ADDON_LOG_LEVEL);
   else
     prefs.reset(ADDON_LOG_LEVEL_PREF);
 
--- a/addon-sdk/source/test/test-require.js
+++ b/addon-sdk/source/test/test-require.js
@@ -46,18 +46,22 @@ function checkError (assert, name, e) {
   else {
     assert.equal(msg.indexOf('Error: you must provide a module name when calling require() from '), 0);
     assert.ok(msg.indexOf("test-require") !== -1, msg);
   }
 
   // we'd also like to assert that the right filename
   // and linenumber is in the stacktrace
   let tb = traceback.fromException(e);
-  // Get the second to last frame, as the last frame is inside
-  // toolkit/loader
-  let lastFrame = tb[tb.length-2];
+
+  // The last frame may be inside a loader
+  let lastFrame = tb[tb.length - 1];
+  if (lastFrame.fileName.indexOf("toolkit/loader.js") !== -1 ||
+      lastFrame.fileName.indexOf("sdk/loader/cuddlefish.js") !== -1)
+    lastFrame = tb[tb.length - 2];
+
   assert.ok(lastFrame.fileName.indexOf("test-require.js") !== -1,
                           'Filename found in stacktrace');
   assert.equal(lastFrame.lineNumber, REQUIRE_LINE_NO,
                           'stacktrace has correct line number');
 }
 
 require('test').run(exports);
--- a/addon-sdk/source/test/test-self.js
+++ b/addon-sdk/source/test/test-self.js
@@ -35,37 +35,16 @@ exports.testSelf = function(assert) {
                                                             : "startup";
   assert.equal(self.loadReason, testLoadReason,
                "self.loadReason is either startup or install on test runs");
 
   assert.equal(self.isPrivateBrowsingSupported, false,
                'usePrivateBrowsing property is false by default');
 };
 
-exports.testSelfID = function(assert, done) {
-  var self = require("sdk/self");
-  // We can't assert anything about the ID inside the unit test right now,
-  // because the ID we get depends upon how the test was invoked. The idea
-  // is that it is supposed to come from the main top-level package's
-  // package.json file, from the "id" key.
-  assert.equal(typeof(self.id), "string", "self.id is a string");
-  assert.ok(self.id.length > 0);
-
-  AddonManager.getAddonByID(self.id, function(addon) {
-    if (!addon) {
-      assert.fail("did not find addon with self.id");
-    }
-    else {
-      assert.pass("found addon with self.id");
-    }
-
-    done();
-  });
-}
-
 exports.testSelfHandlesLackingLoaderOptions = function (assert) {
   let root = module.uri.substr(0, module.uri.lastIndexOf('/'));
   let uri = root + '/fixtures/loader/self/';
   let sdkPath = loaderOptions.paths[''] + 'sdk';
   let loader = Loader({ paths: { '': uri, 'sdk': sdkPath }});
   let program = main(loader, 'main');
   let self = program.self;
   assert.pass("No errors thrown when including sdk/self without loader options");