jenkins-bot has submitted this change and it was merged.

Change subject: Resolve rendered URLs according to the provided <base>
......................................................................


Resolve rendered URLs according to the provided <base>

This is done by using the computed property value rather than the
literal attribute value when rendering href and src attributes.
Helpfully, this provides perfect URL resolution natively in the browser,
which means the document's <base> is respected and all that good stuff.

For GeneratedContentNodes, we also need to find all DOM elements inside
the rendered DOM that have href or src attributes and resolve those.
This is done in the new getRenderedDomElements() function, which the
existing cleanup steps (remove <link>/<meta>/<style>, clone for
correct document) were moved into.

In order to make sure that the computed values are always computed
correctly, we need to make sure that in cases where HTML strings
in data-mw are parsed, they're parsed in the context of the correct
document so the correct <base> is applied.

We still need to solve this problem for models that actually store and
edit an href or src as an attribute. I'll post more about that on
bug 48915.

Bug: 48915
Change-Id: Iaccb9e3fc05cd151a0f5e632c8d3bd3568735309
---
M modules/ve-mw/ce/annotations/ve.ce.MWInternalLinkAnnotation.js
M modules/ve-mw/dm/nodes/ve.dm.MWReferenceListNode.js
M modules/ve-mw/test/dm/ve.dm.mwExample.js
M modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js
M modules/ve/ce/ve.ce.View.js
M modules/ve/dm/ve.dm.Converter.js
M modules/ve/dm/ve.dm.InternalList.js
M modules/ve/test/dm/ve.dm.InternalList.test.js
M modules/ve/test/dm/ve.dm.example.js
M modules/ve/test/ve.test.utils.js
10 files changed, 356 insertions(+), 107 deletions(-)

Approvals:
  Esanders: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/modules/ve-mw/ce/annotations/ve.ce.MWInternalLinkAnnotation.js 
b/modules/ve-mw/ce/annotations/ve.ce.MWInternalLinkAnnotation.js
index 5d5869d..6cb6e77 100644
--- a/modules/ve-mw/ce/annotations/ve.ce.MWInternalLinkAnnotation.js
+++ b/modules/ve-mw/ce/annotations/ve.ce.MWInternalLinkAnnotation.js
@@ -22,9 +22,14 @@
        // DOM changes
        this.$.addClass( 've-ce-mwInternalLinkAnnotation' );
        this.$.attr( 'title', model.getAttribute( 'title' ) );
-       // Get href from DM rendering
-       dmRendering = model.getDomElements()[0];
-       this.$.attr( 'href', dmRendering.getAttribute( 'href' ) );
+       // HACK get href from DM rendering
+       // HACK HACK except if we already have a computed href
+       // FIXME get rid of this hack, see bug 51487
+       if ( !this.$.attr( 'href' ) ) {
+               dmRendering = model.getDomElements()[0];
+               this.$.attr( 'href', dmRendering.getAttribute( 'href' ) );
+       }
+       // else let the default attribute rendering happen
 };
 
 /* Inheritance */
diff --git a/modules/ve-mw/dm/nodes/ve.dm.MWReferenceListNode.js 
b/modules/ve-mw/dm/nodes/ve.dm.MWReferenceListNode.js
index 66bf96c..a36b556 100644
--- a/modules/ve-mw/dm/nodes/ve.dm.MWReferenceListNode.js
+++ b/modules/ve-mw/dm/nodes/ve.dm.MWReferenceListNode.js
@@ -53,7 +53,7 @@
                }
        };
        if ( mwData.body && mwData.body.html ) {
-               $contents = $( '<div>' ).append( mwData.body.html );
+               $contents = $( '<div>', domElements[0].ownerDocument ).append( 
mwData.body.html );
                contentsData = converter.getDataFromDomRecursionClean( 
$contents[0] );
                return [ referenceListData ].
                        concat( contentsData ).
diff --git a/modules/ve-mw/test/dm/ve.dm.mwExample.js 
b/modules/ve-mw/test/dm/ve.dm.mwExample.js
index bfe6c2b..a65c5a1 100644
--- a/modules/ve-mw/test/dm/ve.dm.mwExample.js
+++ b/modules/ve-mw/test/dm/ve.dm.mwExample.js
@@ -258,7 +258,17 @@
                        'origSortkey': '',
                        'origRel': 'mw:PageProp/Category'
                },
