Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 11 Oct 2014 16:24:05 -0400
changeset 233199 bdba65a0ead7241c34a19fd6fb44d518755cb8f9
parent 233192 44168a7af20de3db73f0a7ed63abd923ad3a3732 (current diff)
parent 233198 44cb72be622dc5581a944eab309738232ab104c3 (diff)
child 233200 d18f4f55dd4e5e845a0ae6d6a22b4dc2bdd7f84a
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.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
Merge m-c to fx-team. a=merge
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -1012,20 +1012,16 @@ let SessionStoreInternal = {
 
       if (isFullyLoaded) {
         winData.title = tabbrowser.selectedBrowser.contentTitle || tabbrowser.selectedTab.label;
         winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
                                                   tabbrowser.selectedTab);
         SessionCookies.update([winData]);
       }
 
-      // Until we decide otherwise elsewhere, this window is part of a series
-      // of closing windows to quit.
-      RevivableWindows.add(winData);
-
       // Store the window's close date to figure out when each individual tab
       // was closed. This timestamp should allow re-arranging data based on how
       // recently something was closed.
       winData.closedAt = Date.now();
 
       // Save non-private windows if they have at
       // least one saveable tab or are the last window.
       if (!winData.isPrivate) {
@@ -1046,16 +1042,20 @@ let SessionStoreInternal = {
 
         if (hasSaveableTabs || isLastWindow) {
           // we don't want to save the busy state
           delete winData.busy;
 
           this._closedWindows.unshift(winData);
           this._capClosedWindows();
         }
+
+        // Until we decide otherwise elsewhere, this window
+        // is part of a series of closing windows to quit.
+        RevivableWindows.add(winData);
       }
 
       // clear this window from the list
       delete this._windows[aWindow.__SSi];
 
       // save the state without this window to disk
       this.saveStateDelayed();
     }
--- a/browser/components/sessionstore/test/browser_revive_windows.js
+++ b/browser/components/sessionstore/test/browser_revive_windows.js
@@ -34,16 +34,26 @@ add_task(function* test_revive_windows()
   for (let i = 0; i < 3; i++) {
     let win = yield promiseNewWindow();
     windows.push(win);
 
     let tab = win.gBrowser.addTab("about:mozilla");
     yield promiseBrowserLoaded(tab.linkedBrowser);
   }
 
+  // Create a private window.
+  // This window must not be revived.
+  {
+    let win = yield promiseNewWindow({private: true});
+    windows.push(win);
+
+    let tab = win.gBrowser.addTab("about:mozilla");
+    yield promiseBrowserLoaded(tab.linkedBrowser);
+  }
+
   // Close all windows.
   for (let win of windows) {
     yield promiseWindowClosed(win);
   }
 
   is(ss.getClosedWindowCount(), 1, "one window restorable");
 
   // Save to disk and read.
@@ -134,17 +144,17 @@ add_task(function* test_revive_windows_o
       ok(JSON.stringify(windows[1]).contains(URL_ADD_WINDOW2),
         "correct second additional window");
       ok(JSON.stringify(windows[2]).contains(URL_MAIN_WINDOW),
         "correct main window");
     }
   }
 });
 
