Bug 1493819 - Provide in-tree document on UA Widgets r=bgrins,bholley
☠☠ backed out by c35093e35345 ☠ ☠
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 05 Oct 2018 03:34:18 +0000
changeset 495483 a74eb57d75ffe3ba2b49070fe2723b39c84f6d56
parent 495482 7d15211dbdb5a32d87ce9c2cf414e6137b2b3df3
child 495484 cff04dbdad5134c2583c86bb7f8472828f0afc49
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins, bholley
bugs1493819
milestone64.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 1493819 - Provide in-tree document on UA Widgets r=bgrins,bholley Differential Revision: https://phabricator.services.mozilla.com/D7545
testing/mochitest/tests/Harness_sanity/head_with_add_task.js
testing/mochitest/tests/Harness_sanity/mochitest.ini
testing/mochitest/tests/Harness_sanity/test_add_task_in_head.html
toolkit/content/moz.build
toolkit/content/widgets/docs/index.rst
toolkit/content/widgets/docs/ua_widget.rst
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/Harness_sanity/head_with_add_task.js
@@ -0,0 +1,26 @@
+"use strict";
+
+// This function here will prevent test() function in the HTML to run.
+//add_task(async function noop() {
+//});
+
+// This does too.
+/*
+add_task(async function noop_with_promise() {
+  await Promise.resolve();
+});
+*/
+
+// This does too.
+/*
+add_task(async function noop_with_one_executeSoon() {
+  await new Promise(resolve => SimpleTest.executeSoon(resolve));
+});
+*/
+
+// While the function here can workaround the bug
+// (need at least two executeSoon())
+add_task(async function workaround_function_in_header() {
+  await new Promise(resolve => SimpleTest.executeSoon(resolve));
+  await new Promise(resolve => SimpleTest.executeSoon(resolve));
+});
--- a/testing/mochitest/tests/Harness_sanity/mochitest.ini
+++ b/testing/mochitest/tests/Harness_sanity/mochitest.ini
@@ -38,8 +38,10 @@ subsuite = clipboard
 skip-if = toolkit == 'android'  # bug 688052
 [test_sanity_manifest.html]
 skip-if = toolkit == 'android' # we use the old manifest style on android
 fail-if = true
 [test_sanity_manifest_pf.html]
 skip-if = toolkit == 'android' # we use the old manifest style on android
 fail-if = true
 [test_sanity_waitForCondition.html]
+[test_add_task_in_head.html]
+support-files = head_with_add_task.js
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/Harness_sanity/test_add_task_in_head.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for mochitest with add_task</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
+  <script type="text/javascript" src="head_with_add_task.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<div id="content" style="display: none">
+
+</div>
+<script class="testbody" type="text/javascript">
+
+info("The <script> block runs");
+
+add_task(async function test() {
+  ok(true, "test function runs");
+});
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/toolkit/content/moz.build
+++ b/toolkit/content/moz.build
@@ -24,16 +24,18 @@ if CONFIG['OS_TARGET'] == 'Android':
 if CONFIG['MOZ_INSTALL_TRACKING']:
     DEFINES['MOZ_INSTALL_TRACKING'] = 1
 
 if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
     DEFINES['MOZ_FENNEC'] = True
 
 JAR_MANIFESTS += ['jar.mn']
 