-               'htmlAttributes': [ { 'values': { 'rel': 
'mw:PageProp/Category', 'href': './Category:Bar' } } ]
+               'htmlAttributes': [
+                       {
+                               'values': {
+                                       'rel': 'mw:PageProp/Category',
+                                       'href': './Category:Bar'
+                               },
+                               'computed': {
+                                       'href': 
'http://example.com/Category:Bar'
+                               }
+                       }
+               ]
        },
        { 'type': '/mwCategory' },
        'B',
@@ -306,10 +316,18 @@
                        'origSortkey': 'Bar baz%23quux',
                        'origRel': 'mw:PageProp/Category'
                },
-               'htmlAttributes': [ { 'values':  {
-                       'rel': 'mw:PageProp/Category',
-                       'href': './Category:Foo_foo#Bar baz%23quux'
-               } } ]
+
+               'htmlAttributes': [
+                       {
+                               'values':  {
+                                       'rel': 'mw:PageProp/Category',
+                                       'href': './Category:Foo_foo#Bar 
baz%23quux'
+                               },
+                               'computed': {
+                                       'href': 
'http://example.com/Category:Foo_foo#Bar baz%23quux'
+                               }
+                       }
+               ]
        },
        { 'type': '/mwCategory' },
        {
@@ -367,7 +385,17 @@
                                'sortkey': '',
                                'origSortkey': ''
                        },
-                       'htmlAttributes': [ { 'values': { 'rel': 
'mw:WikiLink/Category', 'href': './Category:Bar' } } ]
+                       'htmlAttributes': [
+                               {
+                                       'values': {
+                                               'rel': 'mw:WikiLink/Category',
+                                               'href': './Category:Bar'
+                                       },
+                                       'computed': {
+                                               'href': 
'http://example.com/Category:Bar'
+                                       }
+                               }
+                       ]
                }
        ],
        undefined,
@@ -956,6 +984,7 @@
                                '</p>' +
                                ve.dm.mwExample.MWReference.referenceList +
                        '</body>',
+               'head': '<base href="http://example.com"; />',
                'data': [
                        { 'type': 'paragraph' },
                        'F', 'o', 'o',
@@ -986,6 +1015,9 @@
                                                        {
                                                                'values': {
                                                                        'href': 
'#cite_note-bar-1'
+                                                               },
+                                                               'computed': {
+                                                                       'href': 
'http://example.com/#cite_note-bar-1'
                                                                }
                                                        }
                                                ]
@@ -1165,9 +1197,16 @@
                                        'refGroup': ''
                                },
                                'htmlAttributes': [ {
-                                       'children': [ { 'values': {
-                                               'href': '#cite_note-foo-3'
-                                       } } ],
+                                       'children': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'#cite_note-foo-3'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/#cite_note-foo-3'
+                                                       }
+                                               }
+                                       ],
                                        'values': {
                                                'about': '#mwt8',
                                                'class': 'reference',
@@ -1193,10 +1232,17 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1209,10 +1255,17 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1225,10 +1278,17 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        { 'type': '/paragraph' },
@@ -1257,6 +1317,7 @@
                        '{&quot;html&quot;:&quot;Foo<!-- bar 
-->&quot;},&quot;attrs&quot;:{}}" ' +
                        'id="cite_ref-1-0" rel="dc:references" 
typeof="mw:Extension/ref" data-parsoid="{}">' +
                        '<a href="#cite_note-bar-1" 
data-parsoid="{}">[1]</a></span></p>',
+               'head': '<base href="http://example.com"; />',
                'data': [
                        { 'type': 'paragraph' },
                        {
@@ -1288,7 +1349,15 @@
                                                        'rel': 'dc:references',
                                                        'typeof': 
'mw:Extension/ref'
                                                },
-                                               'children': [ { 'values': { 
'data-parsoid': '{}', 'href': '#cite_note-bar-1' } } ]
+                                               'children': [
+                                                       {
+                                                               'values': {
+                                                                       
'data-parsoid': '{}',
+                                                                       'href': 
'#cite_note-bar-1'
+                                                               },
+                                                               'computed': {
+                                                                       'href': 
'http://example.com/#cite_note-bar-1'
+                                                               } } ]
                                        }
                                ]
                        },
