1/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
3/* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7"use strict";
8
9var EXPORTED_SYMBOLS = ["ContentTask"];
10
11const { Promise } = ChromeUtils.import("resource://gre/modules/Promise.jsm");
12const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
13
14const FRAME_SCRIPT = "resource://testing-common/content-task.js";
15
16/**
17 * Keeps track of whether the frame script was already loaded.
18 */
19var gFrameScriptLoaded = false;
20
21/**
22 * Mapping from message id to associated promise.
23 */
24var gPromises = new Map();
25
26/**
27 * Incrementing integer to generate unique message id.
28 */
29var gMessageID = 1;
30
31/**
32 * This object provides the public module functions.
33 */
34var ContentTask = {
35  /**
36   * _testScope saves the current testScope from
37   * browser-test.js. This is used to implement SimpleTest functions
38   * like ok() and is() in the content process. The scope is only
39   * valid for tasks spawned in the current test, so we keep track of
40   * the ID of the first task spawned in this test (_scopeValidId).
41   */
42  _testScope: null,
43  _scopeValidId: 0,
44
45  /**
46   * Creates and starts a new task in a browser's content.
47   *
48   * @param browser A xul:browser
49   * @param arg A single serializable argument that will be passed to the
50   *             task when executed on the content process.
51   * @param task
52   *        - A generator or function which will be serialized and sent to
53   *          the remote browser to be executed. Unlike Task.spawn, this
54   *          argument may not be an iterator as it will be serialized and
55   *          sent to the remote browser.
56   * @return A promise object where you can register completion callbacks to be
57   *         called when the task terminates.
58   * @resolves With the final returned value of the task if it executes
59   *           successfully.
60   * @rejects An error message if execution fails.
61   */
62  spawn: function ContentTask_spawn(browser, arg, task) {
63    // Load the frame script if needed.
64    if (!gFrameScriptLoaded) {
65      Services.mm.loadFrameScript(FRAME_SCRIPT, true);
66      gFrameScriptLoaded = true;
67    }
68
69    let deferred = {};
70    deferred.promise = new Promise((resolve, reject) => {
71      deferred.resolve = resolve;
72      deferred.reject = reject;
73    });
74
75    let id = gMessageID++;
76    gPromises.set(id, deferred);
77
78    browser.messageManager.sendAsyncMessage("content-task:spawn", {
79      id,
80      runnable: task.toString(),
81      arg,
82    });
83
84    return deferred.promise;
85  },
86
87  setTestScope(scope) {
88    this._testScope = scope;
89    this._scopeValidId = gMessageID;
90  },
91};
92
93var ContentMessageListener = {
94  receiveMessage(aMessage) {
95    let id = aMessage.data.id;
96
97    if (id < ContentTask._scopeValidId) {
98      throw new Error("test result returned after test finished");
99    }
100
101    if (aMessage.name == "content-task:complete") {
102      let deferred = gPromises.get(id);
103      gPromises.delete(id);
104
105      if (aMessage.data.error) {
106        deferred.reject(aMessage.data.error);
107      } else {
108        deferred.resolve(aMessage.data.result);
109      }
110    } else if (aMessage.name == "content-task:test-result") {
111      let data = aMessage.data;
112      ContentTask._testScope.record(
113        data.condition,
114        data.name,
115        null,
116        data.stack
117      );
118    } else if (aMessage.name == "content-task:test-info") {
119      ContentTask._testScope.info(aMessage.data.name);
120    } else if (aMessage.name == "content-task:test-todo") {
121      ContentTask._testScope.todo(aMessage.data.expr, aMessage.data.name);
122    } else if (aMessage.name == "content-task:test-todo_is") {
123      ContentTask._testScope.todo_is(
124        aMessage.data.a,
125        aMessage.data.b,
126        aMessage.data.name
127      );
128    }
129  },
130};
131
132Services.mm.addMessageListener("content-task:complete", ContentMessageListener);
133Services.mm.addMessageListener(
134  "content-task:test-result",
135  ContentMessageListener
136);
137Services.mm.addMessageListener(
138  "content-task:test-info",
139  ContentMessageListener
140);
141Services.mm.addMessageListener(
142  "content-task:test-todo",
143  ContentMessageListener
144);
145Services.mm.addMessageListener(
146  "content-task:test-todo_is",
147  ContentMessageListener
148);
149