+SPHINX_TREES['toolkit_widgets'] = 'widgets/docs'
+
 DEFINES['TOPOBJDIR'] = TOPOBJDIR
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 with Files('aboutTelemetry.*'):
     BUG_COMPONENT = ('Toolkit', 'Telemetry')
 
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/docs/index.rst
@@ -0,0 +1,10 @@
+===============
+Toolkit Widgets
+===============
+
+The ``/toolkit/content/widgets`` directory contains XBL bindings, Mozilla Custom Elements, and UA Widgets usable for all applications.
+
+.. toctree::
+  :maxdepth: 1
+
+  ua_widget
new file mode 100644
--- /dev/null
+++ b/toolkit/content/widgets/docs/ua_widget.rst
@@ -0,0 +1,48 @@
+UA Widgets
+==========
+
+Introduction
+------------
+
+UA Widgets are intended to be a replacement of our usage of XBL bindings in web content. These widgets run JavaScript inside extended principal per-origin sandboxes. They insert their own DOM inside of a special, closed Shadow Root inaccessible to the page, called a UA Widget Shadow Root.
+
+UA Widget lifecycle
+-------------------
+
+UA Widgets are generally constructed when the element is appended to the document and destroyed when the element is removed from the tree. Yet, in order to be fast, specialization was made to each of the widgets.
+
+When the element is appended to the tree, a chrome-only ``UAWidgetBindToTree`` event is dispatched and is caught by a frame script, namely UAWidgetsChild.
+
+UAWidgetsChild then grabs the sandbox for that origin (lazily creating it as needed), loads the script as needed, and initializes an instance by calling the JS constructor with a reference to the UA Widget Shadow Root created by the DOM. We will discuss the sandbox in the latter section.
+
+When the element is removed from the tree, ``UAWidgetUnbindFromTree`` is dispatched so UAWidgetsChild can destroy the widget, if it exists. If so, the UAWidgetsChild calls the ``destructor()`` method on the widget, causing the widget to destruct itself.
+
+When a UA Widget initializes, it should create its own DOM inside the passed UA Widget Shadow Root, including the ``<link>`` element necessary to load the stylesheet, add event listeners, etc. When destroyed (i.e. the destructor method is called), it should do the opposite.
+
+**Specialization**: for video controls, we do not want to do the work if the control is not needed (i.e. when the ``<video>`` or ``<audio>`` element has no "controls" attribute set), so we forgo dispatching the event from HTMLMediaElement in the BindToTree method. Instead, a ``UAWidgetAttributeChanged`` event will cause the sandbox and the widget instance to construct when the attribute is set to true. The same event is also responsible for triggering the ``onattributechange()`` method on UA Widgets if the widget is already initialized.
+
+The specialization does not apply to the lifecycle of the UA Widget Shadow Root. It is always constructed in order to suppress children of the DOM element from the web content from receiving a layout frame.
+
+UA Widget Shadow Root
+---------------------
+
+The UA Widget Shadow Root is a closed shadow root, with the UA Widget flag turned on. As a closed shadow root, it may not be accessed by other scripts. It is attached on host element which the spec disallow a shadow root to be attached.
+
+The UA Widget flag enables the security feature covered in the next section.
+
+The JavaScript sandbox
+----------------------
+
+The sandboxes created for UA Widgets are per-origin and set to the expanded principal. This allows the script to access other DOM APIs unavailable to the web content, while keeping its principal tied to the document origin. They are created as needed, backing the lifecycle of the UA Widgets as previously mentioned. These sandbox globals are not associated with any window object because they are shared across all same-origin documents. It is the job of the UA Widget script to hold and manage the references of the window and document objects the widget is being initiated on, by accessing them from the UA Widget Shadow Root instance passed.
+
+While the closed shadow root technically prevents content from accessing the contents, we want a stronger guarantee to protect against accidental leakage of references to the UA Widget shadow tree into content script. Access to the UA Widget DOM is restricted by having their reflectors set in the UA Widgets scope, as opposed to the normal scope. To accomplish this, we avoid having any script (UA Widget script included) getting a hold of the reference of any created DOM element before appending to the Shadow DOM. Once the element is in the Shadow DOM, the binding mechanism will put the reflector in the desired scope as it is being accessed.
+
+To avoid creating reflectors before DOM insertion, the available DOM interfaces is limited. For example, instead of ``createElement()`` and ``appendChild()``, the script would have to call ``createElementAndAppendChildAt()`` available on the UA Widget Shadow Root instance, to avoid receiving a reference to the DOM element and thus triggering the creation of its reflector in the wrong scope, before the element is properly associated with the UA Widget shadow tree. To find out the differences, search for ``Func="IsChromeOrXBLOrUAWidget"`` and ``Func="IsNotUAWidget"`` in in-tree WebIDL files.
+
+Other things to watch out for
+-----------------------------
+
+As part of the implementation of the Web Platform, it is important to make sure the web-observable characteristics of the widget correctly reflect what the script on the web expects.
+
+* Do not dispatch non-spec compliant events on the UA Widget Shadow Root host element, as event listeners in web content scripts can access them.
+* The layout and the dimensions of the widget should be ready by the time the constructor returns, since they can be detectable as soon as the content script gets the reference of the host element (i.e. when ``appendChild()`` returns). In order to make this easier we load ``<link>`` elements load chrome stylesheets synchronously when inside a UA Widget Shadow DOM.