@@ -1317,6 +1386,7 @@
        },
        'internal link with ./ and ../': {
                'html': '<body><p><a rel="mw:WikiLink" 
href="./../../../Foo/Bar">Foo</a></p></body>',
+               'head': '<base 
href="http://example.com/one/two/three/four/five"; />',
                'data': [
                        { 'type': 'paragraph' },
                        [
@@ -1329,10 +1399,17 @@
                                                'normalizedTitle': 'Foo/Bar',
                                                'hrefPrefix': './../../../'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './../../../Foo/Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'./../../../Foo/Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/one/Foo/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1345,10 +1422,17 @@
                                                'normalizedTitle': 'Foo/Bar',
                                                'hrefPrefix': './../../../'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './../../../Foo/Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'./../../../Foo/Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/one/Foo/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1361,10 +1445,17 @@
                                                'normalizedTitle': 'Foo/Bar',
                                                'hrefPrefix': './../../../'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': './../../../Foo/Bar',
-                                               'rel': 'mw:WikiLink'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'./../../../Foo/Bar',
+                                                               'rel': 
'mw:WikiLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/one/Foo/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        { 'type': '/paragraph' },
@@ -1386,6 +1477,9 @@
                                        'values': {
                                                'href': 
'http://www.example.com',
                                                'rel': 'mw:ExtLink'
+                                       },
+                                       'computed': {
+                                               'href': 
'http://www.example.com/'
                                        }
                                } ]
                        },
@@ -1408,10 +1502,17 @@
                                                'href': 
'http://www.mediawiki.org/',
                                                'rel': 'mw:ExtLink'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'rel': 'mw:ExtLink',
-                                               'href': 
'http://www.mediawiki.org/'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'http://www.mediawiki.org/',
+                                                               'rel': 
'mw:ExtLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://www.mediawiki.org/'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1422,10 +1523,17 @@
                                                'href': 
'http://www.mediawiki.org/',
                                                'rel': 'mw:ExtLink'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'rel': 'mw:ExtLink',
-                                               'href': 
'http://www.mediawiki.org/'
-                                       } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': 
'http://www.mediawiki.org/',
+                                                               'rel': 
'mw:ExtLink'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://www.mediawiki.org/'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        { 'type': '/paragraph' },
@@ -1455,10 +1563,17 @@
                                        'href': 
'http://de.wikipedia.org/wiki/Foo',
                                        'origRel': 'mw:WikiLink/Language'
                                },
-                               'htmlAttributes': [ { 'values': {
-                                       'href': 
'http://de.wikipedia.org/wiki/Foo',
-                                       'rel': 'mw:WikiLink/Language'
-                               } } ],
+                               'htmlAttributes': [
+                                       {
+                                               'values': {
+                                                       'href': 
'http://de.wikipedia.org/wiki/Foo',
+                                                       'rel': 
'mw:WikiLink/Language'
+                                               },
+                                               'computed': {
+                                                       'href': 
'http://de.wikipedia.org/wiki/Foo'
+                                               }
+                                       }
+                               ],
                                'internal': { 'whitespace': [ '\n', undefined, 
undefined, '\n' ] }
                        },
                        { 'type': '/mwLanguage' },
@@ -1468,10 +1583,17 @@
                                        'href': 
'http://fr.wikipedia.org/wiki/Foo',
                                        'origRel': 'mw:PageProp/Language'
                                 },
-                                'htmlAttributes': [ { 'values': {
-                                       'href': 
'http://fr.wikipedia.org/wiki/Foo',
-                                       'rel': 'mw:PageProp/Language'
-                               } } ],
+                               'htmlAttributes': [
+                                       {
+                                               'values': {
+                                                       'href': 
'http://fr.wikipedia.org/wiki/Foo',
+                                                       'rel': 
'mw:PageProp/Language'
+                                               },
+                                               'computed': {
+                                                       'href': 
'http://fr.wikipedia.org/wiki/Foo'
+                                               }
+                                       }
+                               ],
                                'internal': { 'whitespace': [ '\n' ] }
                        },
                        { 'type': '/mwLanguage' },
