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