-function promiseNewWindow() {
-  return new Promise(resolve => whenNewWindowLoaded({private: false}, resolve));
+function promiseNewWindow(opts = {private: false}) {
+  return new Promise(resolve => whenNewWindowLoaded(opts, resolve));
 }
 
 function forgetClosedWindows() {
   while (ss.getClosedWindowCount()) {
     ss.forgetClosedWindow(0);
   }
 }
--- a/toolkit/devtools/server/actors/string.js
+++ b/toolkit/devtools/server/actors/string.js
@@ -74,32 +74,29 @@ exports.ShortLongString = Class({
 
   release: function() {
     this.str = null;
     return promise.resolve(undefined);
   }
 })
 
 exports.LongStringFront = protocol.FrontClass(exports.LongStringActor, {
-  initialize: function(client, form) {
-    // Don't give the form by default, because we're being tricky and it might just
-    // be a string.
-    protocol.Front.prototype.initialize.call(this, client, null);
-    this.form(form);
+  initialize: function(client) {
+    protocol.Front.prototype.initialize.call(this, client);
   },
 
   destroy: function() {
     this.initial = null;
     this.length = null;
     this.strPromise = null;
     protocol.Front.prototype.destroy.call(this);
   },
 
   form: function(form) {
-    this.actorID = form.actorID;
+    this.actorID = form.actor;
     this.initial = form.initial;
     this.length = form.length;
   },
 
   string: function() {
     if (!this.strPromise) {
       let promiseRest = (thusFar) => {
         if (thusFar.length === this.length)
--- a/toolkit/devtools/server/actors/webaudio.js
+++ b/toolkit/devtools/server/actors/webaudio.js
@@ -293,17 +293,21 @@ let AudioNodeActor = exports.AudioNodeAc
 });
 
 /**
  * The corresponding Front object for the AudioNodeActor.
  */
 let AudioNodeFront = protocol.FrontClass(AudioNodeActor, {
   initialize: function (client, form) {
     protocol.Front.prototype.initialize.call(this, client, form);
-    this.manage(this);
+    // if we were manually passed a form, this was created manually and
+    // needs to own itself for now.
+    if (form) {
+      this.manage(this);
+    }
   }
 });
 
 /**
  * The Web Audio Actor handles simple interaction with an AudioContext
  * high-level methods. After instantiating this actor, you'll need to set it
  * up by calling setup().
  */
@@ -430,16 +434,18 @@ let WebAudioActor = exports.WebAudioActo
    * to hibernation. This method is called automatically just before the
    * actor is destroyed.
    */
   finalize: method(function() {
     if (!this._initialized) {
       return;
     }
     this._initialized = false;
+    systemOff("webaudio-node-demise", this._onDestroyNode);
+
     off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
     off(this.tabActor, "window-ready", this._onGlobalCreated);
     this.tabActor = null;
     this._nativeToActorID = null;
     this._callWatcher.eraseRecording();
     this._callWatcher.finalize();
     this._callWatcher = null;
   }, {
--- a/toolkit/devtools/server/protocol.js
+++ b/toolkit/devtools/server/protocol.js
@@ -140,16 +140,17 @@ function identityWrite(v) {
  * @returns a type object that can be used in protocol definitions.
  */
 types.addType = function(name, typeObject={}, options={}) {
   if (registeredTypes.has(name)) {
     throw Error("Type '" + name + "' already exists.");
   }
 
   let type = object.merge({
+    toString() { return "[protocol type:" + name + "]"},
     name: name,
     primitive: !(typeObject.read || typeObject.write),
     read: identityWrite,
     write: identityWrite
   }, typeObject);
 
   registeredTypes.set(name, type);
 
@@ -253,38 +254,56 @@ types.addActorType = function(name) {
         return ctx.conn.getActor(v);
       }
 
       // Reading a response on the client side, check for an
       // existing front on the connection, and create the front
       // if it isn't found.
       let actorID = typeof(v) === "string" ? v : v.actor;
       let front = ctx.conn.getActor(actorID);
-      if (front) {
-        front.form(v, detail, ctx);
-      } else {
-        front = new type.frontClass(ctx.conn, v, detail, ctx)
+      if (!front) {
+        front = new type.frontClass(ctx.conn);
         front.actorID = actorID;
         ctx.marshallPool().manage(front);
       }
+
+      v = type.formType(detail).read(v, front, detail);
+      front.form(v, detail, ctx);
+
       return front;
     },
     write: (v, ctx, detail) => {
       // If returning a response from the server side, make sure
       // the actor is added to a parent object and return its form.
       if (v instanceof Actor) {
         if (!v.actorID) {
           ctx.marshallPool().manage(v);
         }
-        return v.form(detail);
+        return type.formType(detail).write(v.form(detail), ctx, detail);
       }
 
       // Writing a request from the client side, just send the actor id.
       return v.actorID;
     },
+    formType: (detail) => {
+      if (!("formType" in type.actorSpec)) {
+        return types.Primitive;
+      }
+
+      let formAttr = "formType";
+      if (detail) {
+        formAttr += "#" + detail;
+      }
+
+      if (!(formAttr in type.actorSpec)) {
+        throw new Error("No type defined for " + formAttr);
+      }
+
+      return type.actorSpec[formAttr];
+    }
   }, {
     // We usually freeze types, but actor types are updated when clients are
     // created, so don't freeze yet.
     thawed: true
   });
   return type;
 }
 
@@ -819,16 +838,18 @@ let Actor = Class({
         let sendEvent = this._sendEvent.bind(this, name)
         this.on(name, (...args) => {
           sendEvent.apply(null, args);
         });
       }
     }
   },
 
+  toString: function() { return "[Actor " + this.typeName + "/" + this.actorID + "]" },
+
   _sendEvent: function(name, ...args) {
     if (!this._actorSpec.events.has(name)) {
       // It's ok to emit events that don't go over the wire.
       return;
     }
     let request = this._actorSpec.events.get(name);
     let packet;
     try {
@@ -903,23 +924,34 @@ let actorProto = function(actorProto) {
   if (actorProto._actorSpec) {
     throw new Error("actorProto called twice on the same actor prototype!");
   }
 
   let protoSpec = {
     methods: [],
   };
 
-  // Find method specifications attached to prototype properties.
+  // Find method and form specifications attached to prototype properties.
   for (let name of Object.getOwnPropertyNames(actorProto)) {
     let desc = Object.getOwnPropertyDescriptor(actorProto, name);
     if (!desc.value) {
       continue;
     }
 
+    if (name.startsWith("formType")) {
+      if (typeof(desc.value) === "string") {
+        protoSpec[name] = types.getType(desc.value);
+      } else if (desc.value.name && registeredTypes.has(desc.value.name)) {
+        protoSpec[name] = desc.value;
+      } else {
+        // Shorthand for a newly-registered DictType.
+        protoSpec[name] = types.addDictType(actorProto.typeName + "__" + name, desc.value);
+      }
+    }
+
     if (desc.value._methodSpec) {
       let frozenSpec = desc.value._methodSpec;
       let spec = {};
       spec.name = frozenSpec.name || name;
       spec.request = Request(object.merge({type: spec.name}, frozenSpec.request || undefined));
       spec.response = Response(frozenSpec.response || undefined);
       spec.telemetry = frozenSpec.telemetry;
       spec.release = frozenSpec.release;
@@ -1039,18 +1071,24 @@ let Front = Class({
    *   conn can be null if the subclass provides a conn property.
    * @param optional form
    *   The json form provided by the server.
    * @constructor
    */
   initialize: function(conn=null, form=null, detail=null, context=null) {
     Pool.prototype.initialize.call(this, conn);
     this._requests = [];
+
+    // protocol.js no longer uses this data in the constructor, only external
+    // uses do.  External usage of manually-constructed fronts will be
+    // drastically reduced if we convert the root and tab actors to
+    // protocol.js, in which case this can probably go away.
     if (form) {
       this.actorID = form.actor;
+      form = types.getType(this.typeName).formType(detail).read(form, this, detail);
       this.form(form, detail, context);
     }
   },
 
   destroy: function() {
     // Reject all outstanding requests, they won't make sense after
     // the front is destroyed.
     while (this._requests && this._requests.length > 0) {
@@ -1115,16 +1153,17 @@ let Front = Class({
     let type = packet.type || undefined;
     if (this._clientSpec.events && this._clientSpec.events.has(type)) {
       let event = this._clientSpec.events.get(packet.type);
       let args;
       try {
         args = event.request.read(packet, this);
       } catch(ex) {
         console.error("Error reading event: " + packet.type);
+        console.exception(ex);
         throw ex;
       }
       if (event.pre) {
         event.pre.forEach((pre) => pre.apply(this, args));
       }
       events.emit.apply(null, [this, event.name].concat(args));
       return;
     }
--- a/toolkit/devtools/server/tests/unit/test_protocol_children.js
+++ b/toolkit/devtools/server/tests/unit/test_protocol_children.js
@@ -229,17 +229,19 @@ let RootFront = protocol.FrontClass(Root
     this.actorID = "root";
     protocol.Front.prototype.initialize.call(this, client);
     // Root actor owns itself.
     this.manage(this);
   },
 
   getTemporaryChild: protocol.custom(function(id) {
     if (!this._temporaryHolder) {
-      this._temporaryHolder = this.manage(new protocol.Front(this.conn, {actor: this.actorID + "_temp"}));
+      this._temporaryHolder = protocol.Front(this.conn);
+      this._temporaryHolder.actorID = this.actorID + "_temp";
+      this._temporaryHolder = this.manage(this._temporaryHolder);
     }
     return this._getTemporaryChild(id);
   },{
    impl: "_getTemporaryChild"
   }),
 
   clearTemporaryChildren: protocol.custom(function() {
     if (!this._temporaryHolder) {
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_protocol_formtype.js
@@ -0,0 +1,160 @@
+let protocol = devtools.require("devtools/server/protocol");
+let {method, Arg, Option, RetVal} = protocol;
+
+protocol.types.addActorType("child");
+protocol.types.addActorType("root");
+
+// The child actor doesn't provide a form description
+let ChildActor = protocol.ActorClass({
+  typeName: "child",
+  initialize(conn) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+  },
+
+  form(detail) {
+    return {
+      actor: this.actorID,
+      extra: "extra"
+    }
+  },
+
+  getChild: method(function() {
+    return this;
+  }, {
+    response: RetVal("child")
+  }),
+});
+
+let ChildFront = protocol.FrontClass(ChildActor, {
+  initialize(client) {
+    protocol.Front.prototype.initialize.call(this, client);
+  },
+
+  form(v, ctx, detail) {
+    this.extra = v.extra;
+  }
+});
+
+// The root actor does provide a form description.
+let RootActor = protocol.ActorClass({
+  typeName: "root",
+  initialize(conn) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    this.manage(this);
+    this.child = new ChildActor();
+  },
+
+  // Basic form type, relies on implicit DictType creation
+  formType: {
+    childActor: "child"
+  },
+
+  sayHello() {
+    return {
+      from: "root",
+      applicationType: "xpcshell-tests",
+      traits: []
+    }
+  },
+
+  // This detail uses explicit DictType creation
+  "formType#detail1": protocol.types.addDictType("RootActorFormTypeDetail1", {
+    detailItem: "child"
+  }),
+
+  // This detail a string type.
+  "formType#actorid": "string",
+
+  form(detail) {
+    if (detail === "detail1") {
+      return {
+        actor: this.actorID,
+        detailItem: this.child
+      }
+    } else if (detail === "actorid") {
+      return this.actorID;
+    }
+
+    return {
+      actor: this.actorID,
+      childActor: this.child
+    }
+  },
+
+  getDefault: method(function() {
+    return this;
+  }, {
+    response: RetVal("root")
+  }),
+
+  getDetail1: method(function() {
+    return this;
+  }, {
+    response: RetVal("root#detail1")
+  }),
+
+  getDetail2: method(function() {
+    return this;
+  }, {
+    response: {
+      item: RetVal("root#actorid")
+    }
+  }),
+
+  getUnknownDetail: method(function() {
+    return this;
+  }, {
+    response: RetVal("root#unknownDetail")
+  }),
+});
+
+let RootFront = protocol.FrontClass(RootActor, {
+  initialize(client) {
+    this.actorID = "root";
+    protocol.Front.prototype.initialize.call(this, client);
+
+    // Root owns itself.
+    this.manage(this);
+  },
+
+  form(v, ctx, detail) {
+    this.lastForm = v;
+  }
+});
+
+const run_test = Test(function*() {
+  DebuggerServer.createRootActor = (conn => {
+    return RootActor(conn);
+  });
+  DebuggerServer.init(() => true);
+
+  const connection = DebuggerServer.connectPipe();
+  const conn = new DebuggerClient(connection);
+  const client = Async(conn);
+
+  yield client.connect();
+
+  let rootFront = RootFront(conn);
+
+  // Trigger some methods that return forms.
+  let retval = yield rootFront.getDefault();
+  do_check_true(retval instanceof RootFront);
+  do_check_true(rootFront.lastForm.childActor instanceof ChildFront);
+
+  retval = yield rootFront.getDetail1();
+  do_check_true(retval instanceof RootFront);
+  do_check_true(rootFront.lastForm.detailItem instanceof ChildFront);
+
+  retval = yield rootFront.getDetail2();
+  do_check_true(retval instanceof RootFront);
+  do_check_true(typeof(rootFront.lastForm) === "string");
+
+  // getUnknownDetail should fail, since no typeName is specified.
+  try {
+    yield rootFront.getUnknownDetail();
+    do_check_true(false);
+  } catch(ex) {
+  }
+
+  yield client.close();
+});
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -60,16 +60,17 @@ support-files =
 [test_eval-02.js]
 [test_eval-03.js]
 [test_eval-04.js]
 [test_eval-05.js]
 [test_protocol_async.js]
 [test_protocol_simple.js]
 [test_protocol_longstring.js]
 [test_protocol_children.js]
+[test_protocol_formtype.js]
 [test_breakpoint-01.js]
 [test_register_actor.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpoint-02.js]
 skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpoint-03.js]