@@ -1486,6 +1608,7 @@
                        '<meta property="mw:bar" content="baz" /><!--barbaz-->' 
+
                        '<link rel="mw:PageProp/Category" 
href="./Category:Foo_foo#Bar baz%23quux" />' +
                        '<meta typeof="mw:Placeholder" data-parsoid="foobar" 
/></body>',
+               'head': '<base href="http://example.com"; />',
                'data': ve.dm.mwExample.withMeta
        },
        'RDFa types spread across two attributes, about grouping is forced': {
@@ -1638,6 +1761,7 @@
                                        '</figcaption>' +
                                '</figure>' +
                        '</body>',
+               'head': '<base href="http://example.com"; />',
                'data': [
                        {
                                'type': 'mwBlockImage',
@@ -1683,7 +1807,18 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': { 
'href': './Bar', 'rel': 'mw:WikiLink', 'data-parsoid': '{}' } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink',
+                                                               'data-parsoid': 
'{}'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1696,7 +1831,18 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': { 
'href': './Bar', 'rel': 'mw:WikiLink', 'data-parsoid': '{}' } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink',
+                                                               'data-parsoid': 
'{}'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        [
@@ -1709,7 +1855,18 @@
                                                'normalizedTitle': 'Bar',
                                                'hrefPrefix': './'
                                        },
-                                       'htmlAttributes': [ { 'values': { 
'href': './Bar', 'rel': 'mw:WikiLink', 'data-parsoid': '{}' } } ]
+                                       'htmlAttributes': [
+                                               {
+                                                       'values': {
+                                                               'href': './Bar',
+                                                               'rel': 
'mw:WikiLink',
+                                                               'data-parsoid': 
'{}'
+                                                       },
+                                                       'computed': {
+                                                               'href': 
'http://example.com/Bar'
+                                                       }
+                                               }
+                                       ]
                                } ]
                        ],
                        ' ', 'b', 'a', 'z',
diff --git a/modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js 
b/modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js
index c0ee2d1..c01684b 100644
--- a/modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js
+++ b/modules/ve/ce/nodes/ve.ce.GeneratedContentNode.js
@@ -85,6 +85,50 @@
 };
 
 /**
+ * Make an array of DOM elements suitable for rendering.
+ *
+ * Subclasses can override this to provide their own cleanup steps. This 
function takes an
+ * array of DOM elements cloned within the source document and returns an 
array of DOM elements
+ * cloned into the target document. If it's important that the DOM elements 
still be associated
+ * with the original document, you should modify domElements before calling 
the parent
+ * implementation, otherwise you should call the parent implementation first 
and modify its
+ * return value.
+ *
+ * @param {HTMLElement[]} domElements Clones of the DOM elements from the store
+ * @returns {HTMLElement[]} Clones of the DOM elements in the right document, 
with modifications
+ */
+ve.ce.GeneratedContentNode.prototype.getRenderedDomElements = function ( 
domElements ) {
+       var i, len, attr, $rendering,
+               doc = this.getElementDocument();
+
+       /**
+        * Callback for jQuery.fn.each that resolves the value of attr to the 
computed
+        * property value. Called in the context of an HTMLElement.
+        * @private
+        */
+       function resolveAttribute() {
+               this.setAttribute( attr, this[attr] );
+       }
+
+       // Copy domElements so we can modify the elements
+       // Filter out link, meta and style tags for bug 50043
+       $rendering = $( domElements ).not( 'link, meta, style' );
+       // Also remove link, meta and style tags nested inside other tags
+       $rendering.find( 'link, meta, style' ).remove();
+
+       // Render the computed values of some attributes
+       for ( i = 0, len = ve.dm.Converter.computedAttributes.length; i < len; 
i++ ) {
+               attr = ve.dm.Converter.computedAttributes[i];
+               $rendering.find( '[' + attr + ']' )
+                       .add( $rendering.filter( '[' + attr + ']' ) )
+                       .each( resolveAttribute );
+       }
+
+       // Clone the elements into the target document
+       return ve.copyDomElements( $rendering.toArray(), doc );
+};
+
+/**
  * Rerender the contents of this node.
  *
  * @param {Object|string|Array} generatedContents Generated contents, in the 
default case an HTMLElement array
@@ -92,15 +136,10 @@
  * @fires teardown
  */
 ve.ce.GeneratedContentNode.prototype.render = function ( generatedContents ) {
-       var $rendering, doc = this.getElementDocument();
        if ( this.live ) {
                this.emit( 'teardown' );
        }
-       // Filter out link, meta and style tags for bug 50043
-       $rendering = $( ve.copyDomElements( generatedContents, doc ) ).not( 
'link, meta, style' );
-       // Also remove link, meta and style tags nested inside other tags
-       $rendering.find( 'link, meta, style' ).remove();
-       this.$.empty().append( $rendering );
+       this.$.empty().append( this.getRenderedDomElements( ve.copyDomElements( 
generatedContents ) ) );
        if ( this.live ) {
                this.emit( 'setup' );
                this.afterRender( generatedContents );
diff --git a/modules/ve/ce/ve.ce.View.js b/modules/ve/ce/ve.ce.View.js
index 5c8e5d1..22112b2 100644
--- a/modules/ve/ce/ve.ce.View.js
+++ b/modules/ve/ce/ve.ce.View.js
@@ -158,6 +158,7 @@
        ve.dm.Converter.renderHtmlAttributeList(
                attributeList || this.model.getHtmlAttributes(),
                this.$,
-               this.constructor.static.renderHtmlAttributes
+               this.constructor.static.renderHtmlAttributes,
+               true // computed attributes
        );
 };
diff --git a/modules/ve/dm/ve.dm.Converter.js b/modules/ve/dm/ve.dm.Converter.js
index 736502c..c031de6 100644
--- a/modules/ve/dm/ve.dm.Converter.js
+++ b/modules/ve/dm/ve.dm.Converter.js
@@ -29,6 +29,14 @@
        this.contextStack = null;
 };
 
+/* Static Properties */
+
+/**
+ * List of HTML attribute names that {#buildHtmlAttributeList} should store 
computed values for.
+ * @type {string[]}
+ */
+ve.dm.Converter.computedAttributes = [ 'href', 'src' ];
+
 /* Static Methods */
 
 /**
@@ -124,8 +132,9 @@
  * Build an HTML attribute list for attribute preservation.
  *
  * The attribute list is an array of objects, one for each DOM element. Each 
object contains a
- * map with attribute keys and values in .values, an (ordered) array of 
attribute keys in .keys,
- * and an array of attribute lists for the child nodes in .children .
+ * map with attribute keys and values in .values, a map with a subset of the 
attribute keys and
+ * their computed values in .computed (see {#computedAttributes}), and an 
array of attribute lists
+ * for the child nodes in .children .
  *
  * @static
  * @param {HTMLElement[]} domElements Array of DOM elements to build attribute 
list for
@@ -135,14 +144,22 @@
  * @returns {Object[]|undefined} Attribute list, or undefined if empty
  */
 ve.dm.Converter.buildHtmlAttributeList = function ( domElements, spec, deep, 
attributeList ) {
-       var i, ilen, j, jlen, domAttributes, childList, empty = true;
+       var i, ilen, j, jlen, domAttributes, childList, attrName,
+               empty = true;
        attributeList = attributeList || [];
        for ( i = 0, ilen = domElements.length; i < ilen; i++ ) {
                domAttributes = domElements[i].attributes || [];
                attributeList[i] = { 'values': {} };
                for ( j = 0, jlen = domAttributes.length; j < jlen; j++ ) {
-                       if ( ve.dm.Model.matchesAttributeSpec( 
domAttributes[j].name, spec ) ) {
-                               attributeList[i].values[domAttributes[j].name] 
= domAttributes[j].value;
+                       attrName = domAttributes[j].name;
+                       if ( ve.dm.Model.matchesAttributeSpec( attrName, spec ) 
) {
+                               attributeList[i].values[attrName] = 
domAttributes[j].value;
+                               if ( ve.indexOf( attrName, 
this.computedAttributes ) !== -1 ) {
+                                       if ( !attributeList[i].computed ) {
+                                               attributeList[i].computed = {};
+                                       }
+                                       attributeList[i].computed[attrName] = 
domElements[i][attrName];
+                               }
                                empty = false;
                        }
                }
@@ -174,10 +191,11 @@
  * @param {Object[]} attributeList Attribute list, see buildHtmlAttributeList()
  * @param {HTMLElement[]} domElements Array of DOM elements to render onto
  * @param {boolean|string|RegExp|Array|Object} [spec=true] Attribute 
specification, see ve.dm.Model
+ * @param {boolean} [computed=false] If true, use the computed values of 
attributes where available
  * @param {boolean} [overwrite=false] If true, overwrite attributes that are 
already set
  */
-ve.dm.Converter.renderHtmlAttributeList = function ( attributeList, 
domElements, spec, overwrite ) {
-       var i, ilen, key, values;
+ve.dm.Converter.renderHtmlAttributeList = function ( attributeList, 
domElements, spec, computed, overwrite ) {
+       var i, ilen, key, values, value;
        if ( spec === undefined ) {
                spec = true;
        }
@@ -191,16 +209,17 @@
                values = attributeList[i].values;
                for ( key in values ) {
                        if ( ve.dm.Model.matchesAttributeSpec( key, spec ) ) {
-                               if ( values[key] === undefined ) {
+                               value = computed && attributeList[i].computed 
&& attributeList[i].computed[key] || values[key];
+                               if ( value === undefined ) {
                                        domElements[i].removeAttribute( key );
                                } else if ( overwrite || 
!domElements[i].hasAttribute( key ) ) {
-                                       domElements[i].setAttribute( key, 
values[key] );
+                                       domElements[i].setAttribute( key, value 
);
                                }
                        }
                }
                if ( attributeList[i].children ) {
                        ve.dm.Converter.renderHtmlAttributeList(
-                               attributeList[i].children, 
domElements[i].children, spec
+                               attributeList[i].children, 
domElements[i].children, spec, computed, overwrite
                        );
                }
        }
@@ -382,7 +401,7 @@
                store,
                this.getDataFromDomRecursion( doc.body )
        );
-       refData = this.internalList.convertToData( this );
+       refData = this.internalList.convertToData( this, doc );
        linearData.batchSplice( linearData.getLength(), 0, refData );
 
        // Clear the state
diff --git a/modules/ve/dm/ve.dm.InternalList.js 
b/modules/ve/dm/ve.dm.InternalList.js
index 20ba482..398c0a1 100644
--- a/modules/ve/dm/ve.dm.InternalList.js
+++ b/modules/ve/dm/ve.dm.InternalList.js
@@ -208,16 +208,17 @@
  *
  * @method
  * @param {ve.dm.Converter} converter Converter object
+ * @param {HTMLDocument} doc Document to create nodes in
  * @returns {Array} Linear model data
  */
-ve.dm.InternalList.prototype.convertToData = function ( converter ) {
+ve.dm.InternalList.prototype.convertToData = function ( converter, doc ) {
        var i, length, itemData,
                itemHtmlQueue = this.getItemHtmlQueue(), list = [];
 
        list.push( { 'type': 'internalList' } );
        for ( i = 0, length = itemHtmlQueue.length; i < length; i++ ) {
                if ( itemHtmlQueue[i] !== '' ) {
-                       itemData = converter.getDataFromDomRecursion( $( 
'<div>' ).html( itemHtmlQueue[i] )[0] );
+                       itemData = converter.getDataFromDomRecursion( $( 
'<div>', doc ).html( itemHtmlQueue[i] )[0] );
                        list = list.concat(
                                [{ 'type': 'internalItem' }],
                                itemData,
diff --git a/modules/ve/test/dm/ve.dm.InternalList.test.js 
b/modules/ve/test/dm/ve.dm.InternalList.test.js
index b5e80c7..d49d533 100644
--- a/modules/ve/test/dm/ve.dm.InternalList.test.js
+++ b/modules/ve/test/dm/ve.dm.InternalList.test.js
@@ -72,7 +72,7 @@
 
        internalList.queueItemHtml( 'reference', 'foo', 'Bar' );
        internalList.queueItemHtml( 'reference', 'bar', 'Baz' );
-       assert.deepEqual( internalList.convertToData( ve.dm.converter ), 
expectedData, 'Data matches' );
+       assert.deepEqual( internalList.convertToData( ve.dm.converter, doc ), 
expectedData, 'Data matches' );
        assert.deepEqual( internalList.getItemHtmlQueue(), [], 'Items html is 
emptied after conversion' );
 } );
 
diff --git a/modules/ve/test/dm/ve.dm.example.js 
b/modules/ve/test/dm/ve.dm.example.js
index a45b6fb..4013439 100644
--- a/modules/ve/test/dm/ve.dm.example.js
+++ b/modules/ve/test/dm/ve.dm.example.js
@@ -163,7 +163,13 @@
 
 ve.dm.example.testDir = window.VE_TESTDIR || '.';
 
+ve.dm.example.fullUrl = function ( href ) {
+       return $( '<a>' ).attr( 'href', href )[0].href;
+};
+
 ve.dm.example.imgSrc = ve.dm.example.testDir + '/example.png';
+
+ve.dm.example.fullImgSrc = ve.dm.example.fullUrl( ve.dm.example.imgSrc );
 
 ve.dm.example.image = {
        html: '<img src="' + ve.dm.example.imgSrc + '" alt="Example" 
width="100" height="50">',
@@ -175,12 +181,17 @@
                        'width': 100,
                        'height': 50
                },
-               'htmlAttributes': [ { 'values': {
-                       'src': ve.dm.example.imgSrc,
-                       'alt': 'Example',
-                       'width': '100',
-                       'height': '50'
-               } } ]
+               'htmlAttributes': [
+                       {
+                               'values': {
+                                       'src': ve.dm.example.imgSrc,
+                                       'alt': 'Example',
+                                       'width': '100',
+                                       'height': '50'
+                               },
+                               'computed': { 'src': ve.dm.example.fullImgSrc }
+                       }
+               ]
        }
 };
 
@@ -1570,6 +1581,7 @@
        },
        'list item with space followed by link': {
                'html': '<body><ul><li><p> <a 
href="Foobar">bar</a></p></li></ul></body>',
+               'head': '<base href="http://example.com/Foo"; />',
                'data': [
                        { 'type': 'list', 'attributes': { 'style': 'bullet' } },
                        { 'type': 'listItem' },
@@ -1581,9 +1593,10 @@
                                        'attributes': {
                                                'href': 'Foobar'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': 'Foobar'
-                                       } } ]
+                                       'htmlAttributes': [ {
+                                               'values': { 'href': 'Foobar' },
+                                               'computed': { 'href': 
'http://example.com/Foobar' }
+                                       } ]
                                } ]
                        ],
                        [
@@ -1593,9 +1606,10 @@
                                        'attributes': {
                                                'href': 'Foobar'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': 'Foobar'
-                                       } } ]
+                                       'htmlAttributes': [ {
+                                               'values': { 'href': 'Foobar' },
+                                               'computed': { 'href': 
'http://example.com/Foobar' }
+                                       } ]
                                } ]
                        ],
                        [
@@ -1605,9 +1619,10 @@
                                        'attributes': {
                                                'href': 'Foobar'
                                        },
-                                       'htmlAttributes': [ { 'values': {
-                                               'href': 'Foobar'
-                                       } } ]
+                                       'htmlAttributes': [ {
+                                               'values': { 'href': 'Foobar' },
+                                               'computed': { 'href': 
'http://example.com/Foobar' }
+                                       } ]
                                } ]
                        ],
                        { 'type': '/paragraph' },
@@ -2374,6 +2389,7 @@
        },
        'nested annotations are closed and reopened in the correct order': {
                'html': '<body><p><a 
href="Foo">F<b>o<i>o</i></b><i>b</i></a><i>a<b>r</b>b<u>a</u>z</i></p></body>',
+               'head': '<base href="http://example.com/Bar/Baz"; />',
                'data': [
                        { 'type': 'paragraph' },
                        [
@@ -2384,9 +2400,10 @@
                                                'attributes': {
                                                        'href': 'Foo'
                                                },
-                                               'htmlAttributes': [ { 'values': 
{
-                                                       'href': 'Foo'
-                                               } } ]
+                                               'htmlAttributes': [ {
+                                                       'values': { 'href': 
'Foo' },
+                                                       'computed': { 'href': 
'http://example.com/Bar/Foo' }
+                                               } ]
                                        }
                                ]
                        ],
@@ -2398,9 +2415,10 @@
                                                'attributes': {
                                                        'href': 'Foo'
                                                },
-                                               'htmlAttributes': [ { 'values': 
{
-                                                       'href': 'Foo'
-                                               } } ]
+                                               'htmlAttributes': [ {
+                                                       'values': { 'href': 
'Foo' },
+                                                       'computed': { 'href': 
'http://example.com/Bar/Foo' }
+                                               } ]
                                        },
                                        ve.dm.example.bold
                                ]
@@ -2413,9 +2431,10 @@
                                                'attributes': {
                                                        'href': 'Foo'
                                                },
-                                               'htmlAttributes': [ { 'values': 
{
-                                                       'href': 'Foo'
-                                               } } ]
+                                               'htmlAttributes': [ {
+                                                       'values': { 'href': 
'Foo' },
+                                                       'computed': { 'href': 
'http://example.com/Bar/Foo' }
+                                               } ]
                                        },
                                        ve.dm.example.bold,
                                        ve.dm.example.italic
@@ -2429,9 +2448,10 @@
                                                'attributes': {
                                                        'href': 'Foo'
                                                },
-                                               'htmlAttributes': [ { 'values': 
{
-                                                       'href': 'Foo'
-                                               } } ]
+                                               'htmlAttributes': [ {
+                                                       'values': { 'href': 
'Foo' },
+                                                       'computed': { 'href': 
'http://example.com/Bar/Foo' }
+                                               } ]
                                        },
                                        ve.dm.example.italic
                                ]
diff --git a/modules/ve/test/ve.test.utils.js b/modules/ve/test/ve.test.utils.js
index b7adce3..b4b6305 100644
--- a/modules/ve/test/ve.test.utils.js
+++ b/modules/ve/test/ve.test.utils.js
@@ -48,7 +48,7 @@
 };
 
 ve.test.utils.runGetDataFromDomTests = function( assert, cases ) {
-       var msg, doc, store, internalList, i, length, hash, data, n = 0;
+       var msg, doc, store, internalList, i, length, hash, data, html, n = 0;
 
        // TODO: this is a hack to make normal heading/preformatted
        // nodes the most recently registered, instead of the MW versions
@@ -70,8 +70,15 @@
                        doc = new ve.dm.Document( [] );
                        store = doc.getStore();
                        internalList = doc.getInternalList();
+
+                       html = '';
+                       if ( cases[msg].head ) {
+                               html = '<head>' + cases[msg].head + '</head>';
+                       }
+                       html += cases[msg].html;
+
                        data = ve.dm.converter.getDataFromDom(
-                               ve.createDocumentFromHtml( cases[msg].html ), 
store, internalList
+                               ve.createDocumentFromHtml( html ), store, 
internalList
                        ).getData();
                        ve.dm.example.preprocessAnnotations( cases[msg].data, 
store );
                        assert.deepEqualWithDomElements( data, cases[msg].data, 
msg );

-- 
To view, visit https://gerrit.wikimedia.org/r/86065
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Iaccb9e3fc05cd151a0f5e632c8d3bd3568735309
Gerrit-PatchSet: 15
Gerrit-Project: mediawiki/extensions/VisualEditor
Gerrit-Branch: master
Gerrit-Owner: Catrope <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Esanders <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to