aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorJack Moffitt <jack@metajack.im>2014-08-28 09:34:23 -0600
committerJack Moffitt <jack@metajack.im>2014-09-08 20:21:42 -0600
commitc6ab60dbfc6da7b4f800c9e40893c8b58413960c (patch)
treed1d74076cf7fa20e4f77ec7cb82cae98b67362cb /components
parentdb2f642c32fc5bed445bb6f2e45b0f6f0b4342cf (diff)
downloadservo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.tar.gz
servo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.zip
Cargoify servo
Diffstat (limited to 'components')
-rw-r--r--components/canvas/Cargo.toml14
-rw-r--r--components/canvas/canvas_render_task.rs78
-rw-r--r--components/canvas/lib.rs8
-rw-r--r--components/compositing/Cargo.toml61
-rw-r--r--components/compositing/compositor.rs924
-rw-r--r--components/compositing/compositor_data.rs183
-rw-r--r--components/compositing/compositor_task.rs247
-rw-r--r--components/compositing/constellation.rs870
-rw-r--r--components/compositing/events.rs178
-rw-r--r--components/compositing/headless.rs99
-rw-r--r--components/compositing/lib.rs59
-rw-r--r--components/compositing/pipeline.rs193
-rw-r--r--components/compositing/platform/common/glfw_windowing.rs380
-rw-r--r--components/compositing/platform/common/glut_windowing.rs303
-rw-r--r--components/compositing/platform/mod.rs18
-rw-r--r--components/compositing/windowing.rs83
-rw-r--r--components/gfx/Cargo.toml62
-rw-r--r--components/gfx/buffer_map.rs156
-rw-r--r--components/gfx/color.rs21
-rw-r--r--components/gfx/display_list/mod.rs773
-rw-r--r--components/gfx/display_list/optimizer.rs73
-rw-r--r--components/gfx/font.rs213
-rw-r--r--components/gfx/font_cache_task.rs276
-rw-r--r--components/gfx/font_context.rs148
-rw-r--r--components/gfx/font_template.rs157
-rw-r--r--components/gfx/lib.rs72
-rw-r--r--components/gfx/platform/freetype/font.rs297
-rw-r--r--components/gfx/platform/freetype/font_context.rs82
-rw-r--r--components/gfx/platform/freetype/font_list.rs115
-rw-r--r--components/gfx/platform/freetype/font_template.rs35
-rw-r--r--components/gfx/platform/macos/font.rs185
-rw-r--r--components/gfx/platform/macos/font_context.rs16
-rw-r--r--components/gfx/platform/macos/font_list.rs37
-rw-r--r--components/gfx/platform/macos/font_template.rs40
-rw-r--r--components/gfx/platform/mod.rs27
-rw-r--r--components/gfx/render_context.rs419
-rw-r--r--components/gfx/render_task.rs443
-rw-r--r--components/gfx/text/glyph.rs752
-rw-r--r--components/gfx/text/mod.rs18
-rw-r--r--components/gfx/text/shaping/harfbuzz.rs541
-rw-r--r--components/gfx/text/shaping/mod.rs19
-rw-r--r--components/gfx/text/text_run.rs271
-rw-r--r--components/gfx/text/util.rs285
-rw-r--r--components/layout/Cargo.toml38
-rw-r--r--components/layout/block.rs2428
-rw-r--r--components/layout/construct.rs1049
-rw-r--r--components/layout/context.rs123
-rw-r--r--components/layout/css/matching.rs558
-rw-r--r--components/layout/css/node_style.rs30
-rw-r--r--components/layout/css/node_util.rs90
-rw-r--r--components/layout/extra.rs44
-rw-r--r--components/layout/floats.rs439
-rw-r--r--components/layout/flow.rs1138
-rw-r--r--components/layout/flow_list.rs296
-rw-r--r--components/layout/flow_ref.rs84
-rw-r--r--components/layout/fragment.rs1597
-rw-r--r--components/layout/incremental.rs78
-rw-r--r--components/layout/inline.rs1170
-rw-r--r--components/layout/layout_debug.rs126
-rw-r--r--components/layout/layout_task.rs1020
-rw-r--r--components/layout/lib.rs68
-rw-r--r--components/layout/model.rs337
-rw-r--r--components/layout/parallel.rs561
-rw-r--r--components/layout/table.rs324
-rw-r--r--components/layout/table_caption.rs73
-rw-r--r--components/layout/table_cell.rs121
-rw-r--r--components/layout/table_colgroup.rs88
-rw-r--r--components/layout/table_row.rs225
-rw-r--r--components/layout/table_rowgroup.rs208
-rw-r--r--components/layout/table_wrapper.rs325
-rw-r--r--components/layout/text.rs327
-rw-r--r--components/layout/util.rs164
-rw-r--r--components/layout/wrapper.rs783
-rw-r--r--components/layout_traits/Cargo.toml23
-rw-r--r--components/layout_traits/lib.rs54
-rw-r--r--components/macros/Cargo.toml9
-rw-r--r--components/macros/lib.rs93
-rw-r--r--components/msg/Cargo.toml30
-rw-r--r--components/msg/compositor_msg.rs123
-rw-r--r--components/msg/constellation_msg.rs84
-rw-r--r--components/msg/lib.rs43
-rw-r--r--components/msg/platform/android/surface.rs20
-rw-r--r--components/msg/platform/linux/surface.rs20
-rw-r--r--components/msg/platform/macos/surface.rs25
-rw-r--r--components/msg/platform/surface.rs12
-rw-r--r--components/net/Cargo.toml27
-rw-r--r--components/net/data_loader.rs154
-rw-r--r--components/net/fetch/cors_cache.rs316
-rw-r--r--components/net/fetch/request.rs149
-rw-r--r--components/net/fetch/response.rs144
-rw-r--r--components/net/file_loader.rs50
-rw-r--r--components/net/http_loader.rs167
-rw-r--r--components/net/image/base.rs67
-rw-r--r--components/net/image/holder.rs109
-rw-r--r--components/net/image/test.jpegbin0 -> 4962 bytes
-rw-r--r--components/net/image_cache_task.rs993
-rw-r--r--components/net/lib.rs44
-rw-r--r--components/net/local_image_cache.rs166
-rw-r--r--components/net/resource_task.rs267
-rw-r--r--components/script/Cargo.toml56
-rw-r--r--components/script/cors.rs419
-rw-r--r--components/script/dom/attr.rs200
-rw-r--r--components/script/dom/bindings/DESIGN.md38
-rw-r--r--components/script/dom/bindings/callback.rs156
-rw-r--r--components/script/dom/bindings/codegen/BindingGen.py52
-rw-r--r--components/script/dom/bindings/codegen/BindingUtils.cpp633
-rw-r--r--components/script/dom/bindings/codegen/BindingUtils.h1151
-rw-r--r--components/script/dom/bindings/codegen/Bindings.conf28
-rw-r--r--components/script/dom/bindings/codegen/Codegen.py5788
-rw-r--r--components/script/dom/bindings/codegen/CodegenRust.py5534
-rw-r--r--components/script/dom/bindings/codegen/Configuration.py341
-rw-r--r--components/script/dom/bindings/codegen/DOMJSClass.h114
-rw-r--r--components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp247
-rw-r--r--components/script/dom/bindings/codegen/DOMJSProxyHandler.h109
-rw-r--r--components/script/dom/bindings/codegen/ErrorResult.h59
-rw-r--r--components/script/dom/bindings/codegen/Errors.msg30
-rw-r--r--components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py26
-rw-r--r--components/script/dom/bindings/codegen/GlobalGen.py83
-rw-r--r--components/script/dom/bindings/codegen/Makefile.in165
-rw-r--r--components/script/dom/bindings/codegen/Nullable.h68
-rw-r--r--components/script/dom/bindings/codegen/PrimitiveConversions.h350
-rw-r--r--components/script/dom/bindings/codegen/RegisterBindings.h14
-rw-r--r--components/script/dom/bindings/codegen/TypedArray.h121
-rw-r--r--components/script/dom/bindings/codegen/crashtests/769464.html11
-rw-r--r--components/script/dom/bindings/codegen/crashtests/crashtests.list1
-rw-r--r--components/script/dom/bindings/codegen/parser/README1
-rw-r--r--components/script/dom/bindings/codegen/parser/UPSTREAM1
-rw-r--r--components/script/dom/bindings/codegen/parser/WebIDL.py5583
-rw-r--r--components/script/dom/bindings/codegen/parser/external.patch49
-rw-r--r--components/script/dom/bindings/codegen/parser/module.patch12
-rw-r--r--components/script/dom/bindings/codegen/parser/runtests.py79
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_any_null.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py13
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py84
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_attr.py302
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py67
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py11
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_builtins.py41
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_callback.py34
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py47
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_const.py64
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_constructor.py75
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py28
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py15
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_dictionary.py198
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py150
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_double_null.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py84
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_enum.py81
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py13
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_error_colno.py20
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py28
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py107
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py15
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_implements.py216
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py18
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py44
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_interface.py188
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py15
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py60
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_method.py145
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py126
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_overload.py47
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_sanity.py7
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py294
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_special_methods.py73
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py62
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py56
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_typedef.py76
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_union.py169
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_union_any.py14
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py53
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py10
-rw-r--r--components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py39
-rwxr-xr-xcomponents/script/dom/bindings/codegen/parser/update.sh3
-rw-r--r--components/script/dom/bindings/codegen/ply/COPYING28
-rw-r--r--components/script/dom/bindings/codegen/ply/README9
-rw-r--r--components/script/dom/bindings/codegen/ply/ply/__init__.py4
-rw-r--r--components/script/dom/bindings/codegen/ply/ply/lex.py1058
-rw-r--r--components/script/dom/bindings/codegen/ply/ply/yacc.py3276
-rw-r--r--components/script/dom/bindings/codegen/pythonpath.py60
-rw-r--r--components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp40
-rw-r--r--components/script/dom/bindings/codegen/stubgenerator/Skeleton.h40
-rw-r--r--components/script/dom/bindings/codegen/stubgenerator/generate.sh16
-rw-r--r--components/script/dom/bindings/codegen/test/Makefile.in87
-rw-r--r--components/script/dom/bindings/codegen/test/TestBindingHeader.h653
-rw-r--r--components/script/dom/bindings/codegen/test/TestCodeGen.webidl442
-rw-r--r--components/script/dom/bindings/codegen/test/TestDictionary.webidl9
-rw-r--r--components/script/dom/bindings/codegen/test/TestTypedef.webidl7
-rw-r--r--components/script/dom/bindings/codegen/test/file_bug775543.html5
-rw-r--r--components/script/dom/bindings/codegen/test/forOf_iframe.html13
-rw-r--r--components/script/dom/bindings/codegen/test/test_InstanceOf.html28
-rw-r--r--components/script/dom/bindings/codegen/test/test_bug773326.html11
-rw-r--r--components/script/dom/bindings/codegen/test/test_bug775543.html37
-rw-r--r--components/script/dom/bindings/codegen/test/test_bug788369.html30
-rw-r--r--components/script/dom/bindings/codegen/test/test_enums.html15
-rw-r--r--components/script/dom/bindings/codegen/test/test_forOf.html94
-rw-r--r--components/script/dom/bindings/codegen/test/test_integers.html45
-rw-r--r--components/script/dom/bindings/codegen/test/test_interfaceToString.html38
-rw-r--r--components/script/dom/bindings/codegen/test/test_lookupGetter.html49
-rw-r--r--components/script/dom/bindings/codegen/test/test_sequence_wrapping.html60
-rw-r--r--components/script/dom/bindings/codegen/test/test_traceProtos.html37
-rw-r--r--components/script/dom/bindings/conversions.rs378
-rw-r--r--components/script/dom/bindings/error.rs114
-rw-r--r--components/script/dom/bindings/global.rs121
-rw-r--r--components/script/dom/bindings/js.rs496
-rw-r--r--components/script/dom/bindings/proxyhandler.rs155
-rw-r--r--components/script/dom/bindings/str.rs157
-rw-r--r--components/script/dom/bindings/trace.rs185
-rw-r--r--components/script/dom/bindings/utils.rs791
-rw-r--r--components/script/dom/blob.rs59
-rw-r--r--components/script/dom/browsercontext.rs120
-rw-r--r--components/script/dom/canvasrenderingcontext2d.rs79
-rw-r--r--components/script/dom/characterdata.rs106
-rw-r--r--components/script/dom/comment.rs55
-rw-r--r--components/script/dom/console.rs65
-rw-r--r--components/script/dom/customevent.rs79
-rw-r--r--components/script/dom/dedicatedworkerglobalscope.rs200
-rw-r--r--components/script/dom/document.rs855
-rw-r--r--components/script/dom/documentfragment.rs78
-rw-r--r--components/script/dom/documenttype.rs81
-rw-r--r--components/script/dom/domexception.rs132
-rw-r--r--components/script/dom/domimplementation.rs172
-rw-r--r--components/script/dom/domparser.rs64
-rw-r--r--components/script/dom/domrect.rs72
-rw-r--r--components/script/dom/domrectlist.rs62
-rw-r--r--components/script/dom/domtokenlist.rs101
-rw-r--r--components/script/dom/element.rs958
-rw-r--r--components/script/dom/event.rs174
-rw-r--r--components/script/dom/eventdispatcher.rs139
-rw-r--r--components/script/dom/eventtarget.rs287
-rw-r--r--components/script/dom/file.rs48
-rw-r--r--components/script/dom/formdata.rs115
-rw-r--r--components/script/dom/htmlanchorelement.rs132
-rw-r--r--components/script/dom/htmlappletelement.rs44
-rw-r--r--components/script/dom/htmlareaelement.rs81
-rw-r--r--components/script/dom/htmlaudioelement.rs44
-rw-r--r--components/script/dom/htmlbaseelement.rs44
-rw-r--r--components/script/dom/htmlbodyelement.rs98
-rw-r--r--components/script/dom/htmlbrelement.rs44
-rw-r--r--components/script/dom/htmlbuttonelement.rs130
-rw-r--r--components/script/dom/htmlcanvaselement.rs160
-rw-r--r--components/script/dom/htmlcollection.rs257
-rw-r--r--components/script/dom/htmldataelement.rs44
-rw-r--r--components/script/dom/htmldatalistelement.rs62
-rw-r--r--components/script/dom/htmldirectoryelement.rs44
-rw-r--r--components/script/dom/htmldivelement.rs44
-rw-r--r--components/script/dom/htmldlistelement.rs44
-rw-r--r--components/script/dom/htmlelement.rs120
-rw-r--r--components/script/dom/htmlembedelement.rs44
-rw-r--r--components/script/dom/htmlfieldsetelement.rs156
-rw-r--r--components/script/dom/htmlfontelement.rs44
-rw-r--r--components/script/dom/htmlformelement.rs44
-rw-r--r--components/script/dom/htmlframeelement.rs44
-rw-r--r--components/script/dom/htmlframesetelement.rs44
-rw-r--r--components/script/dom/htmlheadelement.rs44
-rw-r--r--components/script/dom/htmlheadingelement.rs56
-rw-r--r--components/script/dom/htmlhrelement.rs44
-rw-r--r--components/script/dom/htmlhtmlelement.rs44
-rw-r--r--components/script/dom/htmliframeelement.rs225
-rw-r--r--components/script/dom/htmlimageelement.rs233
-rw-r--r--components/script/dom/htmlinputelement.rs124
-rw-r--r--components/script/dom/htmllabelelement.rs44
-rw-r--r--components/script/dom/htmllegendelement.rs44
-rw-r--r--components/script/dom/htmllielement.rs44
-rw-r--r--components/script/dom/htmllinkelement.rs81
-rw-r--r--components/script/dom/htmlmapelement.rs44
-rw-r--r--components/script/dom/htmlmediaelement.rs42
-rw-r--r--components/script/dom/htmlmetaelement.rs44
-rw-r--r--components/script/dom/htmlmeterelement.rs44
-rw-r--r--components/script/dom/htmlmodelement.rs44
-rw-r--r--components/script/dom/htmlobjectelement.rs113
-rw-r--r--components/script/dom/htmlolistelement.rs44
-rw-r--r--components/script/dom/htmloptgroupelement.rs106
-rw-r--r--components/script/dom/htmloptionelement.rs124
-rw-r--r--components/script/dom/htmloutputelement.rs53
-rw-r--r--components/script/dom/htmlparagraphelement.rs44
-rw-r--r--components/script/dom/htmlparamelement.rs44
-rw-r--r--components/script/dom/htmlpreelement.rs44
-rw-r--r--components/script/dom/htmlprogresselement.rs44
-rw-r--r--components/script/dom/htmlquoteelement.rs44
-rw-r--r--components/script/dom/htmlscriptelement.rs130
-rw-r--r--components/script/dom/htmlselectelement.rs136
-rw-r--r--components/script/dom/htmlserializer.rs171
-rw-r--r--components/script/dom/htmlsourceelement.rs44
-rw-r--r--components/script/dom/htmlspanelement.rs44
-rw-r--r--components/script/dom/htmlstyleelement.rs97
-rw-r--r--components/script/dom/htmltablecaptionelement.rs44
-rw-r--r--components/script/dom/htmltablecellelement.rs42
-rw-r--r--components/script/dom/htmltablecolelement.rs44
-rw-r--r--components/script/dom/htmltabledatacellelement.rs44
-rw-r--r--components/script/dom/htmltableelement.rs81
-rw-r--r--components/script/dom/htmltableheadercellelement.rs44
-rw-r--r--components/script/dom/htmltablerowelement.rs44
-rw-r--r--components/script/dom/htmltablesectionelement.rs44
-rw-r--r--components/script/dom/htmltemplateelement.rs44
-rw-r--r--components/script/dom/htmltextareaelement.rs124
-rw-r--r--components/script/dom/htmltimeelement.rs44
-rw-r--r--components/script/dom/htmltitleelement.rs69
-rw-r--r--components/script/dom/htmltrackelement.rs44
-rw-r--r--components/script/dom/htmlulistelement.rs44
-rw-r--r--components/script/dom/htmlunknownelement.rs44
-rw-r--r--components/script/dom/htmlvideoelement.rs44
-rw-r--r--components/script/dom/location.rs64
-rw-r--r--components/script/dom/macros.rs44
-rw-r--r--components/script/dom/messageevent.rs99
-rw-r--r--components/script/dom/mouseevent.rs182
-rw-r--r--components/script/dom/namednodemap.rs54
-rw-r--r--components/script/dom/navigator.rs58
-rw-r--r--components/script/dom/node.rs2085
-rw-r--r--components/script/dom/nodeiterator.rs35
-rw-r--r--components/script/dom/nodelist.rs82
-rw-r--r--components/script/dom/performance.rs52
-rw-r--r--components/script/dom/performancetiming.rs57
-rw-r--r--components/script/dom/processinginstruction.rs53
-rw-r--r--components/script/dom/progressevent.rs75
-rw-r--r--components/script/dom/range.rs50
-rw-r--r--components/script/dom/screen.rs45
-rw-r--r--components/script/dom/testbinding.rs299
-rw-r--r--components/script/dom/text.rs52
-rw-r--r--components/script/dom/treewalker.rs35
-rw-r--r--components/script/dom/uievent.rs95
-rw-r--r--components/script/dom/urlsearchparams.rs152
-rw-r--r--components/script/dom/validitystate.rs36
-rw-r--r--components/script/dom/virtualmethods.rs219
-rw-r--r--components/script/dom/webidls/Attr.webidl18
-rw-r--r--components/script/dom/webidls/Blob.webidl29
-rw-r--r--components/script/dom/webidls/CanvasRenderingContext2D.webidl104
-rw-r--r--components/script/dom/webidls/CharacterData.webidl28
-rw-r--r--components/script/dom/webidls/ChildNode.webidl25
-rw-r--r--components/script/dom/webidls/Comment.webidl15
-rw-r--r--components/script/dom/webidls/Console.webidl21
-rw-r--r--components/script/dom/webidls/CustomEvent.webidl27
-rw-r--r--components/script/dom/webidls/DOMException.webidl47
-rw-r--r--components/script/dom/webidls/DOMImplementation.webidl25
-rw-r--r--components/script/dom/webidls/DOMParser.webidl21
-rw-r--r--components/script/dom/webidls/DOMRect.webidl14
-rw-r--r--components/script/dom/webidls/DOMRectList.webidl12
-rw-r--r--components/script/dom/webidls/DOMTokenList.webidl18
-rw-r--r--components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl10
-rw-r--r--components/script/dom/webidls/Document.webidl71
-rw-r--r--components/script/dom/webidls/DocumentFragment.webidl11
-rw-r--r--components/script/dom/webidls/DocumentType.webidl19
-rw-r--r--components/script/dom/webidls/Element.webidl70
-rw-r--r--components/script/dom/webidls/Event.webidl43
-rw-r--r--components/script/dom/webidls/EventHandler.webidl46
-rw-r--r--components/script/dom/webidls/EventListener.webidl16
-rw-r--r--components/script/dom/webidls/EventTarget.webidl22
-rw-r--r--components/script/dom/webidls/File.webidl15
-rw-r--r--components/script/dom/webidls/FormData.webidl22
-rw-r--r--components/script/dom/webidls/HTMLAnchorElement.webidl38
-rw-r--r--components/script/dom/webidls/HTMLAppletElement.webidl19
-rw-r--r--components/script/dom/webidls/HTMLAreaElement.webidl26
-rw-r--r--components/script/dom/webidls/HTMLAudioElement.webidl8
-rw-r--r--components/script/dom/webidls/HTMLBRElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLBaseElement.webidl10
-rw-r--r--components/script/dom/webidls/HTMLBodyElement.webidl21
-rw-r--r--components/script/dom/webidls/HTMLButtonElement.webidl29
-rw-r--r--components/script/dom/webidls/HTMLCanvasElement.webidl24
-rw-r--r--components/script/dom/webidls/HTMLCollection.webidl10
-rw-r--r--components/script/dom/webidls/HTMLDListElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLDataElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLDataListElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLDirectoryElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLDivElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLElement.webidl48
-rw-r--r--components/script/dom/webidls/HTMLEmbedElement.webidl21
-rw-r--r--components/script/dom/webidls/HTMLFieldSetElement.webidl23
-rw-r--r--components/script/dom/webidls/HTMLFontElement.webidl11
-rw-r--r--components/script/dom/webidls/HTMLFormElement.webidl30
-rw-r--r--components/script/dom/webidls/HTMLFrameElement.webidl19
-rw-r--r--components/script/dom/webidls/HTMLFrameSetElement.webidl11
-rw-r--r--components/script/dom/webidls/HTMLHRElement.webidl18
-rw-r--r--components/script/dom/webidls/HTMLHeadElement.webidl7
-rw-r--r--components/script/dom/webidls/HTMLHeadingElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLHtmlElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLIFrameElement.webidl33
-rw-r--r--components/script/dom/webidls/HTMLImageElement.webidl34
-rw-r--r--components/script/dom/webidls/HTMLInputElement.webidl76
-rw-r--r--components/script/dom/webidls/HTMLLIElement.webidl16
-rw-r--r--components/script/dom/webidls/HTMLLabelElement.webidl11
-rw-r--r--components/script/dom/webidls/HTMLLegendElement.webidl16
-rw-r--r--components/script/dom/webidls/HTMLLinkElement.webidl26
-rw-r--r--components/script/dom/webidls/HTMLMapElement.webidl11
-rw-r--r--components/script/dom/webidls/HTMLMediaElement.webidl67
-rw-r--r--components/script/dom/webidls/HTMLMetaElement.webidl18
-rw-r--r--components/script/dom/webidls/HTMLMeterElement.webidl15
-rw-r--r--components/script/dom/webidls/HTMLModElement.webidl10
-rw-r--r--components/script/dom/webidls/HTMLOListElement.webidl18
-rw-r--r--components/script/dom/webidls/HTMLObjectElement.webidl44
-rw-r--r--components/script/dom/webidls/HTMLOptGroupElement.webidl10
-rw-r--r--components/script/dom/webidls/HTMLOptionElement.webidl18
-rw-r--r--components/script/dom/webidls/HTMLOutputElement.webidl24
-rw-r--r--components/script/dom/webidls/HTMLParagraphElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLParamElement.webidl18
-rw-r--r--components/script/dom/webidls/HTMLPreElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLProgressElement.webidl12
-rw-r--r--components/script/dom/webidls/HTMLQuoteElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLScriptElement.webidl25
-rw-r--r--components/script/dom/webidls/HTMLSelectElement.webidl40
-rw-r--r--components/script/dom/webidls/HTMLSourceElement.webidl10
-rw-r--r--components/script/dom/webidls/HTMLSpanElement.webidl7
-rw-r--r--components/script/dom/webidls/HTMLStyleElement.webidl12
-rw-r--r--components/script/dom/webidls/HTMLTableCaptionElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLTableCellElement.webidl29
-rw-r--r--components/script/dom/webidls/HTMLTableColElement.webidl20
-rw-r--r--components/script/dom/webidls/HTMLTableDataCellElement.webidl14
-rw-r--r--components/script/dom/webidls/HTMLTableElement.webidl40
-rw-r--r--components/script/dom/webidls/HTMLTableHeaderCellElement.webidl12
-rw-r--r--components/script/dom/webidls/HTMLTableRowElement.webidl25
-rw-r--r--components/script/dom/webidls/HTMLTableSectionElement.webidl21
-rw-r--r--components/script/dom/webidls/HTMLTemplateElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLTextAreaElement.webidl45
-rw-r--r--components/script/dom/webidls/HTMLTimeElement.webidl9
-rw-r--r--components/script/dom/webidls/HTMLTitleElement.webidl10
-rw-r--r--components/script/dom/webidls/HTMLTrackElement.webidl21
-rw-r--r--components/script/dom/webidls/HTMLUListElement.webidl15
-rw-r--r--components/script/dom/webidls/HTMLUnknownElement.webidl16
-rw-r--r--components/script/dom/webidls/HTMLVideoElement.webidl13
-rw-r--r--components/script/dom/webidls/Location.webidl12
-rw-r--r--components/script/dom/webidls/MessageEvent.webidl23
-rw-r--r--components/script/dom/webidls/MouseEvent.webidl43
-rw-r--r--components/script/dom/webidls/NamedNodeMap.webidl8
-rw-r--r--components/script/dom/webidls/Navigator.webidl27
-rw-r--r--components/script/dom/webidls/Node.webidl79
-rw-r--r--components/script/dom/webidls/NodeFilter.webidl33
-rw-r--r--components/script/dom/webidls/NodeIterator.webidl32
-rw-r--r--components/script/dom/webidls/NodeList.webidl13
-rw-r--r--components/script/dom/webidls/ParentNode.webidl34
-rw-r--r--components/script/dom/webidls/Performance.webidl19
-rw-r--r--components/script/dom/webidls/PerformanceTiming.webidl32
-rw-r--r--components/script/dom/webidls/ProcessingInstruction.webidl12
-rw-r--r--components/script/dom/webidls/ProgressEvent.webidl28
-rw-r--r--components/script/dom/webidls/Range.webidl85
-rw-r--r--components/script/dom/webidls/Screen.webidl14
-rw-r--r--components/script/dom/webidls/TestBinding.webidl276
-rw-r--r--components/script/dom/webidls/Text.webidl18
-rw-r--r--components/script/dom/webidls/TreeWalker.webidl23
-rw-r--r--components/script/dom/webidls/UIEvent.webidl25
-rw-r--r--components/script/dom/webidls/URLSearchParams.webidl19
-rw-r--r--components/script/dom/webidls/URLUtils.webidl25
-rw-r--r--components/script/dom/webidls/URLUtilsReadOnly.webidl23
-rw-r--r--components/script/dom/webidls/ValidityState.webidl19
-rw-r--r--components/script/dom/webidls/Window.webidl131
-rw-r--r--components/script/dom/webidls/Worker.webidl20
-rw-r--r--components/script/dom/webidls/WorkerGlobalScope.webidl32
-rw-r--r--components/script/dom/webidls/WorkerLocation.webidl9
-rw-r--r--components/script/dom/webidls/WorkerNavigator.webidl11
-rw-r--r--components/script/dom/webidls/XMLHttpRequest.webidl72
-rw-r--r--components/script/dom/webidls/XMLHttpRequestEventTarget.webidl26
-rw-r--r--components/script/dom/webidls/XMLHttpRequestUpload.webidl18
-rw-r--r--components/script/dom/window.rs513
-rw-r--r--components/script/dom/worker.rs160
-rw-r--r--components/script/dom/workerglobalscope.rs145
-rw-r--r--components/script/dom/workerlocation.rs64
-rw-r--r--components/script/dom/workernavigator.rs58
-rw-r--r--components/script/dom/xmlhttprequest.rs970
-rw-r--r--components/script/dom/xmlhttprequesteventtarget.rs112
-rw-r--r--components/script/dom/xmlhttprequestupload.rs41
-rw-r--r--components/script/html/cssparse.rs72
-rw-r--r--components/script/html/hubbub_html_parser.rs615
-rw-r--r--components/script/layout_interface.rs204
-rw-r--r--components/script/lib.rs209
-rw-r--r--components/script/makefile.cargo45
-rw-r--r--components/script/page.rs437
-rw-r--r--components/script/script_task.rs933
-rw-r--r--components/script_traits/Cargo.toml20
-rw-r--r--components/script_traits/lib.rs95
-rw-r--r--components/style/.gitignore2
-rw-r--r--components/style/Cargo.toml31
-rw-r--r--components/style/Mako-0.9.1.zipbin0 -> 469500 bytes
-rw-r--r--components/style/README.md6
-rw-r--r--components/style/errors.rs32
-rw-r--r--components/style/font_face.rs183
-rw-r--r--components/style/lib.rs52
-rw-r--r--components/style/makefile.cargo8
-rw-r--r--components/style/media_queries.rs131
-rw-r--r--components/style/namespaces.rs64
-rw-r--r--components/style/node.rs34
-rw-r--r--components/style/parsing_utils.rs81
-rw-r--r--components/style/properties/common_types.rs262
-rw-r--r--components/style/properties/mod.rs.mako2143
-rw-r--r--components/style/selector_matching.rs990
-rw-r--r--components/style/selectors.rs717
-rw-r--r--components/style/stylesheets.rs178
-rw-r--r--components/style/user-agent.css118
-rw-r--r--components/util/Cargo.toml20
-rw-r--r--components/util/atom.rs43
-rw-r--r--components/util/cache.rs279
-rw-r--r--components/util/debug_utils.rs33
-rw-r--r--components/util/geometry.rs304
-rw-r--r--components/util/lib.rs44
-rw-r--r--components/util/logical_geometry.rs1023
-rw-r--r--components/util/memory.rs209
-rw-r--r--components/util/namespace.rs43
-rw-r--r--components/util/opts.rs235
-rw-r--r--components/util/range.rs355
-rw-r--r--components/util/smallvec.rs530
-rw-r--r--components/util/sort.rs101
-rw-r--r--components/util/str.rs111
-rw-r--r--components/util/task.rs40
-rw-r--r--components/util/time.rs241
-rw-r--r--components/util/vec.rs124
-rw-r--r--components/util/workqueue.rs291
509 files changed, 89980 insertions, 0 deletions
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml
new file mode 100644
index 00000000000..7b80f00f12d
--- /dev/null
+++ b/components/canvas/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "canvas"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "canvas"
+path = "lib.rs"
+
+[dependencies.azure]
+git = "https://github.com/servo/rust-azure"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
diff --git a/components/canvas/canvas_render_task.rs b/components/canvas/canvas_render_task.rs
new file mode 100644
index 00000000000..7eafd1aef09
--- /dev/null
+++ b/components/canvas/canvas_render_task.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use azure::azure_hl::{DrawTarget, Color, B8G8R8A8, SkiaBackend, StrokeOptions, DrawOptions};
+use azure::azure_hl::ColorPattern;
+use geom::rect::Rect;
+use geom::size::Size2D;
+
+use std::comm;
+use std::task::TaskBuilder;
+
+pub enum CanvasMsg {
+ FillRect(Rect<f32>),
+ ClearRect(Rect<f32>),
+ StrokeRect(Rect<f32>),
+ Recreate(Size2D<i32>),
+ Close,
+}
+
+pub struct CanvasRenderTask {
+ drawtarget: DrawTarget,
+ fill_color: ColorPattern,
+ stroke_color: ColorPattern,
+ stroke_opts: StrokeOptions,
+}
+
+impl CanvasRenderTask {
+ fn new(size: Size2D<i32>) -> CanvasRenderTask {
+ CanvasRenderTask {
+ drawtarget: CanvasRenderTask::create(size),
+ fill_color: ColorPattern::new(Color::new(0., 0., 0., 1.)),
+ stroke_color: ColorPattern::new(Color::new(0., 0., 0., 1.)),
+ stroke_opts: StrokeOptions::new(1.0, 1.0),
+ }
+ }
+
+ pub fn start(size: Size2D<i32>) -> Sender<CanvasMsg> {
+ let (chan, port) = comm::channel::<CanvasMsg>();
+ let builder = TaskBuilder::new().named("CanvasTask");
+ builder.spawn(proc() {
+ let mut renderer = CanvasRenderTask::new(size);
+
+ loop {
+ match port.recv() {
+ FillRect(ref rect) => renderer.fill_rect(rect),
+ StrokeRect(ref rect) => renderer.stroke_rect(rect),
+ ClearRect(ref rect) => renderer.clear_rect(rect),
+ Recreate(size) => renderer.recreate(size),
+ Close => break,
+ }
+ }
+ });
+ chan
+ }
+
+ fn fill_rect(&self, rect: &Rect<f32>) {
+ let drawopts = DrawOptions::new(1.0, 0);
+ self.drawtarget.fill_rect(rect, &self.fill_color, Some(&drawopts));
+ }
+
+ fn clear_rect(&self, rect: &Rect<f32>) {
+ self.drawtarget.clear_rect(rect);
+ }
+
+ fn stroke_rect(&self, rect: &Rect<f32>) {
+ let drawopts = DrawOptions::new(1.0, 0);
+ self.drawtarget.stroke_rect(rect, &self.stroke_color, &self.stroke_opts, &drawopts);
+ }
+
+ fn create(size: Size2D<i32>) -> DrawTarget {
+ DrawTarget::new(SkiaBackend, size, B8G8R8A8)
+ }
+
+ fn recreate(&mut self, size: Size2D<i32>) {
+ self.drawtarget = CanvasRenderTask::create(size);
+ }
+}
diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs
new file mode 100644
index 00000000000..0dc2fb7e342
--- /dev/null
+++ b/components/canvas/lib.rs
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate azure;
+extern crate geom;
+
+pub mod canvas_render_task;
diff --git a/components/compositing/Cargo.toml b/components/compositing/Cargo.toml
new file mode 100644
index 00000000000..d9b6adc57d2
--- /dev/null
+++ b/components/compositing/Cargo.toml
@@ -0,0 +1,61 @@
+[package]
+name = "compositing"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "compositing"
+path = "lib.rs"
+
+[dependencies.gfx]
+path = "../gfx"
+
+[dependencies.layout_traits]
+path = "../layout_traits"
+
+[dependencies.script_traits]
+path = "../script_traits"
+
+[dependencies.msg]
+path = "../msg"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.alert]
+git = "https://github.com/servo/rust-alert"
+
+[dependencies.azure]
+git = "https://github.com/servo/rust-azure"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.glfw]
+git = "https://github.com/servo/glfw-rs"
+branch = "servo"
+
+[dependencies.layers]
+git = "https://github.com/servo/rust-layers"
+
+[dependencies.opengles]
+git = "https://github.com/servo/rust-opengles"
+
+[dependencies.png]
+git = "https://github.com/servo/rust-png"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
+
+[dependencies.core_graphics]
+git = "https://github.com/servo/rust-core-graphics"
+
+[dependencies.core_text]
+git = "https://github.com/servo/rust-core-text"
+
+[dependencies.glut]
+git = "https://github.com/servo/rust-glut"
+
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs
new file mode 100644
index 00000000000..087e9b4773c
--- /dev/null
+++ b/components/compositing/compositor.rs
@@ -0,0 +1,924 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use compositor_data::{CompositorData, DoesntWantScrollEvents, WantsScrollEvents};
+use compositor_task::{Msg, CompositorTask, Exit, ChangeReadyState, SetIds, LayerProperties};
+use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer};
+use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
+use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
+use constellation::SendableFrameTree;
+use events;
+use pipeline::CompositionPipeline;
+use platform::{Application, Window};
+use windowing;
+use windowing::{FinishedWindowEvent, IdleWindowEvent, LoadUrlWindowEvent, MouseWindowClickEvent};
+use windowing::{MouseWindowEvent, MouseWindowEventClass, MouseWindowMouseDownEvent};
+use windowing::{MouseWindowMouseUpEvent, MouseWindowMoveEventClass, NavigationWindowEvent};
+use windowing::{QuitWindowEvent, RefreshWindowEvent, ResizeWindowEvent, ScrollWindowEvent};
+use windowing::{WindowEvent, WindowMethods, WindowNavigateMsg, ZoomWindowEvent};
+use windowing::PinchZoomWindowEvent;
+
+use azure::azure_hl::SourceSurfaceMethods;
+use azure::azure_hl;
+use geom::matrix::identity;
+use geom::point::{Point2D, TypedPoint2D};
+use geom::rect::Rect;
+use geom::size::TypedSize2D;
+use geom::scale_factor::ScaleFactor;
+use gfx::render_task::{RenderChan, RenderMsg, RenderRequest, UnusedBufferMsg};
+use layers::geometry::DevicePixel;
+use layers::layers::{BufferRequest, Layer, LayerBufferSet};
+use layers::rendergl;
+use layers::rendergl::RenderContext;
+use layers::scene::Scene;
+use opengles::gl2;
+use png;
+use servo_msg::compositor_msg::{Blank, Epoch, FixedPosition, FinishedLoading, IdleRenderState};
+use servo_msg::compositor_msg::{LayerId, ReadyState, RenderState};
+use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, LoadUrlMsg, NavigateMsg};
+use servo_msg::constellation_msg::{PipelineId, ResizedWindowMsg, WindowSizeData};
+use servo_msg::constellation_msg;
+use servo_util::geometry::{PagePx, ScreenPx, ViewportPx};
+use servo_util::memory::MemoryProfilerChan;
+use servo_util::opts::Opts;
+use servo_util::time::{profile, TimeProfilerChan};
+use servo_util::{memory, time};
+use std::io::timer::sleep;
+use std::collections::hashmap::HashMap;
+use std::path::Path;
+use std::rc::Rc;
+use time::precise_time_s;
+use url::Url;
+
+
+pub struct IOCompositor {
+ /// The application window.
+ window: Rc<Window>,
+
+ /// The port on which we receive messages.
+ port: Receiver<Msg>,
+
+ /// The render context.
+ context: RenderContext,
+
+ /// The root pipeline.
+ root_pipeline: Option<CompositionPipeline>,
+
+ /// The canvas to paint a page.
+ scene: Scene<CompositorData>,
+
+ /// The application window size.
+ window_size: TypedSize2D<DevicePixel, uint>,
+
+ /// "Mobile-style" zoom that does not reflow the page.
+ viewport_zoom: ScaleFactor<PagePx, ViewportPx, f32>,
+
+ /// "Desktop-style" zoom that resizes the viewport to fit the window.
+ /// See `ViewportPx` docs in util/geom.rs for details.
+ page_zoom: ScaleFactor<ViewportPx, ScreenPx, f32>,
+
+ /// The device pixel ratio for this window.
+ hidpi_factor: ScaleFactor<ScreenPx, DevicePixel, f32>,
+
+ /// Tracks whether the renderer has finished its first rendering
+ composite_ready: bool,
+
+ /// Tracks whether we are in the process of shutting down, or have shut down and should close
+ /// the compositor.
+ shutdown_state: ShutdownState,
+
+ /// Tracks whether we need to re-composite a page.
+ recomposite: bool,
+
+ /// Tracks outstanding render_msg's sent to the render tasks.
+ outstanding_render_msgs: uint,
+
+ /// Tracks whether the zoom action has happend recently.
+ zoom_action: bool,
+
+ /// The time of the last zoom action has started.
+ zoom_time: f64,
+
+ /// Current display/reflow status of the page
+ ready_state: ReadyState,
+
+ /// Whether the page being rendered has loaded completely.
+ /// Differs from ReadyState because we can finish loading (ready)
+ /// many times for a single page.
+ load_complete: bool,
+
+ /// The command line option flags.
+ opts: Opts,
+
+ /// The channel on which messages can be sent to the constellation.
+ constellation_chan: ConstellationChan,
+
+ /// The channel on which messages can be sent to the time profiler.
+ time_profiler_chan: TimeProfilerChan,
+
+ /// The channel on which messages can be sent to the memory profiler.
+ memory_profiler_chan: MemoryProfilerChan,
+
+ /// Pending scroll to fragment event, if any
+ fragment_point: Option<Point2D<f32>>
+}
+
+#[deriving(PartialEq)]
+enum ShutdownState {
+ NotShuttingDown,
+ ShuttingDown,
+ FinishedShuttingDown,
+}
+
+impl IOCompositor {
+ fn new(app: &Application,
+ opts: Opts,
+ port: Receiver<Msg>,
+ constellation_chan: ConstellationChan,
+ time_profiler_chan: TimeProfilerChan,
+ memory_profiler_chan: MemoryProfilerChan) -> IOCompositor {
+ let window: Rc<Window> = WindowMethods::new(app, opts.output_file.is_none());
+
+ // Create an initial layer tree.
+ //
+ // TODO: There should be no initial layer tree until the renderer creates one from the
+ // display list. This is only here because we don't have that logic in the renderer yet.
+ let window_size = window.framebuffer_size();
+ let hidpi_factor = window.hidpi_factor();
+
+ let show_debug_borders = opts.show_debug_borders;
+ IOCompositor {
+ window: window,
+ port: port,
+ opts: opts,
+ context: rendergl::RenderContext::new(CompositorTask::create_graphics_context(),
+ show_debug_borders),
+ root_pipeline: None,
+ scene: Scene::new(window_size.as_f32().to_untyped(), identity()),
+ window_size: window_size,
+ hidpi_factor: hidpi_factor,
+ composite_ready: false,
+ shutdown_state: NotShuttingDown,
+ recomposite: false,
+ page_zoom: ScaleFactor(1.0),
+ viewport_zoom: ScaleFactor(1.0),
+ zoom_action: false,
+ zoom_time: 0f64,
+ ready_state: Blank,
+ load_complete: false,
+ constellation_chan: constellation_chan,
+ time_profiler_chan: time_profiler_chan,
+ memory_profiler_chan: memory_profiler_chan,
+ fragment_point: None,
+ outstanding_render_msgs: 0,
+ }
+ }
+
+ pub fn create(app: &Application,
+ opts: Opts,
+ port: Receiver<Msg>,
+ constellation_chan: ConstellationChan,
+ time_profiler_chan: TimeProfilerChan,
+ memory_profiler_chan: MemoryProfilerChan) {
+ let mut compositor = IOCompositor::new(app,
+ opts,
+ port,
+ constellation_chan,
+ time_profiler_chan,
+ memory_profiler_chan);
+ compositor.update_zoom_transform();
+
+ // Starts the compositor, which listens for messages on the specified port.
+ compositor.run();
+ }
+
+ fn run (&mut self) {
+ // Tell the constellation about the initial window size.
+ self.send_window_size();
+
+ // Enter the main event loop.
+ while self.shutdown_state != FinishedShuttingDown {
+ // Check for new messages coming from the rendering task.
+ self.handle_message();
+
+ if self.shutdown_state == FinishedShuttingDown {
+ // We have exited the compositor and passing window
+ // messages to script may crash.
+ debug!("Exiting the compositor due to a request from script.");
+ break;
+ }
+
+ // Check for messages coming from the windowing system.
+ let msg = self.window.recv();
+ self.handle_window_message(msg);
+
+ // If asked to recomposite and renderer has run at least once
+ if self.recomposite && self.composite_ready {
+ self.recomposite = false;
+ self.composite();
+ }
+
+ sleep(10);
+
+ // If a pinch-zoom happened recently, ask for tiles at the new resolution
+ if self.zoom_action && precise_time_s() - self.zoom_time > 0.3 {
+ self.zoom_action = false;
+ self.scene.mark_layer_contents_as_changed_recursively();
+ self.send_buffer_requests_for_all_layers();
+ }
+
+ }
+
+ // Clear out the compositor layers so that painting tasks can destroy the buffers.
+ match self.scene.root {
+ None => {}
+ Some(ref layer) => CompositorData::forget_all_tiles(layer.clone()),
+ }
+
+ // Drain compositor port, sometimes messages contain channels that are blocking
+ // another task from finishing (i.e. SetIds)
+ loop {
+ match self.port.try_recv() {
+ Err(_) => break,
+ Ok(_) => {},
+ }
+ }
+
+ // Tell the profiler and memory profiler to shut down.
+ let TimeProfilerChan(ref time_profiler_chan) = self.time_profiler_chan;
+ time_profiler_chan.send(time::ExitMsg);
+
+ let MemoryProfilerChan(ref memory_profiler_chan) = self.memory_profiler_chan;
+ memory_profiler_chan.send(memory::ExitMsg);
+ }
+
+ fn handle_message(&mut self) {
+ loop {
+ match (self.port.try_recv(), self.shutdown_state) {
+ (_, FinishedShuttingDown) =>
+ fail!("compositor shouldn't be handling messages after shutting down"),
+
+ (Err(_), _) => break,
+
+ (Ok(Exit(chan)), _) => {
+ debug!("shutting down the constellation");
+ let ConstellationChan(ref con_chan) = self.constellation_chan;
+ con_chan.send(ExitMsg);
+ chan.send(());
+ self.shutdown_state = ShuttingDown;
+ }
+
+ (Ok(ShutdownComplete), _) => {
+ debug!("constellation completed shutdown");
+ self.shutdown_state = FinishedShuttingDown;
+ break;
+ }
+
+ (Ok(ChangeReadyState(ready_state)), NotShuttingDown) => {
+ self.window.set_ready_state(ready_state);
+ self.ready_state = ready_state;
+ }
+
+ (Ok(ChangeRenderState(render_state)), NotShuttingDown) => {
+ self.change_render_state(render_state);
+ }
+
+ (Ok(RenderMsgDiscarded), NotShuttingDown) => {
+ self.remove_outstanding_render_msg();
+ }
+
+ (Ok(SetIds(frame_tree, response_chan, new_constellation_chan)), _) => {
+ self.set_ids(frame_tree, response_chan, new_constellation_chan);
+ }
+
+ (Ok(GetGraphicsMetadata(chan)), NotShuttingDown) => {
+ chan.send(Some(azure_hl::current_graphics_metadata()));
+ }
+
+ (Ok(CreateOrUpdateRootLayer(layer_properties)),
+ NotShuttingDown) => {
+ self.create_or_update_root_layer(layer_properties);
+ }
+
+ (Ok(CreateOrUpdateDescendantLayer(layer_properties)),
+ NotShuttingDown) => {
+ self.create_or_update_descendant_layer(layer_properties);
+ }
+
+ (Ok(SetLayerClipRect(pipeline_id, layer_id, new_rect)), NotShuttingDown) => {
+ self.set_layer_clip_rect(pipeline_id, layer_id, new_rect);
+ }
+
+ (Ok(Paint(pipeline_id, epoch, replies)), NotShuttingDown) => {
+ for (layer_id, new_layer_buffer_set) in replies.move_iter() {
+ self.paint(pipeline_id, layer_id, new_layer_buffer_set, epoch);
+ }
+ self.remove_outstanding_render_msg();
+ }
+
+ (Ok(ScrollFragmentPoint(pipeline_id, layer_id, point)), NotShuttingDown) => {
+ self.scroll_fragment_to_point(pipeline_id, layer_id, point);
+ }
+
+ (Ok(LoadComplete(..)), NotShuttingDown) => {
+ self.load_complete = true;
+ }
+
+ // When we are shutting_down, we need to avoid performing operations
+ // such as Paint that may crash because we have begun tearing down
+ // the rest of our resources.
+ (_, ShuttingDown) => { }
+ }
+ }
+ }
+
+ fn change_render_state(&mut self, render_state: RenderState) {
+ self.window.set_render_state(render_state);
+ if render_state == IdleRenderState {
+ self.composite_ready = true;
+ }
+ }
+
+ fn has_render_msg_tracking(&self) -> bool {
+ // only track RenderMsg's if the compositor outputs to a file.
+ self.opts.output_file.is_some()
+ }
+
+ fn has_outstanding_render_msgs(&self) -> bool {
+ self.has_render_msg_tracking() && self.outstanding_render_msgs > 0
+ }
+
+ fn add_outstanding_render_msg(&mut self, count: uint) {
+ // return early if not tracking render_msg's
+ if !self.has_render_msg_tracking() {
+ return;
+ }
+ debug!("add_outstanding_render_msg {}", self.outstanding_render_msgs);
+ self.outstanding_render_msgs += count;
+ }
+
+ fn remove_outstanding_render_msg(&mut self) {
+ if !self.has_render_msg_tracking() {
+ return;
+ }
+ if self.outstanding_render_msgs > 0 {
+ self.outstanding_render_msgs -= 1;
+ } else {
+ debug!("too many rerender msgs completed");
+ }
+ }
+
+ fn set_ids(&mut self,
+ frame_tree: SendableFrameTree,
+ response_chan: Sender<()>,
+ new_constellation_chan: ConstellationChan) {
+ response_chan.send(());
+
+ self.root_pipeline = Some(frame_tree.pipeline.clone());
+
+ // Initialize the new constellation channel by sending it the root window size.
+ self.constellation_chan = new_constellation_chan;
+ self.send_window_size();
+ }
+
+ fn find_layer_with_pipeline_and_layer_id(&self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId)
+ -> Option<Rc<Layer<CompositorData>>> {
+ match self.scene.root {
+ Some(ref root_layer) => {
+ CompositorData::find_layer_with_pipeline_and_layer_id(root_layer.clone(),
+ pipeline_id,
+ layer_id)
+ }
+ None => None,
+ }
+
+ }
+
+ fn update_layer_if_exists(&mut self, properties: LayerProperties) -> bool {
+ match self.find_layer_with_pipeline_and_layer_id(properties.pipeline_id, properties.id) {
+ Some(existing_layer) => {
+ CompositorData::update_layer(existing_layer.clone(), properties);
+ true
+ }
+ None => false,
+ }
+ }
+
+ // rust-layers keeps everything in layer coordinates, so we must convert all rectangles
+ // from page coordinates into layer coordinates based on our current scale.
+ fn convert_page_rect_to_layer_coordinates(&self, page_rect: Rect<f32>) -> Rect<f32> {
+ page_rect * self.device_pixels_per_page_px().get()
+ }
+
+ fn create_or_update_root_layer(&mut self, mut layer_properties: LayerProperties) {
+ layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
+
+ let need_new_root_layer = !self.update_layer_if_exists(layer_properties);
+ if need_new_root_layer {
+ let root_pipeline = match self.root_pipeline {
+ Some(ref root_pipeline) => root_pipeline.clone(),
+ None => fail!("Compositor: Making new layer without initialized pipeline"),
+ };
+
+ let root_properties = LayerProperties {
+ pipeline_id: root_pipeline.id,
+ epoch: layer_properties.epoch,
+ id: LayerId::null(),
+ rect: layer_properties.rect,
+ background_color: layer_properties.background_color,
+ scroll_policy: FixedPosition,
+ };
+ let new_root = CompositorData::new_layer(root_pipeline.clone(),
+ root_properties,
+ WantsScrollEvents,
+ self.opts.tile_size);
+ let first_chid = CompositorData::new_layer(root_pipeline.clone(),
+ layer_properties,
+ DoesntWantScrollEvents,
+ self.opts.tile_size);
+ new_root.add_child(first_chid);
+
+ // Release all tiles from the layer before dropping it.
+ match self.scene.root {
+ Some(ref mut layer) => CompositorData::clear_all_tiles(layer.clone()),
+ None => { }
+ }
+ self.scene.root = Some(new_root);
+ }
+
+ self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id,
+ layer_properties.id);
+ self.send_buffer_requests_for_all_layers();
+ }
+
+ fn create_or_update_descendant_layer(&mut self, mut layer_properties: LayerProperties) {
+ layer_properties.rect = self.convert_page_rect_to_layer_coordinates(layer_properties.rect);
+ if !self.update_layer_if_exists(layer_properties) {
+ self.create_descendant_layer(layer_properties);
+ }
+ self.scroll_layer_to_fragment_point_if_necessary(layer_properties.pipeline_id,
+ layer_properties.id);
+ self.send_buffer_requests_for_all_layers();
+ }
+
+ fn create_descendant_layer(&self, layer_properties: LayerProperties) {
+ match self.scene.root {
+ Some(ref root_layer) => {
+ let root_layer_pipeline = root_layer.extra_data.borrow().pipeline.clone();
+ if root_layer_pipeline.id != layer_properties.pipeline_id {
+ fail!("Compositor: New layer pipeline does not match root layer pipeline");
+ }
+
+ let new_layer = CompositorData::new_layer(root_layer_pipeline,
+ layer_properties,
+ DoesntWantScrollEvents,
+ root_layer.tile_size);
+ root_layer.add_child(new_layer);
+ }
+ None => fail!("Compositor: Received new layer without root layer")
+ }
+ }
+
+ fn send_window_size(&self) {
+ let dppx = self.page_zoom * self.device_pixels_per_screen_px();
+ let initial_viewport = self.window_size.as_f32() / dppx;
+ let visible_viewport = initial_viewport / self.viewport_zoom;
+
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ResizedWindowMsg(WindowSizeData {
+ device_pixel_ratio: dppx,
+ initial_viewport: initial_viewport,
+ visible_viewport: visible_viewport,
+ }));
+ }
+
+ fn scroll_layer_to_fragment_point_if_necessary(&mut self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId) {
+ let device_pixels_per_page_px = self.device_pixels_per_page_px();
+ let window_size = self.window_size.as_f32();
+ let needs_recomposite = match self.scene.root {
+ Some(ref mut root_layer) => {
+ self.fragment_point.take().map_or(false, |fragment_point| {
+ let fragment_point = fragment_point * device_pixels_per_page_px.get();
+ events::move(root_layer.clone(),
+ pipeline_id,
+ layer_id,
+ Point2D::from_untyped(&fragment_point),
+ window_size)
+ })
+ }
+ None => fail!("Compositor: Tried to scroll to fragment without root layer."),
+ };
+
+ self.recomposite_if(needs_recomposite);
+ }
+
+ fn set_layer_clip_rect(&mut self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ new_rect_in_page_coordinates: Rect<f32>) {
+ let new_rect_in_layer_coordinates =
+ self.convert_page_rect_to_layer_coordinates(new_rect_in_page_coordinates);
+ let new_rect_in_layer_coordinates = Rect::from_untyped(&new_rect_in_layer_coordinates);
+
+ match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
+ Some(ref layer) => *layer.bounds.borrow_mut() = new_rect_in_layer_coordinates,
+ None => fail!("compositor received SetLayerClipRect for nonexistent layer"),
+ };
+
+ self.send_buffer_requests_for_all_layers();
+ }
+
+ fn paint(&mut self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ new_layer_buffer_set: Box<LayerBufferSet>,
+ epoch: Epoch) {
+ debug!("compositor received new frame");
+
+ // From now on, if we destroy the buffers, they will leak.
+ let mut new_layer_buffer_set = new_layer_buffer_set;
+ new_layer_buffer_set.mark_will_leak();
+
+ match self.find_layer_with_pipeline_and_layer_id(pipeline_id, layer_id) {
+ Some(ref layer) => {
+ assert!(CompositorData::add_buffers(layer.clone(), new_layer_buffer_set, epoch));
+ self.recomposite = true;
+ }
+ None => {
+ // FIXME: This may potentially be triggered by a race condition where a
+ // buffers are being rendered but the layer is removed before rendering
+ // completes.
+ fail!("compositor given paint command for non-existent layer");
+ }
+ }
+ }
+
+ fn scroll_fragment_to_point(&mut self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ point: Point2D<f32>) {
+
+ let device_pixels_per_page_px = self.device_pixels_per_page_px();
+ let device_point = point * device_pixels_per_page_px.get();
+ let window_size = self.window_size.as_f32();
+
+ let (ask, move): (bool, bool) = match self.scene.root {
+ Some(ref layer) if layer.extra_data.borrow().pipeline.id == pipeline_id => {
+ (true,
+ events::move(layer.clone(),
+ pipeline_id,
+ layer_id,
+ Point2D::from_untyped(&device_point),
+ window_size))
+ }
+ Some(_) | None => {
+ self.fragment_point = Some(point);
+
+ (false, false)
+ }
+ };
+
+ if ask {
+ self.recomposite_if(move);
+ self.send_buffer_requests_for_all_layers();
+ }
+ }
+
+ fn handle_window_message(&mut self, event: WindowEvent) {
+ match event {
+ IdleWindowEvent => {}
+
+ RefreshWindowEvent => {
+ self.recomposite = true;
+ }
+
+ ResizeWindowEvent(size) => {
+ self.on_resize_window_event(size);
+ }
+
+ LoadUrlWindowEvent(url_string) => {
+ self.on_load_url_window_event(url_string);
+ }
+
+ MouseWindowEventClass(mouse_window_event) => {
+ self.on_mouse_window_event_class(mouse_window_event);
+ }
+
+ MouseWindowMoveEventClass(cursor) => {
+ self.on_mouse_window_move_event_class(cursor);
+ }
+
+ ScrollWindowEvent(delta, cursor) => {
+ self.on_scroll_window_event(delta, cursor);
+ }
+
+ ZoomWindowEvent(magnification) => {
+ self.on_zoom_window_event(magnification);
+ }
+
+ PinchZoomWindowEvent(magnification) => {
+ self.on_pinch_zoom_window_event(magnification);
+ }
+
+ NavigationWindowEvent(direction) => {
+ self.on_navigation_window_event(direction);
+ }
+
+ FinishedWindowEvent => {
+ let exit = self.opts.exit_after_load;
+ if exit {
+ debug!("shutting down the constellation for FinishedWindowEvent");
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ExitMsg);
+ self.shutdown_state = ShuttingDown;
+ }
+ }
+
+ QuitWindowEvent => {
+ debug!("shutting down the constellation for QuitWindowEvent");
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ExitMsg);
+ self.shutdown_state = ShuttingDown;
+ }
+ }
+ }
+
+ fn on_resize_window_event(&mut self, new_size: TypedSize2D<DevicePixel, uint>) {
+ // A size change could also mean a resolution change.
+ let new_hidpi_factor = self.window.hidpi_factor();
+ if self.hidpi_factor != new_hidpi_factor {
+ self.hidpi_factor = new_hidpi_factor;
+ self.update_zoom_transform();
+ }
+ if self.window_size != new_size {
+ debug!("osmain: window resized to {:?}", new_size);
+ self.window_size = new_size;
+ self.send_window_size();
+ } else {
+ debug!("osmain: dropping window resize since size is still {:?}", new_size);
+ }
+ }
+
+ fn on_load_url_window_event(&mut self, url_string: String) {
+ debug!("osmain: loading URL `{:s}`", url_string);
+ self.load_complete = false;
+ let root_pipeline_id = match self.scene.root {
+ Some(ref layer) => layer.extra_data.borrow().pipeline.id.clone(),
+ None => fail!("Compositor: Received LoadUrlWindowEvent without initialized compositor \
+ layers"),
+ };
+
+ let msg = LoadUrlMsg(root_pipeline_id, Url::parse(url_string.as_slice()).unwrap());
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(msg);
+ }
+
+ fn on_mouse_window_event_class(&self, mouse_window_event: MouseWindowEvent) {
+ let scale = self.device_pixels_per_page_px();
+ let point = match mouse_window_event {
+ MouseWindowClickEvent(_, p) => p,
+ MouseWindowMouseDownEvent(_, p) => p,
+ MouseWindowMouseUpEvent(_, p) => p,
+ };
+ for layer in self.scene.root.iter() {
+ events::send_mouse_event(layer.clone(), mouse_window_event, point, scale);
+ }
+ }
+
+ fn on_mouse_window_move_event_class(&self, cursor: TypedPoint2D<DevicePixel, f32>) {
+ let scale = self.device_pixels_per_page_px();
+ for layer in self.scene.root.iter() {
+ events::send_mouse_move_event(layer.clone(), cursor / scale);
+ }
+ }
+
+ fn on_scroll_window_event(&mut self,
+ delta: TypedPoint2D<DevicePixel, f32>,
+ cursor: TypedPoint2D<DevicePixel, i32>) {
+ let mut scroll = false;
+ let window_size = self.window_size.as_f32();
+ match self.scene.root {
+ Some(ref mut layer) => {
+ scroll = events::handle_scroll_event(layer.clone(),
+ delta,
+ cursor.as_f32(),
+ window_size) || scroll;
+ }
+ None => { }
+ }
+ self.recomposite_if(scroll);
+ self.send_buffer_requests_for_all_layers();
+ }
+
+ fn device_pixels_per_screen_px(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
+ match self.opts.device_pixels_per_px {
+ Some(device_pixels_per_px) => device_pixels_per_px,
+ None => match self.opts.output_file {
+ Some(_) => ScaleFactor(1.0),
+ None => self.hidpi_factor
+ }
+ }
+ }
+
+ fn device_pixels_per_page_px(&self) -> ScaleFactor<PagePx, DevicePixel, f32> {
+ self.viewport_zoom * self.page_zoom * self.device_pixels_per_screen_px()
+ }
+
+ fn update_zoom_transform(&mut self) {
+ let scale = self.device_pixels_per_page_px();
+ self.scene.transform = identity().scale(scale.get(), scale.get(), 1f32);
+ }
+
+ fn on_zoom_window_event(&mut self, magnification: f32) {
+ self.page_zoom = ScaleFactor((self.page_zoom.get() * magnification).max(1.0));
+ self.update_zoom_transform();
+ self.send_window_size();
+ }
+
+ fn on_pinch_zoom_window_event(&mut self, magnification: f32) {
+ self.zoom_action = true;
+ self.zoom_time = precise_time_s();
+ let old_viewport_zoom = self.viewport_zoom;
+
+ self.viewport_zoom = ScaleFactor((self.viewport_zoom.get() * magnification).max(1.0));
+ let viewport_zoom = self.viewport_zoom;
+
+ self.update_zoom_transform();
+
+ // Scroll as needed
+ let window_size = self.window_size.as_f32();
+ let page_delta: TypedPoint2D<PagePx, f32> = TypedPoint2D(
+ window_size.width.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5,
+ window_size.height.get() * (viewport_zoom.inv() - old_viewport_zoom.inv()).get() * 0.5);
+
+ let delta = page_delta * self.device_pixels_per_page_px();
+ let cursor = TypedPoint2D(-1f32, -1f32); // Make sure this hits the base layer.
+ match self.scene.root {
+ Some(ref mut layer) => {
+ events::handle_scroll_event(layer.clone(),
+ delta,
+ cursor,
+ window_size);
+ }
+ None => { }
+ }
+
+ self.recomposite = true;
+ }
+
+ fn on_navigation_window_event(&self, direction: WindowNavigateMsg) {
+ let direction = match direction {
+ windowing::Forward => constellation_msg::Forward,
+ windowing::Back => constellation_msg::Back,
+ };
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(NavigateMsg(direction))
+ }
+
+ fn convert_buffer_requests_to_pipeline_requests_map(&self,
+ requests: Vec<(Rc<Layer<CompositorData>>,
+ Vec<BufferRequest>)>) ->
+ HashMap<PipelineId, (RenderChan,
+ Vec<RenderRequest>)> {
+ let scale = self.device_pixels_per_page_px();
+ let mut results:
+ HashMap<PipelineId, (RenderChan, Vec<RenderRequest>)> = HashMap::new();
+
+ for (layer, mut layer_requests) in requests.move_iter() {
+ let pipeline_id = layer.extra_data.borrow().pipeline.id;
+ let &(_, ref mut vec) = results.find_or_insert_with(pipeline_id, |_| {
+ (layer.extra_data.borrow().pipeline.render_chan.clone(), Vec::new())
+ });
+
+ // All the BufferRequests are in layer/device coordinates, but the render task
+ // wants to know the page coordinates. We scale them before sending them.
+ for request in layer_requests.mut_iter() {
+ request.page_rect = request.page_rect / scale.get();
+ }
+
+ vec.push(RenderRequest {
+ buffer_requests: layer_requests,
+ scale: scale.get(),
+ layer_id: layer.extra_data.borrow().id,
+ epoch: layer.extra_data.borrow().epoch,
+ });
+ }
+
+ return results;
+ }
+
+ fn send_back_unused_buffers(&mut self) {
+ match self.root_pipeline {
+ Some(ref pipeline) => {
+ let unused_buffers = self.scene.collect_unused_buffers();
+ let have_unused_buffers = unused_buffers.len() > 0;
+ self.recomposite = self.recomposite || have_unused_buffers;
+ if have_unused_buffers {
+ let message = UnusedBufferMsg(unused_buffers);
+ let _ = pipeline.render_chan.send_opt(message);
+ }
+ },
+ None => {}
+ }
+ }
+
+ fn send_buffer_requests_for_all_layers(&mut self) {
+ let mut layers_and_requests = Vec::new();
+ self.scene.get_buffer_requests(&mut layers_and_requests,
+ Rect(TypedPoint2D(0f32, 0f32), self.window_size.as_f32()));
+
+ // Return unused tiles first, so that they can be reused by any new BufferRequests.
+ self.send_back_unused_buffers();
+
+ if layers_and_requests.len() == 0 {
+ return;
+ }
+
+ // We want to batch requests for each pipeline to avoid race conditions
+ // when handling the resulting BufferRequest responses.
+ let pipeline_requests =
+ self.convert_buffer_requests_to_pipeline_requests_map(layers_and_requests);
+
+ let mut num_render_msgs_sent = 0;
+ for (_pipeline_id, (chan, requests)) in pipeline_requests.move_iter() {
+ num_render_msgs_sent += 1;
+ let _ = chan.send_opt(RenderMsg(requests));
+ }
+
+ self.add_outstanding_render_msg(num_render_msgs_sent);
+ }
+
+ fn composite(&mut self) {
+ profile(time::CompositingCategory, self.time_profiler_chan.clone(), || {
+ debug!("compositor: compositing");
+ // Adjust the layer dimensions as necessary to correspond to the size of the window.
+ self.scene.size = self.window_size.as_f32().to_untyped();
+ // Render the scene.
+ match self.scene.root {
+ Some(ref layer) => {
+ self.scene.background_color.r = layer.extra_data.borrow().background_color.r;
+ self.scene.background_color.g = layer.extra_data.borrow().background_color.g;
+ self.scene.background_color.b = layer.extra_data.borrow().background_color.b;
+ self.scene.background_color.a = layer.extra_data.borrow().background_color.a;
+ rendergl::render_scene(layer.clone(), self.context, &self.scene);
+ }
+ None => {}
+ }
+ });
+
+ // Render to PNG. We must read from the back buffer (ie, before
+ // self.window.present()) as OpenGL ES 2 does not have glReadBuffer().
+ if self.load_complete && self.ready_state == FinishedLoading
+ && self.opts.output_file.is_some() && !self.has_outstanding_render_msgs() {
+ let (width, height) = (self.window_size.width.get(), self.window_size.height.get());
+ let path = from_str::<Path>(self.opts.output_file.get_ref().as_slice()).unwrap();
+ let mut pixels = gl2::read_pixels(0, 0,
+ width as gl2::GLsizei,
+ height as gl2::GLsizei,
+ gl2::RGB, gl2::UNSIGNED_BYTE);
+ // flip image vertically (texture is upside down)
+ let orig_pixels = pixels.clone();
+ let stride = width * 3;
+ for y in range(0, height) {
+ let dst_start = y * stride;
+ let src_start = (height - y - 1) * stride;
+ unsafe {
+ let src_slice = orig_pixels.slice(src_start, src_start + stride);
+ pixels.mut_slice(dst_start, dst_start + stride)
+ .copy_memory(src_slice.slice_to(stride));
+ }
+ }
+ let mut img = png::Image {
+ width: width as u32,
+ height: height as u32,
+ pixels: png::RGB8(pixels),
+ };
+ let res = png::store_png(&mut img, &path);
+ assert!(res.is_ok());
+
+ debug!("shutting down the constellation after generating an output file");
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ExitMsg);
+ self.shutdown_state = ShuttingDown;
+ }
+
+ self.window.present();
+
+ let exit = self.opts.exit_after_load;
+ if exit {
+ debug!("shutting down the constellation for exit_after_load");
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(ExitMsg);
+ }
+ }
+
+ fn recomposite_if(&mut self, result: bool) {
+ self.recomposite = result || self.recomposite;
+ }
+}
+
diff --git a/components/compositing/compositor_data.rs b/components/compositing/compositor_data.rs
new file mode 100644
index 00000000000..fdfeac7656e
--- /dev/null
+++ b/components/compositing/compositor_data.rs
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use compositor_task::LayerProperties;
+use events;
+use pipeline::CompositionPipeline;
+
+use azure::azure_hl::Color;
+use geom::point::TypedPoint2D;
+use geom::size::{Size2D, TypedSize2D};
+use geom::rect::Rect;
+use gfx::render_task::UnusedBufferMsg;
+use layers::geometry::DevicePixel;
+use layers::layers::{Layer, LayerBufferSet};
+use layers::platform::surface::NativeSurfaceMethods;
+use servo_msg::compositor_msg::{Epoch, LayerId};
+use servo_msg::compositor_msg::ScrollPolicy;
+use servo_msg::constellation_msg::PipelineId;
+use std::rc::Rc;
+
+pub struct CompositorData {
+ /// This layer's pipeline. BufferRequests and mouse events will be sent through this.
+ pub pipeline: CompositionPipeline,
+
+ /// The ID of this layer within the pipeline.
+ pub id: LayerId,
+
+ /// The behavior of this layer when a scroll message is received.
+ pub wants_scroll_events: WantsScrollEventsFlag,
+
+ /// Whether an ancestor layer that receives scroll events moves this layer.
+ pub scroll_policy: ScrollPolicy,
+
+ /// The color to use for the unrendered-content void
+ pub background_color: Color,
+
+ /// A monotonically increasing counter that keeps track of the current epoch.
+ /// add_buffer() calls that don't match the current epoch will be ignored.
+ pub epoch: Epoch,
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum WantsScrollEventsFlag {
+ WantsScrollEvents,
+ DoesntWantScrollEvents,
+}
+
+impl CompositorData {
+ pub fn new_layer(pipeline: CompositionPipeline,
+ layer_properties: LayerProperties,
+ wants_scroll_events: WantsScrollEventsFlag,
+ tile_size: uint)
+ -> Rc<Layer<CompositorData>> {
+ let new_compositor_data = CompositorData {
+ pipeline: pipeline,
+ id: layer_properties.id,
+ wants_scroll_events: wants_scroll_events,
+ scroll_policy: layer_properties.scroll_policy,
+ background_color: layer_properties.background_color,
+ epoch: layer_properties.epoch,
+ };
+
+ Rc::new(Layer::new(Rect::from_untyped(&layer_properties.rect),
+ tile_size, new_compositor_data))
+ }
+
+ pub fn update_layer(layer: Rc<Layer<CompositorData>>, layer_properties: LayerProperties) {
+ layer.extra_data.borrow_mut().epoch = layer_properties.epoch;
+ layer.extra_data.borrow_mut().background_color = layer_properties.background_color;
+
+ let size: TypedSize2D<DevicePixel, f32> = Size2D::from_untyped(&layer_properties.rect.size);
+ layer.resize(size);
+ layer.contents_changed();
+
+ // Call scroll for bounds checking if the page shrunk. Use (-1, -1) as the
+ // cursor position to make sure the scroll isn't propagated downwards.
+ events::handle_scroll_event(layer.clone(),
+ TypedPoint2D(0f32, 0f32),
+ TypedPoint2D(-1f32, -1f32),
+ size);
+ }
+
+ pub fn find_layer_with_pipeline_and_layer_id(layer: Rc<Layer<CompositorData>>,
+ pipeline_id: PipelineId,
+ layer_id: LayerId)
+ -> Option<Rc<Layer<CompositorData>>> {
+ if layer.extra_data.borrow().pipeline.id == pipeline_id &&
+ layer.extra_data.borrow().id == layer_id {
+ return Some(layer.clone());
+ }
+
+ for kid in layer.children().iter() {
+ match CompositorData::find_layer_with_pipeline_and_layer_id(kid.clone(),
+ pipeline_id,
+ layer_id) {
+ v @ Some(_) => { return v; }
+ None => { }
+ }
+ }
+
+ return None;
+ }
+
+ // Add LayerBuffers to the specified layer. Returns the layer buffer set back if the layer that
+ // matches the given pipeline ID was not found; otherwise returns None and consumes the layer
+ // buffer set.
+ //
+ // If the epoch of the message does not match the layer's epoch, the message is ignored, the
+ // layer buffer set is consumed, and None is returned.
+ pub fn add_buffers(layer: Rc<Layer<CompositorData>>,
+ new_buffers: Box<LayerBufferSet>,
+ epoch: Epoch)
+ -> bool {
+ if layer.extra_data.borrow().epoch != epoch {
+ debug!("add_buffers: compositor epoch mismatch: {:?} != {:?}, id: {:?}",
+ layer.extra_data.borrow().epoch,
+ epoch,
+ layer.extra_data.borrow().pipeline.id);
+ let msg = UnusedBufferMsg(new_buffers.buffers);
+ let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg);
+ return false;
+ }
+
+ {
+ for buffer in new_buffers.buffers.move_iter().rev() {
+ layer.add_buffer(buffer);
+ }
+
+ let unused_buffers = layer.collect_unused_buffers();
+ if !unused_buffers.is_empty() { // send back unused buffers
+ let msg = UnusedBufferMsg(unused_buffers);
+ let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(msg);
+ }
+ }
+
+ return true;
+ }
+
+ /// Destroys all layer tiles, sending the buffers back to the renderer to be destroyed or
+ /// reused.
+ fn clear(layer: Rc<Layer<CompositorData>>) {
+ let mut buffers = layer.collect_buffers();
+
+ if !buffers.is_empty() {
+ // We have no way of knowing without a race whether the render task is even up and
+ // running, but mark the buffers as not leaking. If the render task died, then the
+ // buffers are going to be cleaned up.
+ for buffer in buffers.mut_iter() {
+ buffer.mark_wont_leak()
+ }
+
+ let _ = layer.extra_data.borrow().pipeline.render_chan.send_opt(UnusedBufferMsg(buffers));
+ }
+ }
+
+ /// Destroys tiles for this layer and all descendent layers, sending the buffers back to the
+ /// renderer to be destroyed or reused.
+ pub fn clear_all_tiles(layer: Rc<Layer<CompositorData>>) {
+ CompositorData::clear(layer.clone());
+ for kid in layer.children().iter() {
+ CompositorData::clear_all_tiles(kid.clone());
+ }
+ }
+
+ /// Destroys all tiles of all layers, including children, *without* sending them back to the
+ /// renderer. You must call this only when the render task is destined to be going down;
+ /// otherwise, you will leak tiles.
+ ///
+ /// This is used during shutdown, when we know the render task is going away.
+ pub fn forget_all_tiles(layer: Rc<Layer<CompositorData>>) {
+ let tiles = layer.collect_buffers();
+ for tile in tiles.move_iter() {
+ let mut tile = tile;
+ tile.mark_wont_leak()
+ }
+
+ for kid in layer.children().iter() {
+ CompositorData::forget_all_tiles(kid.clone());
+ }
+ }
+}
+
diff --git a/components/compositing/compositor_task.rs b/components/compositing/compositor_task.rs
new file mode 100644
index 00000000000..be87ce9462e
--- /dev/null
+++ b/components/compositing/compositor_task.rs
@@ -0,0 +1,247 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use windowing;
+
+use compositor;
+use headless;
+pub use constellation::SendableFrameTree;
+use windowing::{ApplicationMethods, WindowMethods};
+use platform::Application;
+
+use azure::azure_hl::{SourceSurfaceMethods, Color};
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use layers::platform::surface::{NativeCompositingGraphicsContext, NativeGraphicsMetadata};
+use layers::layers::LayerBufferSet;
+use servo_msg::compositor_msg::{Epoch, LayerId, LayerMetadata, ReadyState};
+use servo_msg::compositor_msg::{RenderListener, RenderState, ScriptListener, ScrollPolicy};
+use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
+use servo_util::memory::MemoryProfilerChan;
+use servo_util::opts::Opts;
+use servo_util::time::TimeProfilerChan;
+use std::comm::{channel, Sender, Receiver};
+
+use url::Url;
+
+#[cfg(target_os="linux")]
+use azure::azure_hl;
+
+/// The implementation of the layers-based compositor.
+#[deriving(Clone)]
+pub struct CompositorChan {
+ /// A channel on which messages can be sent to the compositor.
+ pub chan: Sender<Msg>,
+}
+
+/// Implementation of the abstract `ScriptListener` interface.
+impl ScriptListener for CompositorChan {
+ fn set_ready_state(&self, ready_state: ReadyState) {
+ let msg = ChangeReadyState(ready_state);
+ self.chan.send(msg);
+ }
+
+ fn scroll_fragment_point(&self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ point: Point2D<f32>) {
+ self.chan.send(ScrollFragmentPoint(pipeline_id, layer_id, point));
+ }
+
+ fn close(&self) {
+ let (chan, port) = channel();
+ self.chan.send(Exit(chan));
+ port.recv();
+ }
+
+ fn dup(&self) -> Box<ScriptListener> {
+ box self.clone() as Box<ScriptListener>
+ }
+}
+
+pub struct LayerProperties {
+ pub pipeline_id: PipelineId,
+ pub epoch: Epoch,
+ pub id: LayerId,
+ pub rect: Rect<f32>,
+ pub background_color: Color,
+ pub scroll_policy: ScrollPolicy,
+}
+
+impl LayerProperties {
+ fn new(pipeline_id: PipelineId, epoch: Epoch, metadata: &LayerMetadata) -> LayerProperties {
+ LayerProperties {
+ pipeline_id: pipeline_id,
+ epoch: epoch,
+ id: metadata.id,
+ rect: Rect(Point2D(metadata.position.origin.x as f32,
+ metadata.position.origin.y as f32),
+ Size2D(metadata.position.size.width as f32,
+ metadata.position.size.height as f32)),
+ background_color: metadata.background_color,
+ scroll_policy: metadata.scroll_policy,
+ }
+ }
+}
+
+/// Implementation of the abstract `RenderListener` interface.
+impl RenderListener for CompositorChan {
+ fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata> {
+ let (chan, port) = channel();
+ self.chan.send(GetGraphicsMetadata(chan));
+ port.recv()
+ }
+
+ fn paint(&self,
+ pipeline_id: PipelineId,
+ epoch: Epoch,
+ replies: Vec<(LayerId, Box<LayerBufferSet>)>) {
+ self.chan.send(Paint(pipeline_id, epoch, replies));
+ }
+
+ fn initialize_layers_for_pipeline(&self,
+ pipeline_id: PipelineId,
+ metadata: Vec<LayerMetadata>,
+ epoch: Epoch) {
+ // FIXME(#2004, pcwalton): This assumes that the first layer determines the page size, and
+ // that all other layers are immediate children of it. This is sufficient to handle
+ // `position: fixed` but will not be sufficient to handle `overflow: scroll` or transforms.
+ let mut first = true;
+ for metadata in metadata.iter() {
+ let layer_properties = LayerProperties::new(pipeline_id, epoch, metadata);
+ if first {
+ self.chan.send(CreateOrUpdateRootLayer(layer_properties));
+ first = false
+ } else {
+ self.chan.send(CreateOrUpdateDescendantLayer(layer_properties));
+ }
+
+ self.chan.send(SetLayerClipRect(pipeline_id, metadata.id, layer_properties.rect));
+ }
+ }
+
+ fn render_msg_discarded(&self) {
+ self.chan.send(RenderMsgDiscarded);
+ }
+
+ fn set_render_state(&self, render_state: RenderState) {
+ self.chan.send(ChangeRenderState(render_state))
+ }
+}
+
+impl CompositorChan {
+ pub fn new() -> (Receiver<Msg>, CompositorChan) {
+ let (chan, port) = channel();
+ let compositor_chan = CompositorChan {
+ chan: chan,
+ };
+ (port, compositor_chan)
+ }
+
+ pub fn send(&self, msg: Msg) {
+ self.chan.send(msg);
+ }
+}
+/// Messages from the painting task and the constellation task to the compositor task.
+pub enum Msg {
+ /// Requests that the compositor shut down.
+ Exit(Sender<()>),
+
+ /// Informs the compositor that the constellation has completed shutdown.
+ /// Required because the constellation can have pending calls to make (e.g. SetIds)
+ /// at the time that we send it an ExitMsg.
+ ShutdownComplete,
+
+ /// Requests the compositor's graphics metadata. Graphics metadata is what the renderer needs
+ /// to create surfaces that the compositor can see. On Linux this is the X display; on Mac this
+ /// is the pixel format.
+ ///
+ /// The headless compositor returns `None`.
+ GetGraphicsMetadata(Sender<Option<NativeGraphicsMetadata>>),
+
+ /// Tells the compositor to create the root layer for a pipeline if necessary (i.e. if no layer
+ /// with that ID exists).
+ CreateOrUpdateRootLayer(LayerProperties),
+ /// Tells the compositor to create a descendant layer for a pipeline if necessary (i.e. if no
+ /// layer with that ID exists).
+ CreateOrUpdateDescendantLayer(LayerProperties),
+ /// Alerts the compositor that the specified layer's clipping rect has changed.
+ SetLayerClipRect(PipelineId, LayerId, Rect<f32>),
+ /// Scroll a page in a window
+ ScrollFragmentPoint(PipelineId, LayerId, Point2D<f32>),
+ /// Requests that the compositor paint the given layer buffer set for the given page size.
+ Paint(PipelineId, Epoch, Vec<(LayerId, Box<LayerBufferSet>)>),
+ /// Alerts the compositor to the current status of page loading.
+ ChangeReadyState(ReadyState),
+ /// Alerts the compositor to the current status of rendering.
+ ChangeRenderState(RenderState),
+ /// Alerts the compositor that the RenderMsg has been discarded.
+ RenderMsgDiscarded,
+ /// Sets the channel to the current layout and render tasks, along with their id
+ SetIds(SendableFrameTree, Sender<()>, ConstellationChan),
+ /// The load of a page for a given URL has completed.
+ LoadComplete(PipelineId, Url),
+}
+
+pub enum CompositorMode {
+ Windowed(Application),
+ Headless
+}
+
+pub struct CompositorTask {
+ pub mode: CompositorMode,
+}
+
+impl CompositorTask {
+ fn new(is_headless: bool) -> CompositorTask {
+ let mode: CompositorMode = if is_headless {
+ Headless
+ } else {
+ Windowed(ApplicationMethods::new())
+ };
+
+ CompositorTask {
+ mode: mode
+ }
+ }
+
+ /// Creates a graphics context. Platform-specific.
+ ///
+ /// FIXME(pcwalton): Probably could be less platform-specific, using the metadata abstraction.
+ #[cfg(target_os="linux")]
+ pub fn create_graphics_context() -> NativeCompositingGraphicsContext {
+ NativeCompositingGraphicsContext::from_display(azure_hl::current_display())
+ }
+ #[cfg(not(target_os="linux"))]
+ pub fn create_graphics_context() -> NativeCompositingGraphicsContext {
+ NativeCompositingGraphicsContext::new()
+ }
+
+ pub fn create(opts: Opts,
+ port: Receiver<Msg>,
+ constellation_chan: ConstellationChan,
+ time_profiler_chan: TimeProfilerChan,
+ memory_profiler_chan: MemoryProfilerChan) {
+
+ let compositor = CompositorTask::new(opts.headless);
+
+ match compositor.mode {
+ Windowed(ref app) => {
+ compositor::IOCompositor::create(app,
+ opts,
+ port,
+ constellation_chan.clone(),
+ time_profiler_chan,
+ memory_profiler_chan)
+ }
+ Headless => {
+ headless::NullCompositor::create(port,
+ constellation_chan.clone(),
+ time_profiler_chan,
+ memory_profiler_chan)
+ }
+ };
+ }
+}
diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs
new file mode 100644
index 00000000000..0cf02f2ce35
--- /dev/null
+++ b/components/compositing/constellation.rs
@@ -0,0 +1,870 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use compositor_task::{CompositorChan, LoadComplete, ShutdownComplete, SetLayerClipRect, SetIds};
+use std::collections::hashmap::{HashMap, HashSet};
+use geom::rect::{Rect, TypedRect};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use gfx::render_task;
+use libc;
+use pipeline::{Pipeline, CompositionPipeline};
+use layout_traits::{LayoutControlChan, LayoutTaskFactory, ExitNowMsg};
+use script_traits::{ResizeMsg, ResizeInactiveMsg, ExitPipelineMsg};
+use script_traits::{ScriptControlChan, ScriptTaskFactory};
+use servo_msg::compositor_msg::LayerId;
+use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, FailureMsg, Failure, FrameRectMsg};
+use servo_msg::constellation_msg::{IFrameSandboxState, IFrameUnsandboxed, InitLoadUrlMsg};
+use servo_msg::constellation_msg::{LoadCompleteMsg, LoadIframeUrlMsg, LoadUrlMsg, Msg, NavigateMsg};
+use servo_msg::constellation_msg::{NavigationType, PipelineId, RendererReadyMsg, ResizedWindowMsg};
+use servo_msg::constellation_msg::{SubpageId, WindowSizeData};
+use servo_msg::constellation_msg;
+use servo_net::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
+use gfx::font_cache_task::FontCacheTask;
+use servo_net::resource_task::ResourceTask;
+use servo_net::resource_task;
+use servo_util::geometry::PagePx;
+use servo_util::opts::Opts;
+use servo_util::time::TimeProfilerChan;
+use servo_util::task::spawn_named;
+use std::cell::RefCell;
+use std::mem::replace;
+use std::io;
+use std::rc::Rc;
+use url::Url;
+
+/// Maintains the pipelines and navigation context and grants permission to composite
+pub struct Constellation<LTF, STF> {
+ pub chan: ConstellationChan,
+ pub request_port: Receiver<Msg>,
+ pub compositor_chan: CompositorChan,
+ pub resource_task: ResourceTask,
+ pub image_cache_task: ImageCacheTask,
+ pipelines: HashMap<PipelineId, Rc<Pipeline>>,
+ font_cache_task: FontCacheTask,
+ navigation_context: NavigationContext,
+ next_pipeline_id: PipelineId,
+ pending_frames: Vec<FrameChange>,
+ pending_sizes: HashMap<(PipelineId, SubpageId), TypedRect<PagePx, f32>>,
+ pub time_profiler_chan: TimeProfilerChan,
+ pub window_size: WindowSizeData,
+ pub opts: Opts,
+}
+
+/// Stores the Id of the outermost frame's pipeline, along with a vector of children frames
+struct FrameTree {
+ pub pipeline: Rc<Pipeline>,
+ pub parent: RefCell<Option<Rc<Pipeline>>>,
+ pub children: RefCell<Vec<ChildFrameTree>>,
+}
+
+#[deriving(Clone)]
+struct ChildFrameTree {
+ frame_tree: Rc<FrameTree>,
+ /// Clipping rect representing the size and position, in page coordinates, of the visible
+ /// region of the child frame relative to the parent.
+ pub rect: Option<TypedRect<PagePx, f32>>,
+}
+
+pub struct SendableFrameTree {
+ pub pipeline: CompositionPipeline,
+ pub children: Vec<SendableChildFrameTree>,
+}
+
+pub struct SendableChildFrameTree {
+ pub frame_tree: SendableFrameTree,
+ pub rect: Option<TypedRect<PagePx, f32>>,
+}
+
+enum ReplaceResult {
+ ReplacedNode(Rc<FrameTree>),
+ OriginalNode(Rc<FrameTree>),
+}
+
+impl FrameTree {
+ fn to_sendable(&self) -> SendableFrameTree {
+ let sendable_frame_tree = SendableFrameTree {
+ pipeline: self.pipeline.to_sendable(),
+ children: self.children.borrow().iter().map(|frame_tree| frame_tree.to_sendable()).collect(),
+ };
+ sendable_frame_tree
+ }
+}
+
+trait FrameTreeTraversal {
+ fn contains(&self, id: PipelineId) -> bool;
+ fn find(&self, id: PipelineId) -> Option<Self>;
+ fn replace_child(&self, id: PipelineId, new_child: Self) -> ReplaceResult;
+ fn iter(&self) -> FrameTreeIterator;
+}
+
+impl FrameTreeTraversal for Rc<FrameTree> {
+ fn contains(&self, id: PipelineId) -> bool {
+ self.iter().any(|frame_tree| id == frame_tree.pipeline.id)
+ }
+
+ /// Returns the frame tree whose key is id
+ fn find(&self, id: PipelineId) -> Option<Rc<FrameTree>> {
+ self.iter().find(|frame_tree| id == frame_tree.pipeline.id)
+ }
+
+ /// Replaces a node of the frame tree in place. Returns the node that was removed or the original node
+ /// if the node to replace could not be found.
+ fn replace_child(&self, id: PipelineId, new_child: Rc<FrameTree>) -> ReplaceResult {
+ for frame_tree in self.iter() {
+ let mut children = frame_tree.children.borrow_mut();
+ let mut child = children.mut_iter()
+ .find(|child| child.frame_tree.pipeline.id == id);
+ for child in child.mut_iter() {
+ *new_child.parent.borrow_mut() = child.frame_tree.parent.borrow().clone();
+ return ReplacedNode(replace(&mut child.frame_tree, new_child));
+ }
+ }
+ OriginalNode(new_child)
+ }
+
+ fn iter(&self) -> FrameTreeIterator {
+ FrameTreeIterator {
+ stack: vec!(self.clone()),
+ }
+ }
+}
+
+impl ChildFrameTree {
+ fn to_sendable(&self) -> SendableChildFrameTree {
+ SendableChildFrameTree {
+ frame_tree: self.frame_tree.to_sendable(),
+ rect: self.rect,
+ }
+ }
+}
+
+/// An iterator over a frame tree, returning nodes in depth-first order.
+/// Note that this iterator should _not_ be used to mutate nodes _during_
+/// iteration. Mutating nodes once the iterator is out of scope is OK.
+struct FrameTreeIterator {
+ stack: Vec<Rc<FrameTree>>,
+}
+
+impl Iterator<Rc<FrameTree>> for FrameTreeIterator {
+ fn next(&mut self) -> Option<Rc<FrameTree>> {
+ if !self.stack.is_empty() {
+ let next = self.stack.pop();
+ for cft in next.get_ref().children.borrow().iter() {
+ self.stack.push(cft.frame_tree.clone());
+ }
+ Some(next.unwrap())
+ } else {
+ None
+ }
+ }
+}
+
+/// Represents the portion of a page that is changing in navigating.
+struct FrameChange {
+ pub before: Option<PipelineId>,
+ pub after: Rc<FrameTree>,
+ pub navigation_type: NavigationType,
+}
+
+/// Stores the Id's of the pipelines previous and next in the browser's history
+struct NavigationContext {
+ pub previous: Vec<Rc<FrameTree>>,
+ pub next: Vec<Rc<FrameTree>>,
+ pub current: Option<Rc<FrameTree>>,
+}
+
+impl NavigationContext {
+ fn new() -> NavigationContext {
+ NavigationContext {
+ previous: vec!(),
+ next: vec!(),
+ current: None,
+ }
+ }
+
+ /* Note that the following two methods can fail. They should only be called *
+ * when it is known that there exists either a previous page or a next page. */
+
+ fn back(&mut self) -> Rc<FrameTree> {
+ self.next.push(self.current.take_unwrap());
+ let prev = self.previous.pop().unwrap();
+ self.current = Some(prev.clone());
+ prev
+ }
+
+ fn forward(&mut self) -> Rc<FrameTree> {
+ self.previous.push(self.current.take_unwrap());
+ let next = self.next.pop().unwrap();
+ self.current = Some(next.clone());
+ next
+ }
+
+ /// Loads a new set of page frames, returning all evicted frame trees
+ fn load(&mut self, frame_tree: Rc<FrameTree>) -> Vec<Rc<FrameTree>> {
+ debug!("navigating to {:?}", frame_tree.pipeline.id);
+ let evicted = replace(&mut self.next, vec!());
+ if self.current.is_some() {
+ self.previous.push(self.current.take_unwrap());
+ }
+ self.current = Some(frame_tree.clone());
+ evicted
+ }
+
+ /// Returns the frame trees whose keys are pipeline_id.
+ fn find_all(&mut self, pipeline_id: PipelineId) -> Vec<Rc<FrameTree>> {
+ let from_current = self.current.iter().filter_map(|frame_tree| {
+ frame_tree.find(pipeline_id)
+ });
+ let from_next = self.next.iter().filter_map(|frame_tree| {
+ frame_tree.find(pipeline_id)
+ });
+ let from_prev = self.previous.iter().filter_map(|frame_tree| {
+ frame_tree.find(pipeline_id)
+ });
+ from_prev.chain(from_current).chain(from_next).collect()
+ }
+
+ fn contains(&mut self, pipeline_id: PipelineId) -> bool {
+ let from_current = self.current.iter();
+ let from_next = self.next.iter();
+ let from_prev = self.previous.iter();
+
+ let mut all_contained = from_prev.chain(from_current).chain(from_next);
+ all_contained.any(|frame_tree| {
+ frame_tree.contains(pipeline_id)
+ })
+ }
+}
+
+impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
+ pub fn start(compositor_chan: CompositorChan,
+ opts: &Opts,
+ resource_task: ResourceTask,
+ image_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ time_profiler_chan: TimeProfilerChan)
+ -> ConstellationChan {
+ let (constellation_port, constellation_chan) = ConstellationChan::new();
+ let constellation_chan_clone = constellation_chan.clone();
+ let opts_clone = opts.clone();
+ spawn_named("Constellation", proc() {
+ let mut constellation : Constellation<LTF, STF> = Constellation {
+ chan: constellation_chan_clone,
+ request_port: constellation_port,
+ compositor_chan: compositor_chan,
+ resource_task: resource_task,
+ image_cache_task: image_cache_task,
+ font_cache_task: font_cache_task,
+ pipelines: HashMap::new(),
+ navigation_context: NavigationContext::new(),
+ next_pipeline_id: PipelineId(0),
+ pending_frames: vec!(),
+ pending_sizes: HashMap::new(),
+ time_profiler_chan: time_profiler_chan,
+ window_size: WindowSizeData {
+ visible_viewport: TypedSize2D(800_f32, 600_f32),
+ initial_viewport: TypedSize2D(800_f32, 600_f32),
+ device_pixel_ratio: ScaleFactor(1.0),
+ },
+ opts: opts_clone,
+ };
+ constellation.run();
+ });
+ constellation_chan
+ }
+
+ fn run(&mut self) {
+ loop {
+ let request = self.request_port.recv();
+ if !self.handle_request(request) {
+ break;
+ }
+ }
+ }
+
+ /// Helper function for creating a pipeline
+ fn new_pipeline(&self,
+ id: PipelineId,
+ subpage_id: Option<SubpageId>,
+ script_pipeline: Option<Rc<Pipeline>>,
+ url: Url)
+ -> Rc<Pipeline> {
+ let pipe = Pipeline::create::<LTF, STF>(id,
+ subpage_id,
+ self.chan.clone(),
+ self.compositor_chan.clone(),
+ self.image_cache_task.clone(),
+ self.font_cache_task.clone(),
+ self.resource_task.clone(),
+ self.time_profiler_chan.clone(),
+ self.window_size,
+ self.opts.clone(),
+ script_pipeline,
+ url);
+ pipe.load();
+ Rc::new(pipe)
+ }
+
+
+ /// Helper function for getting a unique pipeline Id
+ fn get_next_pipeline_id(&mut self) -> PipelineId {
+ let id = self.next_pipeline_id;
+ let PipelineId(ref mut i) = self.next_pipeline_id;
+ *i += 1;
+ id
+ }
+
+ /// Convenience function for getting the currently active frame tree.
+ /// The currently active frame tree should always be the current painter
+ fn current_frame<'a>(&'a self) -> &'a Option<Rc<FrameTree>> {
+ &self.navigation_context.current
+ }
+
+ /// Returns both the navigation context and pending frame trees whose keys are pipeline_id.
+ fn find_all(&mut self, pipeline_id: PipelineId) -> Vec<Rc<FrameTree>> {
+ let matching_navi_frames = self.navigation_context.find_all(pipeline_id);
+ let matching_pending_frames = self.pending_frames.iter().filter_map(|frame_change| {
+ frame_change.after.find(pipeline_id)
+ });
+ matching_navi_frames.move_iter().chain(matching_pending_frames).collect()
+ }
+
+ /// Handles loading pages, navigation, and granting access to the compositor
+ fn handle_request(&mut self, request: Msg) -> bool {
+ match request {
+ ExitMsg => {
+ debug!("constellation exiting");
+ self.handle_exit();
+ return false;
+ }
+ FailureMsg(Failure { pipeline_id, subpage_id }) => {
+ self.handle_failure_msg(pipeline_id, subpage_id);
+ }
+ // This should only be called once per constellation, and only by the browser
+ InitLoadUrlMsg(url) => {
+ debug!("constellation got init load URL message");
+ self.handle_init_load(url);
+ }
+ // A layout assigned a size and position to a subframe. This needs to be reflected by
+ // all frame trees in the navigation context containing the subframe.
+ FrameRectMsg(pipeline_id, subpage_id, rect) => {
+ debug!("constellation got frame rect message");
+ self.handle_frame_rect_msg(pipeline_id, subpage_id, Rect::from_untyped(&rect));
+ }
+ LoadIframeUrlMsg(url, source_pipeline_id, subpage_id, sandbox) => {
+ debug!("constellation got iframe URL load message");
+ self.handle_load_iframe_url_msg(url, source_pipeline_id, subpage_id, sandbox);
+ }
+ // Load a new page, usually -- but not always -- from a mouse click or typed url
+ // If there is already a pending page (self.pending_frames), it will not be overridden;
+ // However, if the id is not encompassed by another change, it will be.
+ LoadUrlMsg(source_id, url) => {
+ debug!("constellation got URL load message");
+ self.handle_load_url_msg(source_id, url);
+ }
+ // A page loaded through one of several methods above has completed all parsing,
+ // script, and reflow messages have been sent.
+ LoadCompleteMsg(pipeline_id, url) => {
+ debug!("constellation got load complete message");
+ self.compositor_chan.send(LoadComplete(pipeline_id, url));
+ }
+ // Handle a forward or back request
+ NavigateMsg(direction) => {
+ debug!("constellation got navigation message");
+ self.handle_navigate_msg(direction);
+ }
+ // Notification that rendering has finished and is requesting permission to paint.
+ RendererReadyMsg(pipeline_id) => {
+ debug!("constellation got renderer ready message");
+ self.handle_renderer_ready_msg(pipeline_id);
+ }
+ ResizedWindowMsg(new_size) => {
+ debug!("constellation got window resize message");
+ self.handle_resized_window_msg(new_size);
+ }
+ }
+ true
+ }
+
+ fn handle_exit(&self) {
+ for (_id, ref pipeline) in self.pipelines.iter() {
+ pipeline.exit();
+ }
+ self.image_cache_task.exit();
+ self.resource_task.send(resource_task::Exit);
+ self.font_cache_task.exit();
+ self.compositor_chan.send(ShutdownComplete);
+ }
+
+ fn handle_failure_msg(&mut self, pipeline_id: PipelineId, subpage_id: Option<SubpageId>) {
+ debug!("handling failure message from pipeline {:?}, {:?}", pipeline_id, subpage_id);
+
+ if self.opts.hard_fail {
+ // It's quite difficult to make Servo exit cleanly if some tasks have failed.
+ // Hard fail exists for test runners so we crash and that's good enough.
+ let mut stderr = io::stderr();
+ stderr.write_str("Pipeline failed in hard-fail mode. Crashing!\n").unwrap();
+ stderr.flush().unwrap();
+ unsafe { libc::exit(1); }
+ }
+
+ let old_pipeline = match self.pipelines.find(&pipeline_id) {
+ None => {
+ debug!("no existing pipeline found; bailing out of failure recovery.");
+ return; // already failed?
+ }
+ Some(pipeline) => pipeline.clone()
+ };
+
+ fn force_pipeline_exit(old_pipeline: &Rc<Pipeline>) {
+ let ScriptControlChan(ref old_script) = old_pipeline.script_chan;
+ let _ = old_script.send_opt(ExitPipelineMsg(old_pipeline.id));
+ let _ = old_pipeline.render_chan.send_opt(render_task::ExitMsg(None));
+ let LayoutControlChan(ref old_layout) = old_pipeline.layout_chan;
+ let _ = old_layout.send_opt(ExitNowMsg);
+ }
+ force_pipeline_exit(&old_pipeline);
+ self.pipelines.remove(&pipeline_id);
+
+ loop {
+ let idx = self.pending_frames.iter().position(|pending| {
+ pending.after.pipeline.id == pipeline_id
+ });
+ idx.map(|idx| {
+ debug!("removing pending frame change for failed pipeline");
+ force_pipeline_exit(&self.pending_frames[idx].after.pipeline);
+ self.pending_frames.remove(idx)
+ });
+ if idx.is_none() {
+ break;
+ }
+ }
+ debug!("creating replacement pipeline for about:failure");
+
+ let new_id = self.get_next_pipeline_id();
+ let pipeline = self.new_pipeline(new_id, subpage_id, None,
+ Url::parse("about:failure").unwrap());
+
+ self.pending_frames.push(FrameChange{
+ before: Some(pipeline_id),
+ after: Rc::new(FrameTree {
+ pipeline: pipeline.clone(),
+ parent: RefCell::new(None),
+ children: RefCell::new(vec!()),
+ }),
+ navigation_type: constellation_msg::Load,
+ });
+
+ self.pipelines.insert(new_id, pipeline);
+ }
+
+ fn handle_init_load(&mut self, url: Url) {
+ let next_pipeline_id = self.get_next_pipeline_id();
+ let pipeline = self.new_pipeline(next_pipeline_id, None, None, url);
+
+ self.pending_frames.push(FrameChange {
+ before: None,
+ after: Rc::new(FrameTree {
+ pipeline: pipeline.clone(),
+ parent: RefCell::new(None),
+ children: RefCell::new(vec!()),
+ }),
+ navigation_type: constellation_msg::Load,
+ });
+ self.pipelines.insert(pipeline.id, pipeline);
+ }
+
+ fn handle_frame_rect_msg(&mut self, pipeline_id: PipelineId, subpage_id: SubpageId,
+ rect: TypedRect<PagePx, f32>) {
+ debug!("Received frame rect {:?} from {:?}, {:?}", rect, pipeline_id, subpage_id);
+ let mut already_sent = HashSet::new();
+
+ // Returns true if a child frame tree's subpage id matches the given subpage id
+ let subpage_eq = |child_frame_tree: & &mut ChildFrameTree| {
+ child_frame_tree.frame_tree.pipeline.
+ subpage_id.expect("Constellation:
+ child frame does not have a subpage id. This should not be possible.")
+ == subpage_id
+ };
+
+ let frames = self.find_all(pipeline_id);
+
+ {
+ // Update a child's frame rect and inform its script task of the change,
+ // if it hasn't been already. Optionally inform the compositor if
+ // resize happens immediately.
+ let update_child_rect = |child_frame_tree: &mut ChildFrameTree, is_active: bool| {
+ child_frame_tree.rect = Some(rect);
+ // NOTE: work around borrowchk issues
+ let pipeline = &child_frame_tree.frame_tree.pipeline;
+ if !already_sent.contains(&pipeline.id) {
+ if is_active {
+ let ScriptControlChan(ref script_chan) = pipeline.script_chan;
+ script_chan.send(ResizeMsg(pipeline.id, WindowSizeData {
+ visible_viewport: rect.size,
+ initial_viewport: rect.size * ScaleFactor(1.0),
+ device_pixel_ratio: self.window_size.device_pixel_ratio,
+ }));
+ self.compositor_chan.send(SetLayerClipRect(pipeline.id,
+ LayerId::null(),
+ rect.to_untyped()));
+ } else {
+ already_sent.insert(pipeline.id);
+ }
+ };
+ };
+
+ // If the subframe is in the current frame tree, the compositor needs the new size
+ for current_frame in self.current_frame().iter() {
+ debug!("Constellation: Sending size for frame in current frame tree.");
+ let source_frame = current_frame.find(pipeline_id);
+ for source_frame in source_frame.iter() {
+ let mut children = source_frame.children.borrow_mut();
+ let found_child = children.mut_iter().find(|child| subpage_eq(child));
+ found_child.map(|child| update_child_rect(child, true));
+ }
+ }
+
+ // Update all frames with matching pipeline- and subpage-ids
+ for frame_tree in frames.iter() {
+ let mut children = frame_tree.children.borrow_mut();
+ let found_child = children.mut_iter().find(|child| subpage_eq(child));
+ found_child.map(|child| update_child_rect(child, false));
+ }
+ }
+
+ // At this point, if no pipelines were sent a resize msg, then this subpage id
+ // should be added to pending sizes
+ if already_sent.len() == 0 {
+ self.pending_sizes.insert((pipeline_id, subpage_id), rect);
+ }
+ }
+
+ fn handle_load_iframe_url_msg(&mut self,
+ url: Url,
+ source_pipeline_id: PipelineId,
+ subpage_id: SubpageId,
+ sandbox: IFrameSandboxState) {
+ // A message from the script associated with pipeline_id that it has
+ // parsed an iframe during html parsing. This iframe will result in a
+ // new pipeline being spawned and a frame tree being added to pipeline_id's
+ // frame tree's children. This message is never the result of a link clicked
+ // or a new url entered.
+ // Start by finding the frame trees matching the pipeline id,
+ // and add the new pipeline to their sub frames.
+ let frame_trees = self.find_all(source_pipeline_id);
+ if frame_trees.is_empty() {
+ fail!("Constellation: source pipeline id of LoadIframeUrlMsg is not in
+ navigation context, nor is it in a pending frame. This should be
+ impossible.");
+ }
+
+ let next_pipeline_id = self.get_next_pipeline_id();
+
+ // Compare the pipeline's url to the new url. If the origin is the same,
+ // then reuse the script task in creating the new pipeline
+ let source_pipeline = self.pipelines.find(&source_pipeline_id).expect("Constellation:
+ source Id of LoadIframeUrlMsg does have an associated pipeline in
+ constellation. This should be impossible.").clone();
+
+ let source_url = source_pipeline.url.clone();
+
+ let same_script = (source_url.host() == url.host() &&
+ source_url.port() == url.port()) && sandbox == IFrameUnsandboxed;
+ // FIXME(tkuehn): Need to follow the standardized spec for checking same-origin
+ // Reuse the script task if the URL is same-origin
+ let new_pipeline = if same_script {
+ debug!("Constellation: loading same-origin iframe at {:?}", url);
+ Some(source_pipeline.clone())
+ } else {
+ debug!("Constellation: loading cross-origin iframe at {:?}", url);
+ None
+ };
+
+ let pipeline = self.new_pipeline(
+ next_pipeline_id,
+ Some(subpage_id),
+ new_pipeline,
+ url
+ );
+
+ let rect = self.pending_sizes.pop(&(source_pipeline_id, subpage_id));
+ for frame_tree in frame_trees.iter() {
+ frame_tree.children.borrow_mut().push(ChildFrameTree {
+ frame_tree: Rc::new(FrameTree {
+ pipeline: pipeline.clone(),
+ parent: RefCell::new(Some(source_pipeline.clone())),
+ children: RefCell::new(vec!()),
+ }),
+ rect: rect,
+ });
+ }
+ self.pipelines.insert(pipeline.id, pipeline);
+ }
+
+ fn handle_load_url_msg(&mut self, source_id: PipelineId, url: Url) {
+ debug!("Constellation: received message to load {:s}", url.to_string());
+ // Make sure no pending page would be overridden.
+ let source_frame = self.current_frame().get_ref().find(source_id).expect(
+ "Constellation: received a LoadUrlMsg from a pipeline_id associated
+ with a pipeline not in the active frame tree. This should be
+ impossible.");
+
+ for frame_change in self.pending_frames.iter() {
+ let old_id = frame_change.before.expect("Constellation: Received load msg
+ from pipeline, but there is no currently active page. This should
+ be impossible.");
+ let changing_frame = self.current_frame().get_ref().find(old_id).expect("Constellation:
+ Pending change has non-active source pipeline. This should be
+ impossible.");
+ if changing_frame.contains(source_id) || source_frame.contains(old_id) {
+ // id that sent load msg is being changed already; abort
+ return;
+ }
+ }
+ // Being here means either there are no pending frames, or none of the pending
+ // changes would be overriden by changing the subframe associated with source_id.
+
+ let parent = source_frame.parent.clone();
+ let subpage_id = source_frame.pipeline.subpage_id;
+ let next_pipeline_id = self.get_next_pipeline_id();
+
+ let pipeline = self.new_pipeline(next_pipeline_id, subpage_id, None, url);
+
+ self.pending_frames.push(FrameChange{
+ before: Some(source_id),
+ after: Rc::new(FrameTree {
+ pipeline: pipeline.clone(),
+ parent: parent,
+ children: RefCell::new(vec!()),
+ }),
+ navigation_type: constellation_msg::Load,
+ });
+ self.pipelines.insert(pipeline.id, pipeline);
+ }
+
+ fn handle_navigate_msg(&mut self, direction: constellation_msg::NavigationDirection) {
+ debug!("received message to navigate {:?}", direction);
+
+ // TODO(tkuehn): what is the "critical point" beyond which pending frames
+ // should not be cleared? Currently, the behavior is that forward/back
+ // navigation always has navigation priority, and after that new page loading is
+ // first come, first served.
+ let destination_frame = match direction {
+ constellation_msg::Forward => {
+ if self.navigation_context.next.is_empty() {
+ debug!("no next page to navigate to");
+ return;
+ } else {
+ let old = self.current_frame().get_ref();
+ for frame in old.iter() {
+ frame.pipeline.revoke_paint_permission();
+ }
+ }
+ self.navigation_context.forward()
+ }
+ constellation_msg::Back => {
+ if self.navigation_context.previous.is_empty() {
+ debug!("no previous page to navigate to");
+ return;
+ } else {
+ let old = self.current_frame().get_ref();
+ for frame in old.iter() {
+ frame.pipeline.revoke_paint_permission();
+ }
+ }
+ self.navigation_context.back()
+ }
+ };
+
+ for frame in destination_frame.iter() {
+ frame.pipeline.load();
+ }
+ self.grant_paint_permission(destination_frame, constellation_msg::Navigate);
+
+ }
+
+ fn handle_renderer_ready_msg(&mut self, pipeline_id: PipelineId) {
+ debug!("Renderer {:?} ready to send paint msg", pipeline_id);
+ // This message could originate from a pipeline in the navigation context or
+ // from a pending frame. The only time that we will grant paint permission is
+ // when the message originates from a pending frame or the current frame.
+
+ for current_frame in self.current_frame().iter() {
+ // Messages originating in the current frame are not navigations;
+ // they may come from a page load in a subframe.
+ if current_frame.contains(pipeline_id) {
+ for frame in current_frame.iter() {
+ frame.pipeline.grant_paint_permission();
+ }
+ return;
+ }
+ }
+
+ // Find the pending frame change whose new pipeline id is pipeline_id.
+ // If it is not found, it simply means that this pipeline will not receive
+ // permission to paint.
+ let pending_index = self.pending_frames.iter().rposition(|frame_change| {
+ frame_change.after.pipeline.id == pipeline_id
+ });
+ for &pending_index in pending_index.iter() {
+ let frame_change = self.pending_frames.swap_remove(pending_index).unwrap();
+ let to_add = frame_change.after.clone();
+
+ // Create the next frame tree that will be given to the compositor
+ let next_frame_tree = if to_add.parent.borrow().is_some() {
+ // NOTE: work around borrowchk issues
+ self.current_frame().get_ref().clone()
+ } else {
+ to_add.clone()
+ };
+
+ // If there are frames to revoke permission from, do so now.
+ match frame_change.before {
+ Some(revoke_id) if self.current_frame().is_some() => {
+ debug!("Constellation: revoking permission from {:?}", revoke_id);
+ let current_frame = self.current_frame().get_ref();
+
+ let to_revoke = current_frame.find(revoke_id).expect(
+ "Constellation: pending frame change refers to an old \
+ frame not contained in the current frame. This is a bug");
+
+ for frame in to_revoke.iter() {
+ frame.pipeline.revoke_paint_permission();
+ }
+
+ // If to_add is not the root frame, then replace revoked_frame with it.
+ // This conveniently keeps scissor rect size intact.
+ // NOTE: work around borrowchk issue
+ let mut flag = false;
+ {
+ if to_add.parent.borrow().is_some() {
+ debug!("Constellation: replacing {:?} with {:?} in {:?}",
+ revoke_id, to_add.pipeline.id,
+ next_frame_tree.pipeline.id);
+ flag = true;
+ }
+ }
+ if flag {
+ next_frame_tree.replace_child(revoke_id, to_add);
+ }
+ }
+
+ _ => {
+ // Add to_add to parent's children, if it is not the root
+ let parent = &to_add.parent;
+ for parent in parent.borrow().iter() {
+ let subpage_id = to_add.pipeline.subpage_id
+ .expect("Constellation:
+ Child frame's subpage id is None. This should be impossible.");
+ let rect = self.pending_sizes.pop(&(parent.id, subpage_id));
+ let parent = next_frame_tree.find(parent.id).expect(
+ "Constellation: pending frame has a parent frame that is not
+ active. This is a bug.");
+ parent.children.borrow_mut().push(ChildFrameTree {
+ frame_tree: to_add.clone(),
+ rect: rect,
+ });
+ }
+ }
+ }
+
+ self.grant_paint_permission(next_frame_tree, frame_change.navigation_type);
+ }
+ }
+
+ /// Called when the window is resized.
+ fn handle_resized_window_msg(&mut self, new_size: WindowSizeData) {
+ let mut already_seen = HashSet::new();
+ for frame_tree in self.current_frame().iter() {
+ debug!("constellation sending resize message to active frame");
+ let pipeline = &frame_tree.pipeline;
+ let ScriptControlChan(ref chan) = pipeline.script_chan;
+ let _ = chan.send_opt(ResizeMsg(pipeline.id, new_size));
+ already_seen.insert(pipeline.id);
+ }
+ for frame_tree in self.navigation_context.previous.iter()
+ .chain(self.navigation_context.next.iter()) {
+ let pipeline = &frame_tree.pipeline;
+ if !already_seen.contains(&pipeline.id) {
+ debug!("constellation sending resize message to inactive frame");
+ let ScriptControlChan(ref chan) = pipeline.script_chan;
+ let _ = chan.send_opt(ResizeInactiveMsg(pipeline.id, new_size));
+ already_seen.insert(pipeline.id);
+ }
+ }
+
+ // If there are any pending outermost frames, then tell them to resize. (This is how the
+ // initial window size gets sent to the first page loaded, giving it permission to reflow.)
+ for change in self.pending_frames.iter() {
+ let frame_tree = &change.after;
+ if frame_tree.parent.borrow().is_none() {
+ debug!("constellation sending resize message to pending outer frame ({:?})",
+ frame_tree.pipeline.id);
+ let ScriptControlChan(ref chan) = frame_tree.pipeline.script_chan;
+ let _ = chan.send_opt(ResizeMsg(frame_tree.pipeline.id, new_size));
+ }
+ }
+
+ self.window_size = new_size;
+ }
+
+ // Close all pipelines at and beneath a given frame
+ fn close_pipelines(&mut self, frame_tree: Rc<FrameTree>) {
+ // TODO(tkuehn): should only exit once per unique script task,
+ // and then that script task will handle sub-exits
+ for frame_tree in frame_tree.iter() {
+ frame_tree.pipeline.exit();
+ self.pipelines.remove(&frame_tree.pipeline.id);
+ }
+ }
+
+ fn handle_evicted_frames(&mut self, evicted: Vec<Rc<FrameTree>>) {
+ for frame_tree in evicted.iter() {
+ if !self.navigation_context.contains(frame_tree.pipeline.id) {
+ self.close_pipelines(frame_tree.clone());
+ } else {
+ let frames = frame_tree.children.borrow().iter()
+ .map(|child| child.frame_tree.clone()).collect();
+ self.handle_evicted_frames(frames);
+ }
+ }
+ }
+
+ // Grants a frame tree permission to paint; optionally updates navigation to reflect a new page
+ fn grant_paint_permission(&mut self, frame_tree: Rc<FrameTree>, navigation_type: NavigationType) {
+ // Give permission to paint to the new frame and all child frames
+ self.set_ids(&frame_tree);
+
+ // Don't call navigation_context.load() on a Navigate type (or None, as in the case of
+ // parsed iframes that finish loading)
+ match navigation_type {
+ constellation_msg::Load => {
+ debug!("evicting old frames due to load");
+ let evicted = self.navigation_context.load(frame_tree);
+ self.handle_evicted_frames(evicted);
+ }
+ _ => {
+ debug!("ignoring non-load navigation type");
+ }
+ }
+ }
+
+ fn set_ids(&self, frame_tree: &Rc<FrameTree>) {
+ let (chan, port) = channel();
+ debug!("Constellation sending SetIds");
+ self.compositor_chan.send(SetIds(frame_tree.to_sendable(), chan, self.chan.clone()));
+ match port.recv_opt() {
+ Ok(()) => {
+ let mut iter = frame_tree.iter();
+ for frame in iter {
+ frame.pipeline.grant_paint_permission();
+ }
+ }
+ Err(()) => {} // message has been discarded, probably shutting down
+ }
+ }
+}
+
diff --git a/components/compositing/events.rs b/components/compositing/events.rs
new file mode 100644
index 00000000000..25779a417af
--- /dev/null
+++ b/components/compositing/events.rs
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use compositor_data::{CompositorData, WantsScrollEvents};
+use windowing::{MouseWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent};
+use windowing::MouseWindowMouseUpEvent;
+
+use geom::length::Length;
+use geom::point::TypedPoint2D;
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use layers::geometry::DevicePixel;
+use layers::layers::Layer;
+use script_traits::{ClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, SendEventMsg};
+use script_traits::{ScriptControlChan};
+use servo_msg::compositor_msg::{FixedPosition, LayerId};
+use servo_msg::constellation_msg::PipelineId;
+use servo_util::geometry::PagePx;
+use std::rc::Rc;
+
+
+use geom::matrix::identity;
+
+trait Clampable {
+ fn clamp(&self, mn: &Self, mx: &Self) -> Self;
+}
+
+impl Clampable for f32 {
+ /// Returns the number constrained within the range `mn <= self <= mx`.
+ /// If any of the numbers are `NAN` then `NAN` is returned.
+ #[inline]
+ fn clamp(&self, mn: &f32, mx: &f32) -> f32 {
+ match () {
+ _ if self.is_nan() => *self,
+ _ if !(*self <= *mx) => *mx,
+ _ if !(*self >= *mn) => *mn,
+ _ => *self,
+ }
+ }
+}
+
+/// Move the layer's descendants that don't want scroll events and scroll by a relative
+/// specified amount in page coordinates. This also takes in a cursor position to see if the
+/// mouse is over child layers first. If a layer successfully scrolled, returns true; otherwise
+/// returns false, so a parent layer can scroll instead.
+pub fn handle_scroll_event(layer: Rc<Layer<CompositorData>>,
+ delta: TypedPoint2D<DevicePixel, f32>,
+ cursor: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ // If this layer doesn't want scroll events, neither it nor its children can handle scroll
+ // events.
+ if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
+ return false
+ }
+
+ // Allow children to scroll.
+ let content_offset = layer.content_offset.borrow().clone();
+ let cursor = cursor - content_offset;
+ for child in layer.children().iter() {
+ let child_bounds = child.bounds.borrow();
+ if child_bounds.contains(&cursor) &&
+ handle_scroll_event(child.clone(),
+ delta,
+ cursor - child_bounds.origin,
+ child_bounds.size) {
+ return true
+ }
+ }
+
+ clamp_scroll_offset_and_scroll_layer(layer, content_offset + delta, window_size)
+
+}
+
+pub fn clamp_scroll_offset_and_scroll_layer(layer: Rc<Layer<CompositorData>>,
+ mut new_offset: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ let layer_size = layer.bounds.borrow().size;
+ let min_x = (window_size.width - layer_size.width).get().min(0.0);
+ new_offset.x = Length(new_offset.x.get().clamp(&min_x, &0.0));
+
+ let min_y = (window_size.height - layer_size.height).get().min(0.0);
+ new_offset.y = Length(new_offset.y.get().clamp(&min_y, &0.0));
+
+ if *layer.content_offset.borrow() == new_offset {
+ return false
+ }
+
+ // FIXME: This allows the base layer to record the current content offset without
+ // updating its transform. This should be replaced with something less strange.
+ *layer.content_offset.borrow_mut() = new_offset;
+ scroll_layer_and_all_child_layers(layer.clone(), new_offset)
+}
+
+fn scroll_layer_and_all_child_layers(layer: Rc<Layer<CompositorData>>,
+ new_offset: TypedPoint2D<DevicePixel, f32>)
+ -> bool {
+ let mut result = false;
+
+ // Only scroll this layer if it's not fixed-positioned.
+ if layer.extra_data.borrow().scroll_policy != FixedPosition {
+ *layer.transform.borrow_mut() = identity().translate(new_offset.x.get(),
+ new_offset.y.get(),
+ 0.0);
+ *layer.content_offset.borrow_mut() = new_offset;
+ result = true
+ }
+
+ for child in layer.children().iter() {
+ result |= scroll_layer_and_all_child_layers(child.clone(), new_offset);
+ }
+
+ return result;
+}
+
+// Takes in a MouseWindowEvent, determines if it should be passed to children, and
+// sends the event off to the appropriate pipeline. NB: the cursor position is in
+// page coordinates.
+pub fn send_mouse_event(layer: Rc<Layer<CompositorData>>,
+ event: MouseWindowEvent,
+ cursor: TypedPoint2D<DevicePixel, f32>,
+ device_pixels_per_page_px: ScaleFactor<PagePx, DevicePixel, f32>) {
+ let cursor = cursor - *layer.content_offset.borrow();
+ for child in layer.children().iter() {
+ let child_bounds = child.bounds.borrow();
+ if child_bounds.contains(&cursor) {
+ send_mouse_event(child.clone(),
+ event,
+ cursor - child_bounds.origin,
+ device_pixels_per_page_px);
+ return;
+ }
+ }
+
+ // This mouse event is mine!
+ let cursor = cursor / device_pixels_per_page_px;
+ let message = match event {
+ MouseWindowClickEvent(button, _) => ClickEvent(button, cursor.to_untyped()),
+ MouseWindowMouseDownEvent(button, _) => MouseDownEvent(button, cursor.to_untyped()),
+ MouseWindowMouseUpEvent(button, _) => MouseUpEvent(button, cursor.to_untyped()),
+ };
+ let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
+ let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
+}
+
+pub fn send_mouse_move_event(layer: Rc<Layer<CompositorData>>,
+ cursor: TypedPoint2D<PagePx, f32>) {
+ let message = MouseMoveEvent(cursor.to_untyped());
+ let ScriptControlChan(ref chan) = layer.extra_data.borrow().pipeline.script_chan;
+ let _ = chan.send_opt(SendEventMsg(layer.extra_data.borrow().pipeline.id.clone(), message));
+}
+
+pub fn move(layer: Rc<Layer<CompositorData>>,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ origin: TypedPoint2D<DevicePixel, f32>,
+ window_size: TypedSize2D<DevicePixel, f32>)
+ -> bool {
+ // Search children for the right layer to move.
+ if layer.extra_data.borrow().pipeline.id != pipeline_id ||
+ layer.extra_data.borrow().id != layer_id {
+ return layer.children().iter().any(|kid| {
+ move(kid.clone(),
+ pipeline_id,
+ layer_id,
+ origin,
+ window_size)
+ });
+ }
+
+ if layer.extra_data.borrow().wants_scroll_events != WantsScrollEvents {
+ return false
+ }
+
+ clamp_scroll_offset_and_scroll_layer(layer, TypedPoint2D(0f32, 0f32) - origin, window_size)
+}
diff --git a/components/compositing/headless.rs b/components/compositing/headless.rs
new file mode 100644
index 00000000000..d8d56e64d00
--- /dev/null
+++ b/components/compositing/headless.rs
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use compositor_task::{Msg, Exit, ChangeReadyState, SetIds};
+use compositor_task::{GetGraphicsMetadata, CreateOrUpdateRootLayer, CreateOrUpdateDescendantLayer};
+use compositor_task::{SetLayerClipRect, Paint, ScrollFragmentPoint, LoadComplete};
+use compositor_task::{ShutdownComplete, ChangeRenderState, RenderMsgDiscarded};
+
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use servo_msg::constellation_msg::{ConstellationChan, ExitMsg, ResizedWindowMsg, WindowSizeData};
+use servo_util::memory::MemoryProfilerChan;
+use servo_util::memory;
+use servo_util::time::TimeProfilerChan;
+use servo_util::time;
+
+/// Starts the compositor, which listens for messages on the specified port.
+///
+/// This is the null compositor which doesn't draw anything to the screen.
+/// It's intended for headless testing.
+pub struct NullCompositor {
+ /// The port on which we receive messages.
+ pub port: Receiver<Msg>,
+}
+
+impl NullCompositor {
+ fn new(port: Receiver<Msg>) -> NullCompositor {
+ NullCompositor {
+ port: port,
+ }
+ }
+
+ pub fn create(port: Receiver<Msg>,
+ constellation_chan: ConstellationChan,
+ time_profiler_chan: TimeProfilerChan,
+ memory_profiler_chan: MemoryProfilerChan) {
+ let compositor = NullCompositor::new(port);
+
+ // Tell the constellation about the initial fake size.
+ {
+ let ConstellationChan(ref chan) = constellation_chan;
+ chan.send(ResizedWindowMsg(WindowSizeData {
+ initial_viewport: TypedSize2D(640_f32, 480_f32),
+ visible_viewport: TypedSize2D(640_f32, 480_f32),
+ device_pixel_ratio: ScaleFactor(1.0),
+ }));
+ }
+ compositor.handle_message(constellation_chan);
+
+ // Drain compositor port, sometimes messages contain channels that are blocking
+ // another task from finishing (i.e. SetIds)
+ loop {
+ match compositor.port.try_recv() {
+ Err(_) => break,
+ Ok(_) => {},
+ }
+ }
+
+ time_profiler_chan.send(time::ExitMsg);
+ memory_profiler_chan.send(memory::ExitMsg);
+ }
+
+ fn handle_message(&self, constellation_chan: ConstellationChan) {
+ loop {
+ match self.port.recv() {
+ Exit(chan) => {
+ debug!("shutting down the constellation");
+ let ConstellationChan(ref con_chan) = constellation_chan;
+ con_chan.send(ExitMsg);
+ chan.send(());
+ }
+
+ ShutdownComplete => {
+ debug!("constellation completed shutdown");
+ break
+ }
+
+ GetGraphicsMetadata(chan) => {
+ chan.send(None);
+ }
+
+ SetIds(_, response_chan, _) => {
+ response_chan.send(());
+ }
+
+ // Explicitly list ignored messages so that when we add a new one,
+ // we'll notice and think about whether it needs a response, like
+ // SetIds.
+
+ CreateOrUpdateRootLayer(..) |
+ CreateOrUpdateDescendantLayer(..) |
+ SetLayerClipRect(..) | Paint(..) |
+ ChangeReadyState(..) | ChangeRenderState(..) | ScrollFragmentPoint(..) |
+ LoadComplete(..) | RenderMsgDiscarded(..) => ()
+ }
+ }
+ }
+}
diff --git a/components/compositing/lib.rs b/components/compositing/lib.rs
new file mode 100644
index 00000000000..a016afd11bd
--- /dev/null
+++ b/components/compositing/lib.rs
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+#![feature(globs, phase, macro_rules)]
+
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+
+extern crate alert;
+extern crate azure;
+extern crate geom;
+extern crate gfx;
+#[cfg(not(target_os="android"))]
+extern crate glfw;
+#[cfg(target_os="android")]
+extern crate glut;
+extern crate layers;
+extern crate layout_traits;
+extern crate opengles;
+extern crate png;
+extern crate script_traits;
+extern crate servo_msg = "msg";
+extern crate servo_net = "net";
+#[phase(plugin, link)]
+extern crate servo_util = "util";
+
+extern crate libc;
+extern crate time;
+extern crate url;
+
+#[cfg(target_os="macos")]
+extern crate core_graphics;
+#[cfg(target_os="macos")]
+extern crate core_text;
+
+pub use compositor_task::{CompositorChan, CompositorTask};
+pub use constellation::Constellation;
+
+pub mod compositor_task;
+
+mod compositor_data;
+mod events;
+
+mod compositor;
+mod headless;
+
+pub mod pipeline;
+pub mod constellation;
+
+mod windowing;
+
+#[path="platform/mod.rs"]
+pub mod platform;
diff --git a/components/compositing/pipeline.rs b/components/compositing/pipeline.rs
new file mode 100644
index 00000000000..8ccc0fe48a5
--- /dev/null
+++ b/components/compositing/pipeline.rs
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use CompositorChan;
+use layout_traits::{LayoutTaskFactory, LayoutControlChan};
+use script_traits::{ScriptControlChan, ScriptTaskFactory};
+use script_traits::{AttachLayoutMsg, LoadMsg, NewLayoutInfo, ExitPipelineMsg};
+
+use gfx::render_task::{PaintPermissionGranted, PaintPermissionRevoked};
+use gfx::render_task::{RenderChan, RenderTask};
+use servo_msg::constellation_msg::{ConstellationChan, Failure, PipelineId, SubpageId};
+use servo_msg::constellation_msg::WindowSizeData;
+use servo_net::image_cache_task::ImageCacheTask;
+use gfx::font_cache_task::FontCacheTask;
+use servo_net::resource_task::ResourceTask;
+use servo_util::opts::Opts;
+use servo_util::time::TimeProfilerChan;
+use std::rc::Rc;
+use url::Url;
+
+/// A uniquely-identifiable pipeline of script task, layout task, and render task.
+pub struct Pipeline {
+ pub id: PipelineId,
+ pub subpage_id: Option<SubpageId>,
+ pub script_chan: ScriptControlChan,
+ pub layout_chan: LayoutControlChan,
+ pub render_chan: RenderChan,
+ pub layout_shutdown_port: Receiver<()>,
+ pub render_shutdown_port: Receiver<()>,
+ /// The most recently loaded url
+ pub url: Url,
+}
+
+/// The subset of the pipeline that is needed for layer composition.
+#[deriving(Clone)]
+pub struct CompositionPipeline {
+ pub id: PipelineId,
+ pub script_chan: ScriptControlChan,
+ pub render_chan: RenderChan,
+}
+
+impl Pipeline {
+ /// Starts a render task, layout task, and possibly a script task.
+ /// Returns the channels wrapped in a struct.
+ /// If script_pipeline is not None, then subpage_id must also be not None.
+ pub fn create<LTF:LayoutTaskFactory, STF:ScriptTaskFactory>(
+ id: PipelineId,
+ subpage_id: Option<SubpageId>,
+ constellation_chan: ConstellationChan,
+ compositor_chan: CompositorChan,
+ image_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ resource_task: ResourceTask,
+ time_profiler_chan: TimeProfilerChan,
+ window_size: WindowSizeData,
+ opts: Opts,
+ script_pipeline: Option<Rc<Pipeline>>,
+ url: Url)
+ -> Pipeline {
+ let layout_pair = ScriptTaskFactory::create_layout_channel(None::<&mut STF>);
+ let (render_port, render_chan) = RenderChan::new();
+ let (render_shutdown_chan, render_shutdown_port) = channel();
+ let (layout_shutdown_chan, layout_shutdown_port) = channel();
+ let (pipeline_chan, pipeline_port) = channel();
+
+ let failure = Failure {
+ pipeline_id: id,
+ subpage_id: subpage_id,
+ };
+
+ let script_chan = match script_pipeline {
+ None => {
+ let (script_chan, script_port) = channel();
+ ScriptTaskFactory::create(None::<&mut STF>,
+ id,
+ box compositor_chan.clone(),
+ &layout_pair,
+ ScriptControlChan(script_chan.clone()),
+ script_port,
+ constellation_chan.clone(),
+ failure.clone(),
+ resource_task,
+ image_cache_task.clone(),
+ window_size);
+ ScriptControlChan(script_chan)
+ }
+ Some(spipe) => {
+ let new_layout_info = NewLayoutInfo {
+ old_pipeline_id: spipe.id.clone(),
+ new_pipeline_id: id,
+ subpage_id: subpage_id.expect("script_pipeline != None but subpage_id == None"),
+ layout_chan: ScriptTaskFactory::clone_layout_channel(None::<&mut STF>, &layout_pair),
+ };
+
+ let ScriptControlChan(ref chan) = spipe.script_chan;
+ chan.send(AttachLayoutMsg(new_layout_info));
+ spipe.script_chan.clone()
+ }
+ };
+
+ RenderTask::create(id,
+ render_port,
+ compositor_chan.clone(),
+ constellation_chan.clone(),
+ font_cache_task.clone(),
+ failure.clone(),
+ opts.clone(),
+ time_profiler_chan.clone(),
+ render_shutdown_chan);
+
+ LayoutTaskFactory::create(None::<&mut LTF>,
+ id,
+ layout_pair,
+ pipeline_port,
+ constellation_chan,
+ failure,
+ script_chan.clone(),
+ render_chan.clone(),
+ image_cache_task,
+ font_cache_task,
+ opts.clone(),
+ time_profiler_chan,
+ layout_shutdown_chan);
+
+ Pipeline::new(id,
+ subpage_id,
+ script_chan,
+ LayoutControlChan(pipeline_chan),
+ render_chan,
+ layout_shutdown_port,
+ render_shutdown_port,
+ url)
+ }
+
+ pub fn new(id: PipelineId,
+ subpage_id: Option<SubpageId>,
+ script_chan: ScriptControlChan,
+ layout_chan: LayoutControlChan,
+ render_chan: RenderChan,
+ layout_shutdown_port: Receiver<()>,
+ render_shutdown_port: Receiver<()>,
+ url: Url)
+ -> Pipeline {
+ Pipeline {
+ id: id,
+ subpage_id: subpage_id,
+ script_chan: script_chan,
+ layout_chan: layout_chan,
+ render_chan: render_chan,
+ layout_shutdown_port: layout_shutdown_port,
+ render_shutdown_port: render_shutdown_port,
+ url: url,
+ }
+ }
+
+ pub fn load(&self) {
+ let ScriptControlChan(ref chan) = self.script_chan;
+ chan.send(LoadMsg(self.id, self.url.clone()));
+ }
+
+ pub fn grant_paint_permission(&self) {
+ let _ = self.render_chan.send_opt(PaintPermissionGranted);
+ }
+
+ pub fn revoke_paint_permission(&self) {
+ debug!("pipeline revoking render channel paint permission");
+ let _ = self.render_chan.send_opt(PaintPermissionRevoked);
+ }
+
+ pub fn exit(&self) {
+ debug!("pipeline {:?} exiting", self.id);
+
+ // Script task handles shutting down layout, and layout handles shutting down the renderer.
+ // For now, if the script task has failed, we give up on clean shutdown.
+ let ScriptControlChan(ref chan) = self.script_chan;
+ if chan.send_opt(ExitPipelineMsg(self.id)).is_ok() {
+ // Wait until all slave tasks have terminated and run destructors
+ // NOTE: We don't wait for script task as we don't always own it
+ let _ = self.render_shutdown_port.recv_opt();
+ let _ = self.layout_shutdown_port.recv_opt();
+ }
+ }
+
+ pub fn to_sendable(&self) -> CompositionPipeline {
+ CompositionPipeline {
+ id: self.id.clone(),
+ script_chan: self.script_chan.clone(),
+ render_chan: self.render_chan.clone(),
+ }
+ }
+}
+
diff --git a/components/compositing/platform/common/glfw_windowing.rs b/components/compositing/platform/common/glfw_windowing.rs
new file mode 100644
index 00000000000..5a7215d3017
--- /dev/null
+++ b/components/compositing/platform/common/glfw_windowing.rs
@@ -0,0 +1,380 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A windowing implementation using GLFW.
+
+use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
+use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass, MouseWindowMoveEventClass};
+use windowing::{ScrollWindowEvent, ZoomWindowEvent, PinchZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
+use windowing::{QuitWindowEvent, MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
+use windowing::RefreshWindowEvent;
+use windowing::{Forward, Back};
+
+use alert::{Alert, AlertMethods};
+use libc::{exit, c_int};
+use time;
+use time::Timespec;
+use std::cell::{Cell, RefCell};
+use std::comm::Receiver;
+use std::rc::Rc;
+
+use geom::point::{Point2D, TypedPoint2D};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use layers::geometry::DevicePixel;
+use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
+use servo_msg::compositor_msg::{FinishedLoading, Blank, Loading, PerformingLayout, ReadyState};
+use servo_util::geometry::ScreenPx;
+
+use glfw;
+use glfw::Context;
+
+/// A structure responsible for setting up and tearing down the entire windowing system.
+pub struct Application {
+ pub glfw: glfw::Glfw,
+}
+
+impl ApplicationMethods for Application {
+ fn new() -> Application {
+ let app = glfw::init(glfw::LOG_ERRORS);
+ match app {
+ Err(_) => {
+ // handles things like inability to connect to X
+ // cannot simply fail, since the runtime isn't up yet (causes a nasty abort)
+ println!("GLFW initialization failed");
+ unsafe { exit(1); }
+ }
+ Ok(app) => {
+ Application { glfw: app }
+ }
+ }
+ }
+}
+
+macro_rules! glfw_callback(
+ (
+ $callback:path ($($arg:ident: $arg_ty:ty),*) $block:expr
+ ) => ({
+ struct GlfwCallback;
+ impl $callback for GlfwCallback {
+ fn call(&self $(, $arg: $arg_ty)*) {
+ $block
+ }
+ }
+ ~GlfwCallback
+ });
+
+ (
+ [$($state:ident: $state_ty:ty),*],
+ $callback:path ($($arg:ident: $arg_ty:ty),*) $block:expr
+ ) => ({
+ struct GlfwCallback {
+ $($state: $state_ty,)*
+ }
+ impl $callback for GlfwCallback {
+ fn call(&self $(, $arg: $arg_ty)*) {
+ $block
+ }
+ }
+ ~GlfwCallback {
+ $($state: $state,)*
+ }
+ });
+)
+
+
+/// The type of a window.
+pub struct Window {
+ glfw: glfw::Glfw,
+
+ glfw_window: glfw::Window,
+ events: Receiver<(f64, glfw::WindowEvent)>,
+
+ event_queue: RefCell<Vec<WindowEvent>>,
+
+ mouse_down_button: Cell<Option<glfw::MouseButton>>,
+ mouse_down_point: Cell<Point2D<c_int>>,
+
+ ready_state: Cell<ReadyState>,
+ render_state: Cell<RenderState>,
+
+ last_title_set_time: Cell<Timespec>,
+}
+
+impl WindowMethods<Application> for Window {
+ /// Creates a new window.
+ fn new(app: &Application, is_foreground: bool) -> Rc<Window> {
+ // Create the GLFW window.
+ app.glfw.window_hint(glfw::Visible(is_foreground));
+ let (glfw_window, events) = app.glfw.create_window(800, 600, "Servo", glfw::Windowed)
+ .expect("Failed to create GLFW window");
+ glfw_window.make_current();
+
+ // Create our window object.
+ let window = Window {
+ glfw: app.glfw,
+
+ glfw_window: glfw_window,
+ events: events,
+
+ event_queue: RefCell::new(vec!()),
+
+ mouse_down_button: Cell::new(None),
+ mouse_down_point: Cell::new(Point2D(0 as c_int, 0)),
+
+ ready_state: Cell::new(Blank),
+ render_state: Cell::new(IdleRenderState),
+
+ last_title_set_time: Cell::new(Timespec::new(0, 0)),
+ };
+
+ // Register event handlers.
+ window.glfw_window.set_framebuffer_size_polling(true);
+ window.glfw_window.set_refresh_polling(true);
+ window.glfw_window.set_key_polling(true);
+ window.glfw_window.set_mouse_button_polling(true);
+ window.glfw_window.set_cursor_pos_polling(true);
+ window.glfw_window.set_scroll_polling(true);
+
+ let wrapped_window = Rc::new(window);
+
+ wrapped_window
+ }
+
+ /// Returns the size of the window in hardware pixels.
+ fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
+ let (width, height) = self.glfw_window.get_framebuffer_size();
+ TypedSize2D(width as uint, height as uint)
+ }
+
+ /// Returns the size of the window in density-independent "px" units.
+ fn size(&self) -> TypedSize2D<ScreenPx, f32> {
+ let (width, height) = self.glfw_window.get_size();
+ TypedSize2D(width as f32, height as f32)
+ }
+
+ /// Presents the window to the screen (perhaps by page flipping).
+ fn present(&self) {
+ self.glfw_window.swap_buffers();
+ }
+
+ fn recv(&self) -> WindowEvent {
+ {
+ let mut event_queue = self.event_queue.borrow_mut();
+ if !event_queue.is_empty() {
+ return event_queue.remove(0).unwrap();
+ }
+ }
+
+ self.glfw.poll_events();
+ for (_, event) in glfw::flush_messages(&self.events) {
+ self.handle_window_event(&self.glfw_window, event);
+ }
+
+ if self.glfw_window.should_close() {
+ QuitWindowEvent
+ } else {
+ self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
+ }
+ }
+
+ /// Sets the ready state.
+ fn set_ready_state(&self, ready_state: ReadyState) {
+ self.ready_state.set(ready_state);
+ self.update_window_title()
+ }
+
+ /// Sets the render state.
+ fn set_render_state(&self, render_state: RenderState) {
+ if self.ready_state.get() == FinishedLoading &&
+ self.render_state.get() == RenderingRenderState &&
+ render_state == IdleRenderState {
+ // page loaded
+ self.event_queue.borrow_mut().push(FinishedWindowEvent);
+ }
+
+ self.render_state.set(render_state);
+ self.update_window_title()
+ }
+
+ fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
+ let backing_size = self.framebuffer_size().width.get();
+ let window_size = self.size().width.get();
+ ScaleFactor((backing_size as f32) / window_size)
+ }
+}
+
+impl Window {
+ fn handle_window_event(&self, window: &glfw::Window, event: glfw::WindowEvent) {
+ match event {
+ glfw::KeyEvent(key, _, action, mods) => {
+ if action == glfw::Press {
+ self.handle_key(key, mods)
+ }
+ },
+ glfw::FramebufferSizeEvent(width, height) => {
+ self.event_queue.borrow_mut().push(
+ ResizeWindowEvent(TypedSize2D(width as uint, height as uint)));
+ },
+ glfw::RefreshEvent => {
+ self.event_queue.borrow_mut().push(RefreshWindowEvent);
+ },
+ glfw::MouseButtonEvent(button, action, _mods) => {
+ let (x, y) = window.get_cursor_pos();
+ //handle hidpi displays, since GLFW returns non-hi-def coordinates.
+ let (backing_size, _) = window.get_framebuffer_size();
+ let (window_size, _) = window.get_size();
+ let hidpi = (backing_size as f32) / (window_size as f32);
+ let x = x as f32 * hidpi;
+ let y = y as f32 * hidpi;
+ if button == glfw::MouseButtonLeft || button == glfw::MouseButtonRight {
+ self.handle_mouse(button, action, x as i32, y as i32);
+ }
+ },
+ glfw::CursorPosEvent(xpos, ypos) => {
+ self.event_queue.borrow_mut().push(
+ MouseWindowMoveEventClass(TypedPoint2D(xpos as f32, ypos as f32)));
+ },
+ glfw::ScrollEvent(xpos, ypos) => {
+ match (window.get_key(glfw::KeyLeftControl),
+ window.get_key(glfw::KeyRightControl)) {
+ (glfw::Press, _) | (_, glfw::Press) => {
+ // Ctrl-Scrollwheel simulates a "pinch zoom" gesture.
+ if ypos < 0.0 {
+ self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.0/1.1));
+ } else if ypos > 0.0 {
+ self.event_queue.borrow_mut().push(PinchZoomWindowEvent(1.1));
+ }
+ },
+ _ => {
+ let dx = (xpos as f32) * 30.0;
+ let dy = (ypos as f32) * 30.0;
+ self.scroll_window(dx, dy);
+ }
+ }
+
+ },
+ _ => {}
+ }
+ }
+
+ /// Helper function to send a scroll event.
+ fn scroll_window(&self, dx: f32, dy: f32) {
+ let (x, y) = self.glfw_window.get_cursor_pos();
+ //handle hidpi displays, since GLFW returns non-hi-def coordinates.
+ let (backing_size, _) = self.glfw_window.get_framebuffer_size();
+ let (window_size, _) = self.glfw_window.get_size();
+ let hidpi = (backing_size as f32) / (window_size as f32);
+ let x = x as f32 * hidpi;
+ let y = y as f32 * hidpi;
+
+ self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(dx, dy),
+ TypedPoint2D(x as i32, y as i32)));
+ }
+
+ /// Helper function to set the window title in accordance with the ready state.
+ fn update_window_title(&self) {
+ let now = time::get_time();
+ if now.sec == self.last_title_set_time.get().sec {
+ return
+ }
+ self.last_title_set_time.set(now);
+
+ match self.ready_state.get() {
+ Blank => {
+ self.glfw_window.set_title("blank — Servo")
+ }
+ Loading => {
+ self.glfw_window.set_title("Loading — Servo")
+ }
+ PerformingLayout => {
+ self.glfw_window.set_title("Performing Layout — Servo")
+ }
+ FinishedLoading => {
+ match self.render_state.get() {
+ RenderingRenderState => {
+ self.glfw_window.set_title("Rendering — Servo")
+ }
+ IdleRenderState => {
+ self.glfw_window.set_title("Servo")
+ }
+ }
+ }
+ }
+ }
+
+ /// Helper function to handle keyboard events.
+ fn handle_key(&self, key: glfw::Key, mods: glfw::Modifiers) {
+ match key {
+ glfw::KeyEscape => self.glfw_window.set_should_close(true),
+ glfw::KeyL if mods.contains(glfw::Control) => self.load_url(), // Ctrl+L
+ glfw::KeyEqual if mods.contains(glfw::Control) => { // Ctrl-+
+ self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1));
+ }
+ glfw::KeyMinus if mods.contains(glfw::Control) => { // Ctrl--
+ self.event_queue.borrow_mut().push(ZoomWindowEvent(1.0/1.1));
+ }
+ glfw::KeyBackspace if mods.contains(glfw::Shift) => { // Shift-Backspace
+ self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
+ }
+ glfw::KeyBackspace => { // Backspace
+ self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
+ }
+ glfw::KeyPageDown => {
+ let (_, height) = self.glfw_window.get_size();
+ self.scroll_window(0.0, -height as f32);
+ }
+ glfw::KeyPageUp => {
+ let (_, height) = self.glfw_window.get_size();
+ self.scroll_window(0.0, height as f32);
+ }
+ _ => {}
+ }
+ }
+
+ /// Helper function to handle a click
+ fn handle_mouse(&self, button: glfw::MouseButton, action: glfw::Action, x: c_int, y: c_int) {
+ // FIXME(tkuehn): max pixel dist should be based on pixel density
+ let max_pixel_dist = 10f64;
+ let event = match action {
+ glfw::Press => {
+ self.mouse_down_point.set(Point2D(x, y));
+ self.mouse_down_button.set(Some(button));
+ MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
+ }
+ glfw::Release => {
+ match self.mouse_down_button.get() {
+ None => (),
+ Some(but) if button == but => {
+ let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
+ let pixel_dist = ((pixel_dist.x * pixel_dist.x +
+ pixel_dist.y * pixel_dist.y) as f64).sqrt();
+ if pixel_dist < max_pixel_dist {
+ let click_event = MouseWindowClickEvent(button as uint,
+ TypedPoint2D(x as f32, y as f32));
+ self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
+ }
+ }
+ Some(_) => (),
+ }
+ MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
+ }
+ _ => fail!("I cannot recognize the type of mouse action that occured. :-(")
+ };
+ self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
+ }
+
+ /// Helper function to pop up an alert box prompting the user to load a URL.
+ fn load_url(&self) {
+ let mut alert: Alert = AlertMethods::new("Navigate to:");
+ alert.add_prompt();
+ alert.run();
+ let value = alert.prompt_value();
+ if "" == value.as_slice() { // To avoid crashing on Linux.
+ self.event_queue.borrow_mut().push(LoadUrlWindowEvent("http://purple.com/".to_string()))
+ } else {
+ self.event_queue.borrow_mut().push(LoadUrlWindowEvent(value.clone()))
+ }
+ }
+}
diff --git a/components/compositing/platform/common/glut_windowing.rs b/components/compositing/platform/common/glut_windowing.rs
new file mode 100644
index 00000000000..8e673376dc5
--- /dev/null
+++ b/components/compositing/platform/common/glut_windowing.rs
@@ -0,0 +1,303 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A windowing implementation using GLUT.
+
+use windowing::{ApplicationMethods, WindowEvent, WindowMethods};
+use windowing::{IdleWindowEvent, ResizeWindowEvent, LoadUrlWindowEvent, MouseWindowEventClass};
+use windowing::{ScrollWindowEvent, ZoomWindowEvent, NavigationWindowEvent, FinishedWindowEvent};
+use windowing::{MouseWindowClickEvent, MouseWindowMouseDownEvent, MouseWindowMouseUpEvent};
+use windowing::{Forward, Back};
+
+use alert::{Alert, AlertMethods};
+use libc::{c_int, c_uchar};
+use std::cell::{Cell, RefCell};
+use std::rc::Rc;
+use geom::point::{Point2D, TypedPoint2D};
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use layers::geometry::DevicePixel;
+use servo_msg::compositor_msg::{IdleRenderState, RenderState, RenderingRenderState};
+use servo_msg::compositor_msg::{FinishedLoading, Blank, ReadyState};
+use servo_util::geometry::ScreenPx;
+
+use glut::glut::{ACTIVE_SHIFT, DOUBLE, WindowHeight};
+use glut::glut::WindowWidth;
+use glut::glut;
+
+// static THROBBER: [char, ..8] = [ '⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷' ];
+
+/// A structure responsible for setting up and tearing down the entire windowing system.
+pub struct Application;
+
+impl ApplicationMethods for Application {
+ fn new() -> Application {
+ glut::init();
+ glut::init_display_mode(DOUBLE);
+ Application
+ }
+}
+
+impl Drop for Application {
+ fn drop(&mut self) {
+ drop_local_window();
+ }
+}
+
+/// The type of a window.
+pub struct Window {
+ pub glut_window: glut::Window,
+
+ pub event_queue: RefCell<Vec<WindowEvent>>,
+
+ pub drag_origin: Point2D<c_int>,
+
+ pub mouse_down_button: Cell<c_int>,
+ pub mouse_down_point: Cell<Point2D<c_int>>,
+
+ pub ready_state: Cell<ReadyState>,
+ pub render_state: Cell<RenderState>,
+ pub throbber_frame: Cell<u8>,
+}
+
+impl WindowMethods<Application> for Window {
+ /// Creates a new window.
+ fn new(_: &Application, _: bool) -> Rc<Window> {
+ // Create the GLUT window.
+ glut::init_window_size(800, 600);
+ let glut_window = glut::create_window("Servo".to_string());
+
+ // Create our window object.
+ let window = Window {
+ glut_window: glut_window,
+
+ event_queue: RefCell::new(vec!()),
+
+ drag_origin: Point2D(0 as c_int, 0),
+
+ mouse_down_button: Cell::new(0),
+ mouse_down_point: Cell::new(Point2D(0 as c_int, 0)),
+
+ ready_state: Cell::new(Blank),
+ render_state: Cell::new(IdleRenderState),
+ throbber_frame: Cell::new(0),
+ };
+
+ // Register event handlers.
+
+ //Added dummy display callback to freeglut. According to freeglut ref, we should register some kind of display callback after freeglut 3.0.
+
+ struct DisplayCallbackState;
+ impl glut::DisplayCallback for DisplayCallbackState {
+ fn call(&self) {
+ debug!("GLUT display func registgered");
+ }
+ }
+ glut::display_func(box DisplayCallbackState);
+ struct ReshapeCallbackState;
+ impl glut::ReshapeCallback for ReshapeCallbackState {
+ fn call(&self, width: c_int, height: c_int) {
+ let tmp = local_window();
+ tmp.event_queue.borrow_mut().push(ResizeWindowEvent(TypedSize2D(width as uint, height as uint)))
+ }
+ }
+ glut::reshape_func(glut_window, box ReshapeCallbackState);
+ struct KeyboardCallbackState;
+ impl glut::KeyboardCallback for KeyboardCallbackState {
+ fn call(&self, key: c_uchar, _x: c_int, _y: c_int) {
+ let tmp = local_window();
+ tmp.handle_key(key)
+ }
+ }
+ glut::keyboard_func(box KeyboardCallbackState);
+ struct MouseCallbackState;
+ impl glut::MouseCallback for MouseCallbackState {
+ fn call(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
+ if button < 3 {
+ let tmp = local_window();
+ tmp.handle_mouse(button, state, x, y);
+ } else {
+ match button {
+ 3 => {
+ let tmp = local_window();
+ tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
+ TypedPoint2D(0.0f32, 5.0f32),
+ TypedPoint2D(0i32, 5i32)));
+ },
+ 4 => {
+ let tmp = local_window();
+ tmp.event_queue.borrow_mut().push(ScrollWindowEvent(
+ TypedPoint2D(0.0f32, -5.0f32),
+ TypedPoint2D(0i32, -5i32)));
+ },
+ _ => {}
+ }
+ }
+ }
+ }
+ glut::mouse_func(box MouseCallbackState);
+
+ let wrapped_window = Rc::new(window);
+
+ install_local_window(wrapped_window.clone());
+
+ wrapped_window
+ }
+
+ /// Returns the size of the window in hardware pixels.
+ fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint> {
+ TypedSize2D(glut::get(WindowWidth) as uint, glut::get(WindowHeight) as uint)
+ }
+
+ /// Returns the size of the window in density-independent "px" units.
+ fn size(&self) -> TypedSize2D<ScreenPx, f32> {
+ self.framebuffer_size().as_f32() / self.hidpi_factor()
+ }
+
+ /// Presents the window to the screen (perhaps by page flipping).
+ fn present(&self) {
+ glut::swap_buffers();
+ }
+
+ fn recv(&self) -> WindowEvent {
+ if !self.event_queue.borrow_mut().is_empty() {
+ return self.event_queue.borrow_mut().remove(0).unwrap();
+ }
+
+ glut::check_loop();
+
+ self.event_queue.borrow_mut().remove(0).unwrap_or(IdleWindowEvent)
+ }
+
+ /// Sets the ready state.
+ fn set_ready_state(&self, ready_state: ReadyState) {
+ self.ready_state.set(ready_state);
+ //FIXME: set_window_title causes crash with Android version of freeGLUT. Temporarily blocked.
+ //self.update_window_title()
+ }
+
+ /// Sets the render state.
+ fn set_render_state(&self, render_state: RenderState) {
+ if self.ready_state.get() == FinishedLoading &&
+ self.render_state.get() == RenderingRenderState &&
+ render_state == IdleRenderState {
+ // page loaded
+ self.event_queue.borrow_mut().push(FinishedWindowEvent);
+ }
+
+ self.render_state.set(render_state);
+ //FIXME: set_window_title causes crash with Android version of freeGLUT. Temporarily blocked.
+ //self.update_window_title()
+ }
+
+ fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32> {
+ //FIXME: Do nothing in GLUT now.
+ ScaleFactor(1.0)
+ }
+}
+
+impl Window {
+ /// Helper function to set the window title in accordance with the ready state.
+ // fn update_window_title(&self) {
+ // let throbber = THROBBER[self.throbber_frame];
+ // match self.ready_state {
+ // Blank => {
+ // glut::set_window_title(self.glut_window, "Blank")
+ // }
+ // Loading => {
+ // glut::set_window_title(self.glut_window, format!("{:c} Loading . Servo", throbber))
+ // }
+ // PerformingLayout => {
+ // glut::set_window_title(self.glut_window, format!("{:c} Performing Layout . Servo", throbber))
+ // }
+ // FinishedLoading => {
+ // match self.render_state {
+ // RenderingRenderState => {
+ // glut::set_window_title(self.glut_window, format!("{:c} Rendering . Servo", throbber))
+ // }
+ // IdleRenderState => glut::set_window_title(self.glut_window, "Servo"),
+ // }
+ // }
+ // }
+ // }
+
+ /// Helper function to handle keyboard events.
+ fn handle_key(&self, key: u8) {
+ debug!("got key: {}", key);
+ let modifiers = glut::get_modifiers();
+ match key {
+ 42 => self.load_url(),
+ 43 => self.event_queue.borrow_mut().push(ZoomWindowEvent(1.1)),
+ 45 => self.event_queue.borrow_mut().push(ZoomWindowEvent(0.909090909)),
+ 56 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0f32, 5.0f32),
+ TypedPoint2D(0i32, 5i32))),
+ 50 => self.event_queue.borrow_mut().push(ScrollWindowEvent(TypedPoint2D(0.0f32, -5.0f32),
+ TypedPoint2D(0i32, -5i32))),
+ 127 => {
+ if (modifiers & ACTIVE_SHIFT) != 0 {
+ self.event_queue.borrow_mut().push(NavigationWindowEvent(Forward));
+ }
+ else {
+ self.event_queue.borrow_mut().push(NavigationWindowEvent(Back));
+ }
+ }
+ _ => {}
+ }
+ }
+
+ /// Helper function to handle a click
+ fn handle_mouse(&self, button: c_int, state: c_int, x: c_int, y: c_int) {
+ // FIXME(tkuehn): max pixel dist should be based on pixel density
+ let max_pixel_dist = 10f32;
+ let event = match state {
+ glut::MOUSE_DOWN => {
+ self.mouse_down_point.set(Point2D(x, y));
+ self.mouse_down_button.set(button);
+ MouseWindowMouseDownEvent(button as uint, TypedPoint2D(x as f32, y as f32))
+ }
+ glut::MOUSE_UP => {
+ if self.mouse_down_button.get() == button {
+ let pixel_dist = self.mouse_down_point.get() - Point2D(x, y);
+ let pixel_dist = ((pixel_dist.x * pixel_dist.x +
+ pixel_dist.y * pixel_dist.y) as f32).sqrt();
+ if pixel_dist < max_pixel_dist {
+ let click_event = MouseWindowClickEvent(button as uint,
+ TypedPoint2D(x as f32, y as f32));
+ self.event_queue.borrow_mut().push(MouseWindowEventClass(click_event));
+ }
+ }
+ MouseWindowMouseUpEvent(button as uint, TypedPoint2D(x as f32, y as f32))
+ }
+ _ => fail!("I cannot recognize the type of mouse action that occured. :-(")
+ };
+ self.event_queue.borrow_mut().push(MouseWindowEventClass(event));
+ }
+
+ /// Helper function to pop up an alert box prompting the user to load a URL.
+ fn load_url(&self) {
+ let mut alert: Alert = AlertMethods::new("Navigate to:");
+ alert.add_prompt();
+ alert.run();
+ let value = alert.prompt_value();
+ if "" == value.as_slice() { // To avoid crashing on Linux.
+ self.event_queue.borrow_mut().push(LoadUrlWindowEvent("http://purple.com/".to_string()))
+ } else {
+ self.event_queue.borrow_mut().push(LoadUrlWindowEvent(value.clone()))
+ }
+ }
+}
+
+local_data_key!(TLS_KEY: Rc<Window>)
+
+fn install_local_window(window: Rc<Window>) {
+ TLS_KEY.replace(Some(window));
+}
+
+fn drop_local_window() {
+ TLS_KEY.replace(None);
+}
+
+fn local_window() -> Rc<Window> {
+ TLS_KEY.get().unwrap().clone()
+}
diff --git a/components/compositing/platform/mod.rs b/components/compositing/platform/mod.rs
new file mode 100644
index 00000000000..5d8a8cba470
--- /dev/null
+++ b/components/compositing/platform/mod.rs
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Platform-specific functionality for Servo.
+
+#[cfg(target_os="android")]
+pub use platform::common::glut_windowing::{Application, Window};
+#[cfg(not(target_os="android"))]
+pub use platform::common::glfw_windowing::{Application, Window};
+
+pub mod common {
+ #[cfg(target_os="android")]
+ pub mod glut_windowing;
+ #[cfg(not(target_os="android"))]
+ pub mod glfw_windowing;
+}
+
diff --git a/components/compositing/windowing.rs b/components/compositing/windowing.rs
new file mode 100644
index 00000000000..77b921bacba
--- /dev/null
+++ b/components/compositing/windowing.rs
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
+
+use geom::point::TypedPoint2D;
+use geom::scale_factor::ScaleFactor;
+use geom::size::TypedSize2D;
+use layers::geometry::DevicePixel;
+use servo_msg::compositor_msg::{ReadyState, RenderState};
+use servo_util::geometry::ScreenPx;
+use std::rc::Rc;
+
+pub enum MouseWindowEvent {
+ MouseWindowClickEvent(uint, TypedPoint2D<DevicePixel, f32>),
+ MouseWindowMouseDownEvent(uint, TypedPoint2D<DevicePixel, f32>),
+ MouseWindowMouseUpEvent(uint, TypedPoint2D<DevicePixel, f32>),
+}
+
+pub enum WindowNavigateMsg {
+ Forward,
+ Back,
+}
+
+/// Events that the windowing system sends to Servo.
+pub enum WindowEvent {
+ /// Sent when no message has arrived.
+ ///
+ /// FIXME: This is a bogus event and is only used because we don't have the new
+ /// scheduler integrated with the platform event loop.
+ IdleWindowEvent,
+ /// Sent when part of the window is marked dirty and needs to be redrawn.
+ RefreshWindowEvent,
+ /// Sent when the window is resized.
+ ResizeWindowEvent(TypedSize2D<DevicePixel, uint>),
+ /// Sent when a new URL is to be loaded.
+ LoadUrlWindowEvent(String),
+ /// Sent when a mouse hit test is to be performed.
+ MouseWindowEventClass(MouseWindowEvent),
+ /// Sent when a mouse move.
+ MouseWindowMoveEventClass(TypedPoint2D<DevicePixel, f32>),
+ /// Sent when the user scrolls. Includes the current cursor position.
+ ScrollWindowEvent(TypedPoint2D<DevicePixel, f32>, TypedPoint2D<DevicePixel, i32>),
+ /// Sent when the user zooms.
+ ZoomWindowEvent(f32),
+ /// Simulated "pinch zoom" gesture for non-touch platforms (e.g. ctrl-scrollwheel).
+ PinchZoomWindowEvent(f32),
+ /// Sent when the user uses chrome navigation (i.e. backspace or shift-backspace).
+ NavigationWindowEvent(WindowNavigateMsg),
+ /// Sent when rendering is finished.
+ FinishedWindowEvent,
+ /// Sent when the user quits the application
+ QuitWindowEvent,
+}
+
+/// Methods for an abstract Application.
+pub trait ApplicationMethods {
+ fn new() -> Self;
+}
+
+pub trait WindowMethods<A> {
+ /// Creates a new window.
+ fn new(app: &A, is_foreground: bool) -> Rc<Self>;
+ /// Returns the size of the window in hardware pixels.
+ fn framebuffer_size(&self) -> TypedSize2D<DevicePixel, uint>;
+ /// Returns the size of the window in density-independent "px" units.
+ fn size(&self) -> TypedSize2D<ScreenPx, f32>;
+ /// Presents the window to the screen (perhaps by page flipping).
+ fn present(&self);
+
+ /// Spins the event loop and returns the next event.
+ fn recv(&self) -> WindowEvent;
+
+ /// Sets the ready state of the current page.
+ fn set_ready_state(&self, ready_state: ReadyState);
+ /// Sets the render state of the current page.
+ fn set_render_state(&self, render_state: RenderState);
+
+ /// Returns the hidpi factor of the monitor.
+ fn hidpi_factor(&self) -> ScaleFactor<ScreenPx, DevicePixel, f32>;
+}
+
diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml
new file mode 100644
index 00000000000..fdac5770f70
--- /dev/null
+++ b/components/gfx/Cargo.toml
@@ -0,0 +1,62 @@
+[package]
+
+name = "gfx"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "gfx"
+path = "lib.rs"
+
+[dependencies.macros]
+path = "../macros"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.msg]
+path = "../msg"
+
+[dependencies.style]
+path = "../style"
+
+[dependencies.azure]
+git = "https://github.com/servo/rust-azure"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.layers]
+git = "https://github.com/servo/rust-layers"
+
+[dependencies.stb_image]
+git = "https://github.com/servo/rust-stb-image"
+
+[dependencies.png]
+git = "https://github.com/servo/rust-png"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
+
+[dependencies.harfbuzz]
+git = "https://github.com/servo/rust-harfbuzz"
+
+[dependencies.fontconfig]
+git = "https://github.com/servo/rust-fontconfig"
+
+[dependencies.freetype]
+git = "https://github.com/servo/rust-freetype"
+
+[dependencies.core_foundation]
+git = "http://github.com/servo/rust-core-foundation"
+
+[dependencies.core_graphics]
+git = "http://github.com/servo/rust-core-graphics"
+
+[dependencies.core_text]
+git = "http://github.com/servo/rust-core-text"
+
+
diff --git a/components/gfx/buffer_map.rs b/components/gfx/buffer_map.rs
new file mode 100644
index 00000000000..0551385f717
--- /dev/null
+++ b/components/gfx/buffer_map.rs
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::hashmap::HashMap;
+use geom::size::Size2D;
+use layers::platform::surface::NativePaintingGraphicsContext;
+use layers::layers::LayerBuffer;
+use std::hash::Hash;
+use std::hash::sip::SipState;
+use std::mem;
+
+/// This is a struct used to store buffers when they are not in use.
+/// The render task can quickly query for a particular size of buffer when it
+/// needs it.
+pub struct BufferMap {
+ /// A HashMap that stores the Buffers.
+ map: HashMap<BufferKey, BufferValue>,
+ /// The current amount of memory stored by the BufferMap's buffers.
+ mem: uint,
+ /// The maximum allowed memory. Unused buffers will be deleted
+ /// when this threshold is exceeded.
+ max_mem: uint,
+ /// A monotonically increasing counter to track how recently tile sizes were used.
+ counter: uint,
+}
+
+/// A key with which to store buffers. It is based on the size of the buffer.
+#[deriving(Eq)]
+struct BufferKey([uint, ..2]);
+
+impl Hash for BufferKey {
+ fn hash(&self, state: &mut SipState) {
+ let BufferKey(ref bytes) = *self;
+ bytes.as_slice().hash(state);
+ }
+}
+
+impl PartialEq for BufferKey {
+ fn eq(&self, other: &BufferKey) -> bool {
+ let BufferKey(s) = *self;
+ let BufferKey(o) = *other;
+ s[0] == o[0] && s[1] == o[1]
+ }
+}
+
+/// Create a key from a given size
+impl BufferKey {
+ fn get(input: Size2D<uint>) -> BufferKey {
+ BufferKey([input.width, input.height])
+ }
+}
+
+/// A helper struct to keep track of buffers in the HashMap
+struct BufferValue {
+ /// An array of buffers, all the same size
+ buffers: Vec<Box<LayerBuffer>>,
+ /// The counter when this size was last requested
+ last_action: uint,
+}
+
+impl BufferMap {
+ // Creates a new BufferMap with a given buffer limit.
+ pub fn new(max_mem: uint) -> BufferMap {
+ BufferMap {
+ map: HashMap::new(),
+ mem: 0u,
+ max_mem: max_mem,
+ counter: 0u,
+ }
+ }
+
+ /// Insert a new buffer into the map.
+ pub fn insert(&mut self, graphics_context: &NativePaintingGraphicsContext, new_buffer: Box<LayerBuffer>) {
+ let new_key = BufferKey::get(new_buffer.get_size_2d());
+
+ // If all our buffers are the same size and we're already at our
+ // memory limit, no need to store this new buffer; just let it drop.
+ if self.mem + new_buffer.get_mem() > self.max_mem && self.map.len() == 1 &&
+ self.map.contains_key(&new_key) {
+ new_buffer.destroy(graphics_context);
+ return;
+ }
+
+ self.mem += new_buffer.get_mem();
+ // use lazy insertion function to prevent unnecessary allocation
+ let counter = &self.counter;
+ self.map.find_or_insert_with(new_key, |_| BufferValue {
+ buffers: vec!(),
+ last_action: *counter
+ }).buffers.push(new_buffer);
+
+ let mut opt_key: Option<BufferKey> = None;
+ while self.mem > self.max_mem {
+ let old_key = match opt_key {
+ Some(key) => key,
+ None => {
+ match self.map.iter().min_by(|&(_, x)| x.last_action) {
+ Some((k, _)) => *k,
+ None => fail!("BufferMap: tried to delete with no elements in map"),
+ }
+ }
+ };
+ if {
+ let list = &mut self.map.get_mut(&old_key).buffers;
+ let condemned_buffer = list.pop().take_unwrap();
+ self.mem -= condemned_buffer.get_mem();
+ condemned_buffer.destroy(graphics_context);
+ list.is_empty()
+ }
+ { // then
+ self.map.pop(&old_key); // Don't store empty vectors!
+ opt_key = None;
+ } else {
+ opt_key = Some(old_key);
+ }
+ }
+ }
+
+ // Try to find a buffer for the given size.
+ pub fn find(&mut self, size: Size2D<uint>) -> Option<Box<LayerBuffer>> {
+ let mut flag = false; // True if key needs to be popped after retrieval.
+ let key = BufferKey::get(size);
+ let ret = match self.map.find_mut(&key) {
+ Some(ref mut buffer_val) => {
+ buffer_val.last_action = self.counter;
+ self.counter += 1;
+
+ let buffer = buffer_val.buffers.pop().take_unwrap();
+ self.mem -= buffer.get_mem();
+ if buffer_val.buffers.is_empty() {
+ flag = true;
+ }
+ Some(buffer)
+ }
+ None => None,
+ };
+
+ if flag {
+ self.map.pop(&key); // Don't store empty vectors!
+ }
+
+ ret
+ }
+
+ /// Destroys all buffers.
+ pub fn clear(&mut self, graphics_context: &NativePaintingGraphicsContext) {
+ let map = mem::replace(&mut self.map, HashMap::new());
+ for (_, value) in map.move_iter() {
+ for tile in value.buffers.move_iter() {
+ tile.destroy(graphics_context)
+ }
+ }
+ self.mem = 0
+ }
+}
diff --git a/components/gfx/color.rs b/components/gfx/color.rs
new file mode 100644
index 00000000000..ffd5b5ed2b2
--- /dev/null
+++ b/components/gfx/color.rs
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use azure::AzFloat;
+use AzColor = azure::azure_hl::Color;
+
+pub type Color = AzColor;
+
+pub fn rgb(r: u8, g: u8, b: u8) -> AzColor {
+ AzColor {
+ r: (r as AzFloat) / (255.0 as AzFloat),
+ g: (g as AzFloat) / (255.0 as AzFloat),
+ b: (b as AzFloat) / (255.0 as AzFloat),
+ a: 1.0 as AzFloat
+ }
+}
+
+pub fn rgba(r: AzFloat, g: AzFloat, b: AzFloat, a: AzFloat) -> AzColor {
+ AzColor { r: r, g: g, b: b, a: a }
+}
diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs
new file mode 100644
index 00000000000..e0796c61fb2
--- /dev/null
+++ b/components/gfx/display_list/mod.rs
@@ -0,0 +1,773 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Servo heavily uses display lists, which are retained-mode lists of rendering commands to
+//! perform. Using a list instead of rendering elements in immediate mode allows transforms, hit
+//! testing, and invalidation to be performed using the same primitives as painting. It also allows
+//! Servo to aggressively cull invisible and out-of-bounds rendering elements, to reduce overdraw.
+//! Finally, display lists allow tiles to be farmed out onto multiple CPUs and rendered in
+//! parallel (although this benefit does not apply to GPU-based rendering).
+//!
+//! Display items describe relatively high-level drawing operations (for example, entire borders
+//! and shadows instead of lines and blur operations), to reduce the amount of allocation required.
+//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of
+//! low-level drawing primitives.
+
+use color::Color;
+use render_context::RenderContext;
+use text::glyph::CharIndex;
+use text::TextRun;
+
+use collections::dlist::DList;
+use collections::dlist;
+use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D};
+use libc::uintptr_t;
+use servo_net::image::base::Image;
+use servo_util::geometry::Au;
+use servo_util::range::Range;
+use std::fmt;
+use std::mem;
+use std::slice::Items;
+use style::computed_values::border_style;
+use sync::Arc;
+use std::num::Zero;
+use std::ptr;
+
+use azure::AzFloat;
+use azure::scaled_font::ScaledFont;
+use azure::azure_hl::ColorPattern;
+
+pub mod optimizer;
+
+/// An opaque handle to a node. The only safe operation that can be performed on this node is to
+/// compare it to another opaque handle or to another node.
+///
+/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout
+/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for
+/// locality reasons. Using `OpaqueNode` enforces this invariant.
+#[deriving(Clone, PartialEq)]
+pub struct OpaqueNode(pub uintptr_t);
+
+impl OpaqueNode {
+ /// Returns the address of this node, for debugging purposes.
+ pub fn id(&self) -> uintptr_t {
+ let OpaqueNode(pointer) = *self;
+ pointer
+ }
+}
+
+trait ScaledFontExtensionMethods {
+ fn draw_text_into_context(&self,
+ rctx: &RenderContext,
+ run: &Box<TextRun>,
+ range: &Range<CharIndex>,
+ baseline_origin: Point2D<Au>,
+ color: Color,
+ antialias: bool);
+}
+
+impl ScaledFontExtensionMethods for ScaledFont {
+ fn draw_text_into_context(&self,
+ rctx: &RenderContext,
+ run: &Box<TextRun>,
+ range: &Range<CharIndex>,
+ baseline_origin: Point2D<Au>,
+ color: Color,
+ antialias: bool) {
+ use libc::types::common::c99::uint32_t;
+ use azure::{struct__AzDrawOptions,
+ struct__AzGlyph,
+ struct__AzGlyphBuffer,
+ struct__AzPoint};
+ use azure::azure::{AzDrawTargetFillGlyphs};
+
+ let target = rctx.get_draw_target();
+ let pattern = ColorPattern::new(color);
+ let azure_pattern = pattern.azure_color_pattern;
+ assert!(azure_pattern.is_not_null());
+
+ let fields = if antialias {
+ 0x0200
+ } else {
+ 0
+ };
+
+ let mut options = struct__AzDrawOptions {
+ mAlpha: 1f64 as AzFloat,
+ fields: fields,
+ };
+
+ let mut origin = baseline_origin.clone();
+ let mut azglyphs = vec!();
+ azglyphs.reserve(range.length().to_uint());
+
+ for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) {
+ for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) {
+ let glyph_advance = glyph.advance();
+ let glyph_offset = glyph.offset().unwrap_or(Zero::zero());
+
+ let azglyph = struct__AzGlyph {
+ mIndex: glyph.id() as uint32_t,
+ mPosition: struct__AzPoint {
+ x: (origin.x + glyph_offset.x).to_nearest_px() as AzFloat,
+ y: (origin.y + glyph_offset.y).to_nearest_px() as AzFloat
+ }
+ };
+ origin = Point2D(origin.x + glyph_advance, origin.y);
+ azglyphs.push(azglyph)
+ };
+ }
+
+ let azglyph_buf_len = azglyphs.len();
+ if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert.
+
+ let mut glyphbuf = struct__AzGlyphBuffer {
+ mGlyphs: azglyphs.as_mut_ptr(),
+ mNumGlyphs: azglyph_buf_len as uint32_t
+ };
+
+ unsafe {
+ // TODO(Issue #64): this call needs to move into azure_hl.rs
+ AzDrawTargetFillGlyphs(target.azure_draw_target,
+ self.get_ref(),
+ &mut glyphbuf,
+ azure_pattern,
+ &mut options,
+ ptr::mut_null());
+ }
+ }
+}
+
+/// "Steps" as defined by CSS 2.1 § E.2.
+#[deriving(Clone, PartialEq)]
+pub enum StackingLevel {
+ /// The border and backgrounds for the root of this stacking context: steps 1 and 2.
+ BackgroundAndBordersStackingLevel,
+ /// Borders and backgrounds for block-level descendants: step 4.
+ BlockBackgroundsAndBordersStackingLevel,
+ /// Floats: step 5. These are treated as pseudo-stacking contexts.
+ FloatStackingLevel,
+ /// All other content.
+ ContentStackingLevel,
+ /// Positioned descendant stacking contexts, along with their `z-index` levels.
+ ///
+ /// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
+ /// `auto`, not just an integer.
+ PositionedDescendantStackingLevel(i32)
+}
+
+impl StackingLevel {
+ pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel {
+ match level {
+ RootOfStackingContextLevel => BackgroundAndBordersStackingLevel,
+ BlockLevel => BlockBackgroundsAndBordersStackingLevel,
+ ContentLevel => ContentStackingLevel,
+ }
+ }
+}
+
+struct StackingContext {
+ /// The border and backgrounds for the root of this stacking context: steps 1 and 2.
+ pub background_and_borders: DisplayList,
+ /// Borders and backgrounds for block-level descendants: step 4.
+ pub block_backgrounds_and_borders: DisplayList,
+ /// Floats: step 5. These are treated as pseudo-stacking contexts.
+ pub floats: DisplayList,
+ /// All other content.
+ pub content: DisplayList,
+ /// Positioned descendant stacking contexts, along with their `z-index` levels.
+ ///
+ /// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle
+ /// `auto`, not just an integer.
+ pub positioned_descendants: Vec<(i32, DisplayList)>,
+}
+
+impl StackingContext {
+ /// Creates a stacking context from a display list.
+ fn new(list: DisplayList) -> StackingContext {
+ let DisplayList {
+ list: list
+ } = list;
+
+ let mut stacking_context = StackingContext {
+ background_and_borders: DisplayList::new(),
+ block_backgrounds_and_borders: DisplayList::new(),
+ floats: DisplayList::new(),
+ content: DisplayList::new(),
+ positioned_descendants: Vec::new(),
+ };
+
+ for item in list.move_iter() {
+ match item {
+ ClipDisplayItemClass(box ClipDisplayItem {
+ base: base,
+ children: sublist
+ }) => {
+ let sub_stacking_context = StackingContext::new(sublist);
+ stacking_context.merge_with_clip(sub_stacking_context, &base.bounds, base.node)
+ }
+ item => {
+ match item.base().level {
+ BackgroundAndBordersStackingLevel => {
+ stacking_context.background_and_borders.push(item)
+ }
+ BlockBackgroundsAndBordersStackingLevel => {
+ stacking_context.block_backgrounds_and_borders.push(item)
+ }
+ FloatStackingLevel => stacking_context.floats.push(item),
+ ContentStackingLevel => stacking_context.content.push(item),
+ PositionedDescendantStackingLevel(z_index) => {
+ match stacking_context.positioned_descendants
+ .mut_iter()
+ .find(|& &(z, _)| z_index == z) {
+ Some(&(_, ref mut my_list)) => {
+ my_list.push(item);
+ continue
+ }
+ None => {}
+ }
+
+ let mut new_list = DisplayList::new();
+ new_list.list.push(item);
+ stacking_context.positioned_descendants.push((z_index, new_list))
+ }
+ }
+ }
+ }
+ }
+
+ stacking_context
+ }
+
+ /// Merges another stacking context into this one, with the given clipping rectangle and DOM
+ /// node that supplies it.
+ fn merge_with_clip(&mut self,
+ other: StackingContext,
+ clip_rect: &Rect<Au>,
+ clipping_dom_node: OpaqueNode) {
+ let StackingContext {
+ background_and_borders,
+ block_backgrounds_and_borders,
+ floats,
+ content,
+ positioned_descendants: positioned_descendants
+ } = other;
+
+ let push = |destination: &mut DisplayList, source: DisplayList, level| {
+ if !source.is_empty() {
+ let base = BaseDisplayItem::new(*clip_rect, clipping_dom_node, level);
+ destination.push(ClipDisplayItemClass(box ClipDisplayItem::new(base, source)))
+ }
+ };
+
+ push(&mut self.background_and_borders,
+ background_and_borders,
+ BackgroundAndBordersStackingLevel);
+ push(&mut self.block_backgrounds_and_borders,
+ block_backgrounds_and_borders,
+ BlockBackgroundsAndBordersStackingLevel);
+ push(&mut self.floats, floats, FloatStackingLevel);
+ push(&mut self.content, content, ContentStackingLevel);
+
+ for (z_index, list) in positioned_descendants.move_iter() {
+ match self.positioned_descendants
+ .mut_iter()
+ .find(|& &(existing_z_index, _)| z_index == existing_z_index) {
+ Some(&(_, ref mut existing_list)) => {
+ push(existing_list, list, PositionedDescendantStackingLevel(z_index));
+ continue
+ }
+ None => {}
+ }
+
+ let mut new_list = DisplayList::new();
+ push(&mut new_list, list, PositionedDescendantStackingLevel(z_index));
+ self.positioned_descendants.push((z_index, new_list));
+ }
+ }
+}
+
+/// Which level to place backgrounds and borders in.
+pub enum BackgroundAndBorderLevel {
+ RootOfStackingContextLevel,
+ BlockLevel,
+ ContentLevel,
+}
+
+/// A list of rendering operations to be performed.
+#[deriving(Clone)]
+pub struct DisplayList {
+ pub list: DList<DisplayItem>,
+}
+
+pub enum DisplayListIterator<'a> {
+ EmptyDisplayListIterator,
+ ParentDisplayListIterator(Items<'a,DisplayList>),
+}
+
+impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a DisplayList> {
+ match *self {
+ EmptyDisplayListIterator => None,
+ ParentDisplayListIterator(ref mut subiterator) => subiterator.next(),
+ }
+ }
+}
+
+impl DisplayList {
+ /// Creates a new display list.
+ pub fn new() -> DisplayList {
+ DisplayList {
+ list: DList::new(),
+ }
+ }
+
+ /// Appends the given item to the display list.
+ pub fn push(&mut self, item: DisplayItem) {
+ self.list.push(item)
+ }
+
+ /// Appends the given display list to this display list, consuming the other display list in
+ /// the process.
+ pub fn push_all_move(&mut self, other: DisplayList) {
+ self.list.append(other.list)
+ }
+
+ pub fn debug(&self) {
+ if log_enabled!(::log::DEBUG) {
+ for item in self.list.iter() {
+ item.debug_with_level(0);
+ }
+ }
+ }
+
+ /// Draws the display list into the given render context. The display list must be flattened
+ /// first for correct painting.
+ pub fn draw_into_context(&self, render_context: &mut RenderContext,
+ current_transform: &Matrix2D<AzFloat>) {
+ debug!("Beginning display list.");
+ for item in self.list.iter() {
+ item.draw_into_context(render_context, current_transform)
+ }
+ debug!("Ending display list.");
+ }
+
+ /// Returns a preorder iterator over the given display list.
+ pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> {
+ ParentDisplayItemIterator(self.list.iter())
+ }
+
+ /// Returns true if this list is empty and false otherwise.
+ fn is_empty(&self) -> bool {
+ self.list.len() == 0
+ }
+
+ /// Flattens a display list into a display list with a single stacking level according to the
+ /// steps in CSS 2.1 § E.2.
+ ///
+ /// This must be called before `draw_into_context()` is for correct results.
+ pub fn flatten(self, resulting_level: StackingLevel) -> DisplayList {
+ // TODO(pcwalton): Sort positioned children according to z-index.
+
+ let mut result = DisplayList::new();
+ let StackingContext {
+ background_and_borders,
+ block_backgrounds_and_borders,
+ floats,
+ content,
+ positioned_descendants: mut positioned_descendants
+ } = StackingContext::new(self);
+
+ // Steps 1 and 2: Borders and background for the root.
+ result.push_all_move(background_and_borders);
+
+ // TODO(pcwalton): Sort positioned children according to z-index.
+
+ // Step 3: Positioned descendants with negative z-indices.
+ for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() {
+ if *z_index < 0 {
+ result.push_all_move(mem::replace(list, DisplayList::new()))
+ }
+ }
+
+ // Step 4: Block backgrounds and borders.
+ result.push_all_move(block_backgrounds_and_borders);
+
+ // Step 5: Floats.
+ result.push_all_move(floats);
+
+ // TODO(pcwalton): Step 6: Inlines that generate stacking contexts.
+
+ // Step 7: Content.
+ result.push_all_move(content);
+
+ // Steps 8 and 9: Positioned descendants with nonnegative z-indices.
+ for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() {
+ if *z_index >= 0 {
+ result.push_all_move(mem::replace(list, DisplayList::new()))
+ }
+ }
+
+ // TODO(pcwalton): Step 10: Outlines.
+
+ result.set_stacking_level(resulting_level);
+ result
+ }
+
+ /// Sets the stacking level for this display list and all its subitems.
+ fn set_stacking_level(&mut self, new_level: StackingLevel) {
+ for item in self.list.mut_iter() {
+ item.mut_base().level = new_level;
+ match item.mut_sublist() {
+ None => {}
+ Some(sublist) => sublist.set_stacking_level(new_level),
+ }
+ }
+ }
+}
+
+/// One drawing command in the list.
+#[deriving(Clone)]
+pub enum DisplayItem {
+ SolidColorDisplayItemClass(Box<SolidColorDisplayItem>),
+ TextDisplayItemClass(Box<TextDisplayItem>),
+ ImageDisplayItemClass(Box<ImageDisplayItem>),
+ BorderDisplayItemClass(Box<BorderDisplayItem>),
+ LineDisplayItemClass(Box<LineDisplayItem>),
+ ClipDisplayItemClass(Box<ClipDisplayItem>),
+
+ /// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and
+ /// `ContentBoxesQuery` can be answered.
+ ///
+ /// FIXME(pcwalton): This is really bogus. Those queries should not consult the display list
+ /// but should instead consult the flow/box tree.
+ PseudoDisplayItemClass(Box<BaseDisplayItem>),
+}
+
+/// Information common to all display items.
+#[deriving(Clone)]
+pub struct BaseDisplayItem {
+ /// The boundaries of the display item.
+ ///
+ /// TODO: Which coordinate system should this use?
+ pub bounds: Rect<Au>,
+
+ /// The originating DOM node.
+ pub node: OpaqueNode,
+
+ /// The stacking level in which this display item lives.
+ pub level: StackingLevel,
+}
+
+impl BaseDisplayItem {
+ pub fn new(bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel) -> BaseDisplayItem {
+ BaseDisplayItem {
+ bounds: bounds,
+ node: node,
+ level: level,
+ }
+ }
+}
+
+/// Renders a solid color.
+#[deriving(Clone)]
+pub struct SolidColorDisplayItem {
+ pub base: BaseDisplayItem,
+ pub color: Color,
+}
+
+/// Renders text.
+#[deriving(Clone)]
+pub struct TextDisplayItem {
+ /// Fields common to all display items.
+ pub base: BaseDisplayItem,
+
+ /// The text run.
+ pub text_run: Arc<Box<TextRun>>,
+
+ /// The range of text within the text run.
+ pub range: Range<CharIndex>,
+
+ /// The color of the text.
+ pub text_color: Color,
+
+ pub baseline_origin: Point2D<Au>,
+ pub orientation: TextOrientation,
+}
+
+#[deriving(Clone, Eq, PartialEq)]
+pub enum TextOrientation {
+ Upright,
+ SidewaysLeft,
+ SidewaysRight,
+}
+
+/// Renders an image.
+#[deriving(Clone)]
+pub struct ImageDisplayItem {
+ pub base: BaseDisplayItem,
+ pub image: Arc<Box<Image>>,
+
+ /// The dimensions to which the image display item should be stretched. If this is smaller than
+ /// the bounds of this display item, then the image will be repeated in the appropriate
+ /// direction to tile the entire bounds.
+ pub stretch_size: Size2D<Au>,
+}
+
+/// Renders a border.
+#[deriving(Clone)]
+pub struct BorderDisplayItem {
+ pub base: BaseDisplayItem,
+
+ /// The border widths
+ pub border: SideOffsets2D<Au>,
+
+ /// The border colors.
+ pub color: SideOffsets2D<Color>,
+
+ /// The border styles.
+ pub style: SideOffsets2D<border_style::T>
+}
+
+/// Renders a line segment.
+#[deriving(Clone)]
+pub struct LineDisplayItem {
+ pub base: BaseDisplayItem,
+
+ /// The line segment color.
+ pub color: Color,
+
+ /// The line segment style.
+ pub style: border_style::T
+}
+
+/// Clips a list of child display items to this display item's boundaries.
+#[deriving(Clone)]
+pub struct ClipDisplayItem {
+ /// The base information.
+ pub base: BaseDisplayItem,
+
+ /// The child nodes.
+ pub children: DisplayList,
+}
+
+impl ClipDisplayItem {
+ pub fn new(base: BaseDisplayItem, children: DisplayList) -> ClipDisplayItem {
+ ClipDisplayItem {
+ base: base,
+ children: children,
+ }
+ }
+}
+
+pub enum DisplayItemIterator<'a> {
+ EmptyDisplayItemIterator,
+ ParentDisplayItemIterator(dlist::Items<'a,DisplayItem>),
+}
+
+impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a DisplayItem> {
+ match *self {
+ EmptyDisplayItemIterator => None,
+ ParentDisplayItemIterator(ref mut subiterator) => subiterator.next(),
+ }
+ }
+}
+
+impl DisplayItem {
+ /// Renders this display item into the given render context.
+ fn draw_into_context(&self, render_context: &mut RenderContext,
+ current_transform: &Matrix2D<AzFloat>) {
+ // This should have been flattened to the content stacking level first.
+ assert!(self.base().level == ContentStackingLevel);
+
+ match *self {
+ SolidColorDisplayItemClass(ref solid_color) => {
+ render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
+ }
+
+ ClipDisplayItemClass(ref clip) => {
+ render_context.draw_push_clip(&clip.base.bounds);
+ for item in clip.children.iter() {
+ (*item).draw_into_context(render_context, current_transform);
+ }
+ render_context.draw_pop_clip();
+ }
+
+ TextDisplayItemClass(ref text) => {
+ debug!("Drawing text at {}.", text.base.bounds);
+
+ // Optimization: Don’t set a transform matrix for upright text,
+ // and pass a strart point to `draw_text_into_context`.
+ // For sideways text, it’s easier to do the rotation such that its center
+ // (the baseline’s start point) is at (0, 0) coordinates.
+ let baseline_origin = match text.orientation {
+ Upright => text.baseline_origin,
+ SidewaysLeft => {
+ let x = text.baseline_origin.x.to_nearest_px() as AzFloat;
+ let y = text.baseline_origin.y.to_nearest_px() as AzFloat;
+ render_context.draw_target.set_transform(&current_transform.mul(
+ &Matrix2D::new(
+ 0., -1.,
+ 1., 0.,
+ x, y
+ )
+ ));
+ Zero::zero()
+ },
+ SidewaysRight => {
+ let x = text.baseline_origin.x.to_nearest_px() as AzFloat;
+ let y = text.baseline_origin.y.to_nearest_px() as AzFloat;
+ render_context.draw_target.set_transform(&current_transform.mul(
+ &Matrix2D::new(
+ 0., 1.,
+ -1., 0.,
+ x, y
+ )
+ ));
+ Zero::zero()
+ }
+ };
+
+ render_context.font_ctx.get_render_font_from_template(
+ &text.text_run.font_template,
+ text.text_run.pt_size,
+ render_context.opts.render_backend
+ ).borrow().draw_text_into_context(
+ render_context,
+ &*text.text_run,
+ &text.range,
+ baseline_origin,
+ text.text_color,
+ render_context.opts.enable_text_antialiasing
+ );
+
+ // Undo the transform, only when we did one.
+ if text.orientation != Upright {
+ render_context.draw_target.set_transform(current_transform)
+ }
+ }
+
+ ImageDisplayItemClass(ref image_item) => {
+ debug!("Drawing image at {:?}.", image_item.base.bounds);
+
+ let mut y_offset = Au(0);
+ while y_offset < image_item.base.bounds.size.height {
+ let mut x_offset = Au(0);
+ while x_offset < image_item.base.bounds.size.width {
+ let mut bounds = image_item.base.bounds;
+ bounds.origin.x = bounds.origin.x + x_offset;
+ bounds.origin.y = bounds.origin.y + y_offset;
+ bounds.size = image_item.stretch_size;
+
+ render_context.draw_image(bounds, image_item.image.clone());
+
+ x_offset = x_offset + image_item.stretch_size.width;
+ }
+
+ y_offset = y_offset + image_item.stretch_size.height;
+ }
+ }
+
+ BorderDisplayItemClass(ref border) => {
+ render_context.draw_border(&border.base.bounds,
+ border.border,
+ border.color,
+ border.style)
+ }
+
+ LineDisplayItemClass(ref line) => {
+ render_context.draw_line(&line.base.bounds,
+ line.color,
+ line.style)
+ }
+
+ PseudoDisplayItemClass(_) => {}
+ }
+ }
+
+ pub fn base<'a>(&'a self) -> &'a BaseDisplayItem {
+ match *self {
+ SolidColorDisplayItemClass(ref solid_color) => &solid_color.base,
+ TextDisplayItemClass(ref text) => &text.base,
+ ImageDisplayItemClass(ref image_item) => &image_item.base,
+ BorderDisplayItemClass(ref border) => &border.base,
+ LineDisplayItemClass(ref line) => &line.base,
+ ClipDisplayItemClass(ref clip) => &clip.base,
+ PseudoDisplayItemClass(ref base) => &**base,
+ }
+ }
+
+ pub fn mut_base<'a>(&'a mut self) -> &'a mut BaseDisplayItem {
+ match *self {
+ SolidColorDisplayItemClass(ref mut solid_color) => &mut solid_color.base,
+ TextDisplayItemClass(ref mut text) => &mut text.base,
+ ImageDisplayItemClass(ref mut image_item) => &mut image_item.base,
+ BorderDisplayItemClass(ref mut border) => &mut border.base,
+ LineDisplayItemClass(ref mut line) => &mut line.base,
+ ClipDisplayItemClass(ref mut clip) => &mut clip.base,
+ PseudoDisplayItemClass(ref mut base) => &mut **base,
+ }
+ }
+
+ pub fn bounds(&self) -> Rect<Au> {
+ self.base().bounds
+ }
+
+ pub fn children<'a>(&'a self) -> DisplayItemIterator<'a> {
+ match *self {
+ ClipDisplayItemClass(ref clip) => ParentDisplayItemIterator(clip.children.list.iter()),
+ SolidColorDisplayItemClass(..) |
+ TextDisplayItemClass(..) |
+ ImageDisplayItemClass(..) |
+ BorderDisplayItemClass(..) |
+ LineDisplayItemClass(..) |
+ PseudoDisplayItemClass(..) => EmptyDisplayItemIterator,
+ }
+ }
+
+ /// Returns a mutable reference to the sublist contained within this display list item, if any.
+ fn mut_sublist<'a>(&'a mut self) -> Option<&'a mut DisplayList> {
+ match *self {
+ ClipDisplayItemClass(ref mut clip) => Some(&mut clip.children),
+ SolidColorDisplayItemClass(..) |
+ TextDisplayItemClass(..) |
+ ImageDisplayItemClass(..) |
+ BorderDisplayItemClass(..) |
+ LineDisplayItemClass(..) |
+ PseudoDisplayItemClass(..) => None,
+ }
+ }
+
+ pub fn debug_with_level(&self, level: uint) {
+ let mut indent = String::new();
+ for _ in range(0, level) {
+ indent.push_str("| ")
+ }
+ debug!("{}+ {}", indent, self);
+ for child in self.children() {
+ child.debug_with_level(level + 1);
+ }
+ }
+}
+
+impl fmt::Show for DisplayItem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} @ {} ({:x})",
+ match *self {
+ SolidColorDisplayItemClass(_) => "SolidColor",
+ TextDisplayItemClass(_) => "Text",
+ ImageDisplayItemClass(_) => "Image",
+ BorderDisplayItemClass(_) => "Border",
+ LineDisplayItemClass(_) => "Line",
+ ClipDisplayItemClass(_) => "Clip",
+ PseudoDisplayItemClass(_) => "Pseudo",
+ },
+ self.base().bounds,
+ self.base().node.id(),
+ )
+ }
+}
diff --git a/components/gfx/display_list/optimizer.rs b/components/gfx/display_list/optimizer.rs
new file mode 100644
index 00000000000..5e32238704c
--- /dev/null
+++ b/components/gfx/display_list/optimizer.rs
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass, DisplayItem};
+use display_list::{DisplayList, ImageDisplayItemClass, LineDisplayItemClass};
+use display_list::{PseudoDisplayItemClass, SolidColorDisplayItemClass, TextDisplayItemClass};
+
+use collections::dlist::DList;
+use geom::rect::Rect;
+use servo_util::geometry::Au;
+use sync::Arc;
+
+pub struct DisplayListOptimizer {
+ display_list: Arc<DisplayList>,
+ /// The visible rect in page coordinates.
+ visible_rect: Rect<Au>,
+}
+
+impl DisplayListOptimizer {
+ /// `visible_rect` specifies the visible rect in page coordinates.
+ pub fn new(display_list: Arc<DisplayList>, visible_rect: Rect<Au>) -> DisplayListOptimizer {
+ DisplayListOptimizer {
+ display_list: display_list,
+ visible_rect: visible_rect,
+ }
+ }
+
+ pub fn optimize(self) -> DisplayList {
+ self.process_display_list(&*self.display_list)
+ }
+
+ fn process_display_list(&self, display_list: &DisplayList) -> DisplayList {
+ let mut result = DList::new();
+ for item in display_list.iter() {
+ match self.process_display_item(item) {
+ None => {}
+ Some(display_item) => result.push(display_item),
+ }
+ }
+ DisplayList {
+ list: result,
+ }
+ }
+
+ fn process_display_item(&self, display_item: &DisplayItem) -> Option<DisplayItem> {
+ // Eliminate display items outside the visible region.
+ if !self.visible_rect.intersects(&display_item.base().bounds) {
+ return None
+ }
+
+ // Recur.
+ match *display_item {
+ ClipDisplayItemClass(ref clip) => {
+ let new_children = self.process_display_list(&clip.children);
+ if new_children.is_empty() {
+ return None
+ }
+ Some(ClipDisplayItemClass(box ClipDisplayItem {
+ base: clip.base.clone(),
+ children: new_children,
+ }))
+ }
+
+ BorderDisplayItemClass(_) | ImageDisplayItemClass(_) | LineDisplayItemClass(_) |
+ PseudoDisplayItemClass(_) | SolidColorDisplayItemClass(_) |
+ TextDisplayItemClass(_) => {
+ Some((*display_item).clone())
+ }
+ }
+ }
+}
+
diff --git a/components/gfx/font.rs b/components/gfx/font.rs
new file mode 100644
index 00000000000..74930da0b4a
--- /dev/null
+++ b/components/gfx/font.rs
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use geom::{Point2D, Rect, Size2D};
+use std::mem;
+use std::string;
+use std::rc::Rc;
+use std::cell::RefCell;
+use servo_util::cache::{Cache, HashCache};
+use style::computed_values::{font_weight, font_style};
+use sync::Arc;
+
+use servo_util::geometry::Au;
+use platform::font_context::FontContextHandle;
+use platform::font::{FontHandle, FontTable};
+use text::glyph::{GlyphStore, GlyphId};
+use text::shaping::ShaperMethods;
+use text::{Shaper, TextRun};
+use font_template::FontTemplateDescriptor;
+use platform::font_template::FontTemplateData;
+
+// FontHandle encapsulates access to the platform's font API,
+// e.g. quartz, FreeType. It provides access to metrics and tables
+// needed by the text shaper as well as access to the underlying font
+// resources needed by the graphics layer to draw glyphs.
+
+pub trait FontHandleMethods {
+ fn new_from_template(fctx: &FontContextHandle, template: Arc<FontTemplateData>, pt_size: Option<f64>)
+ -> Result<Self,()>;
+ fn get_template(&self) -> Arc<FontTemplateData>;
+ fn family_name(&self) -> String;
+ fn face_name(&self) -> String;
+ fn is_italic(&self) -> bool;
+ fn boldness(&self) -> font_weight::T;
+
+ fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
+ fn glyph_h_advance(&self, GlyphId) -> Option<FractionalPixel>;
+ fn glyph_h_kerning(&self, GlyphId, GlyphId) -> FractionalPixel;
+ fn get_metrics(&self) -> FontMetrics;
+ fn get_table_for_tag(&self, FontTableTag) -> Option<FontTable>;
+}
+
+// Used to abstract over the shaper's choice of fixed int representation.
+pub type FractionalPixel = f64;
+
+pub type FontTableTag = u32;
+
+pub trait FontTableTagConversions {
+ fn tag_to_str(&self) -> String;
+}
+
+impl FontTableTagConversions for FontTableTag {
+ fn tag_to_str(&self) -> String {
+ unsafe {
+ let reversed = string::raw::from_buf_len(mem::transmute(self), 4);
+ return String::from_chars([reversed.as_slice().char_at(3),
+ reversed.as_slice().char_at(2),
+ reversed.as_slice().char_at(1),
+ reversed.as_slice().char_at(0)]);
+ }
+ }
+}
+
+pub trait FontTableMethods {
+ fn with_buffer(&self, |*const u8, uint|);
+}
+
+#[deriving(Clone)]
+pub struct FontMetrics {
+ pub underline_size: Au,
+ pub underline_offset: Au,
+ pub strikeout_size: Au,
+ pub strikeout_offset: Au,
+ pub leading: Au,
+ pub x_height: Au,
+ pub em_size: Au,
+ pub ascent: Au,
+ pub descent: Au,
+ pub max_advance: Au,
+ pub line_gap: Au,
+}
+
+// TODO(Issue #179): eventually this will be split into the specified
+// and used font styles. specified contains uninterpreted CSS font
+// property values, while 'used' is attached to gfx::Font to descript
+// the instance's properties.
+//
+// For now, the cases are differentiated with a typedef
+#[deriving(Clone, PartialEq)]
+pub struct FontStyle {
+ pub pt_size: f64,
+ pub weight: font_weight::T,
+ pub style: font_style::T,
+ pub families: Vec<String>,
+ // TODO(Issue #198): font-stretch, text-decoration, font-variant, size-adjust
+}
+
+pub type SpecifiedFontStyle = FontStyle;
+pub type UsedFontStyle = FontStyle;
+
+pub struct Font {
+ pub handle: FontHandle,
+ pub metrics: FontMetrics,
+ pub descriptor: FontTemplateDescriptor,
+ pub pt_size: f64,
+ pub shaper: Option<Shaper>,
+ pub shape_cache: HashCache<String, Arc<GlyphStore>>,
+ pub glyph_advance_cache: HashCache<u32, FractionalPixel>,
+}
+
+impl Font {
+ pub fn shape_text(&mut self, text: String, is_whitespace: bool) -> Arc<GlyphStore> {
+ self.make_shaper();
+ let shaper = &self.shaper;
+ self.shape_cache.find_or_create(&text, |txt| {
+ let mut glyphs = GlyphStore::new(text.as_slice().char_len() as int, is_whitespace);
+ shaper.get_ref().shape_text(txt.as_slice(), &mut glyphs);
+ Arc::new(glyphs)
+ })
+ }
+
+ fn make_shaper<'a>(&'a mut self) -> &'a Shaper {
+ // fast path: already created a shaper
+ match self.shaper {
+ Some(ref shaper) => {
+ let s: &'a Shaper = shaper;
+ return s;
+ },
+ None => {}
+ }
+
+ let shaper = Shaper::new(self);
+ self.shaper = Some(shaper);
+ self.shaper.get_ref()
+ }
+
+ pub fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
+ let result = self.handle.get_table_for_tag(tag);
+ let status = if result.is_some() { "Found" } else { "Didn't find" };
+
+ debug!("{:s} font table[{:s}] with family={}, face={}",
+ status, tag.tag_to_str(),
+ self.handle.family_name(), self.handle.face_name());
+
+ return result;
+ }
+
+ pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
+ self.handle.glyph_index(codepoint)
+ }
+
+ pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
+ self.handle.glyph_h_kerning(first_glyph, second_glyph)
+ }
+
+ pub fn glyph_h_advance(&mut self, glyph: GlyphId) -> FractionalPixel {
+ let handle = &self.handle;
+ self.glyph_advance_cache.find_or_create(&glyph, |glyph| {
+ match handle.glyph_h_advance(*glyph) {
+ Some(adv) => adv,
+ None => 10f64 as FractionalPixel // FIXME: Need fallback strategy
+ }
+ })
+ }
+}
+
+pub struct FontGroup {
+ pub fonts: Vec<Rc<RefCell<Font>>>,
+}
+
+impl FontGroup {
+ pub fn new(fonts: Vec<Rc<RefCell<Font>>>) -> FontGroup {
+ FontGroup {
+ fonts: fonts
+ }
+ }
+
+ pub fn create_textrun(&self, text: String) -> TextRun {
+ assert!(self.fonts.len() > 0);
+
+ // TODO(Issue #177): Actually fall back through the FontGroup when a font is unsuitable.
+ TextRun::new(&mut *self.fonts[0].borrow_mut(), text.clone())
+ }
+}
+
+pub struct RunMetrics {
+ // may be negative due to negative width (i.e., kerning of '.' in 'P.T.')
+ pub advance_width: Au,
+ pub ascent: Au, // nonzero
+ pub descent: Au, // nonzero
+ // this bounding box is relative to the left origin baseline.
+ // so, bounding_box.position.y = -ascent
+ pub bounding_box: Rect<Au>
+}
+
+impl RunMetrics {
+ pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
+ let bounds = Rect(Point2D(Au(0), -ascent),
+ Size2D(advance, ascent + descent));
+
+ // TODO(Issue #125): support loose and tight bounding boxes; using the
+ // ascent+descent and advance is sometimes too generous and
+ // looking at actual glyph extents can yield a tighter box.
+
+ RunMetrics {
+ advance_width: advance,
+ bounding_box: bounds,
+ ascent: ascent,
+ descent: descent,
+ }
+ }
+}
diff --git a/components/gfx/font_cache_task.rs b/components/gfx/font_cache_task.rs
new file mode 100644
index 00000000000..1b1ff6227cb
--- /dev/null
+++ b/components/gfx/font_cache_task.rs
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use platform::font_list::get_available_families;
+use platform::font_list::get_variations_for_family;
+use platform::font_list::get_last_resort_font_families;
+use platform::font_context::FontContextHandle;
+
+use std::collections::HashMap;
+use sync::Arc;
+use font_template::{FontTemplate, FontTemplateDescriptor};
+use platform::font_template::FontTemplateData;
+use servo_net::resource_task::{ResourceTask, load_whole_resource};
+use url::Url;
+
+/// A list of font templates that make up a given font family.
+struct FontFamily {
+ templates: Vec<FontTemplate>,
+}
+
+impl FontFamily {
+ fn new() -> FontFamily {
+ FontFamily {
+ templates: vec!(),
+ }
+ }
+
+ /// Find a font in this family that matches a given desriptor.
+ fn find_font_for_style<'a>(&'a mut self, desc: &FontTemplateDescriptor, fctx: &FontContextHandle)
+ -> Option<Arc<FontTemplateData>> {
+ // TODO(Issue #189): optimize lookup for
+ // regular/bold/italic/bolditalic with fixed offsets and a
+ // static decision table for fallback between these values.
+
+ // TODO(Issue #190): if not in the fast path above, do
+ // expensive matching of weights, etc.
+ for template in self.templates.mut_iter() {
+ let maybe_template = template.get_if_matches(fctx, desc);
+ if maybe_template.is_some() {
+ return maybe_template;
+ }
+ }
+
+ // If a request is made for a font family that exists,
+ // pick the first valid font in the family if we failed
+ // to find an exact match for the descriptor.
+ for template in self.templates.mut_iter() {
+ let maybe_template = template.get();
+ if maybe_template.is_some() {
+ return maybe_template;
+ }
+ }
+
+ None
+ }
+
+ fn add_template(&mut self, identifier: &str, maybe_data: Option<Vec<u8>>) {
+ for template in self.templates.iter() {
+ if template.identifier() == identifier {
+ return;
+ }
+ }
+
+ let template = FontTemplate::new(identifier, maybe_data);
+ self.templates.push(template);
+ }
+}
+
+/// Commands that the FontContext sends to the font cache task.
+pub enum Command {
+ GetFontTemplate(String, FontTemplateDescriptor, Sender<Reply>),
+ AddWebFont(String, Url, Sender<()>),
+ Exit(Sender<()>),
+}
+
+/// Reply messages sent from the font cache task to the FontContext caller.
+pub enum Reply {
+ GetFontTemplateReply(Arc<FontTemplateData>),
+}
+
+/// The font cache task itself. It maintains a list of reference counted
+/// font templates that are currently in use.
+struct FontCache {
+ port: Receiver<Command>,
+ generic_fonts: HashMap<String, String>,
+ local_families: HashMap<String, FontFamily>,
+ web_families: HashMap<String, FontFamily>,
+ font_context: FontContextHandle,
+ resource_task: ResourceTask,
+}
+
+impl FontCache {
+ fn run(&mut self) {
+ loop {
+ let msg = self.port.recv();
+
+ match msg {
+ GetFontTemplate(family, descriptor, result) => {
+ let maybe_font_template = self.get_font_template(&family, &descriptor);
+ let font_template = match maybe_font_template {
+ Some(font_template) => font_template,
+ None => self.get_last_resort_template(&descriptor),
+ };
+
+ result.send(GetFontTemplateReply(font_template));
+ }
+ AddWebFont(family_name, url, result) => {
+ let maybe_resource = load_whole_resource(&self.resource_task, url.clone());
+ match maybe_resource {
+ Ok((_, bytes)) => {
+ if !self.web_families.contains_key(&family_name) {
+ let family = FontFamily::new();
+ self.web_families.insert(family_name.clone(), family);
+ }
+ let family = self.web_families.get_mut(&family_name);
+ family.add_template(format!("{}", url).as_slice(), Some(bytes));
+ },
+ Err(msg) => {
+ fail!("{}: url={}", msg, url);
+ }
+ }
+ result.send(());
+ }
+ Exit(result) => {
+ result.send(());
+ break;
+ }
+ }
+ }
+ }
+
+ fn refresh_local_families(&mut self) {
+ self.local_families.clear();
+ get_available_families(|family_name| {
+ if !self.local_families.contains_key(&family_name) {
+ let family = FontFamily::new();
+ self.local_families.insert(family_name, family);
+ }
+ });
+ }
+
+ fn transform_family(&self, family: &String) -> String {
+ match self.generic_fonts.find(family) {
+ None => family.to_string(),
+ Some(mapped_family) => (*mapped_family).clone()
+ }
+ }
+
+ fn find_font_in_local_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
+ -> Option<Arc<FontTemplateData>> {
+ // TODO(Issue #188): look up localized font family names if canonical name not found
+ // look up canonical name
+ if self.local_families.contains_key(family_name) {
+ debug!("FontList: Found font family with name={:s}", family_name.to_string());
+ let s = self.local_families.get_mut(family_name);
+
+ if s.templates.len() == 0 {
+ get_variations_for_family(family_name.as_slice(), |path| {
+ s.add_template(path.as_slice(), None);
+ });
+ }
+
+ // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.
+ // if such family exists, try to match style to a font
+ let result = s.find_font_for_style(desc, &self.font_context);
+ if result.is_some() {
+ return result;
+ }
+
+ None
+ } else {
+ debug!("FontList: Couldn't find font family with name={:s}", family_name.to_string());
+ None
+ }
+ }
+
+ fn find_font_in_web_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor)
+ -> Option<Arc<FontTemplateData>> {
+ if self.web_families.contains_key(family_name) {
+ let family = self.web_families.get_mut(family_name);
+ let maybe_font = family.find_font_for_style(desc, &self.font_context);
+ maybe_font
+ } else {
+ None
+ }
+ }
+
+ fn get_font_template(&mut self, family: &String, desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
+ let transformed_family_name = self.transform_family(family);
+ let mut maybe_template = self.find_font_in_web_family(&transformed_family_name, desc);
+ if maybe_template.is_none() {
+ maybe_template = self.find_font_in_local_family(&transformed_family_name, desc);
+ }
+ maybe_template
+ }
+
+ fn get_last_resort_template(&mut self, desc: &FontTemplateDescriptor) -> Arc<FontTemplateData> {
+ let last_resort = get_last_resort_font_families();
+
+ for family in last_resort.iter() {
+ let maybe_font_in_family = self.find_font_in_local_family(family, desc);
+ if maybe_font_in_family.is_some() {
+ return maybe_font_in_family.unwrap();
+ }
+ }
+
+ fail!("Unable to find any fonts that match (do you have fallback fonts installed?)");
+ }
+}
+
+/// The public interface to the font cache task, used exclusively by
+/// the per-thread/task FontContext structures.
+#[deriving(Clone)]
+pub struct FontCacheTask {
+ chan: Sender<Command>,
+}
+
+impl FontCacheTask {
+ pub fn new(resource_task: ResourceTask) -> FontCacheTask {
+ let (chan, port) = channel();
+
+ spawn(proc() {
+ // TODO: Allow users to specify these.
+ let mut generic_fonts = HashMap::with_capacity(5);
+ generic_fonts.insert("serif".to_string(), "Times New Roman".to_string());
+ generic_fonts.insert("sans-serif".to_string(), "Arial".to_string());
+ generic_fonts.insert("cursive".to_string(), "Apple Chancery".to_string());
+ generic_fonts.insert("fantasy".to_string(), "Papyrus".to_string());
+ generic_fonts.insert("monospace".to_string(), "Menlo".to_string());
+
+ let mut cache = FontCache {
+ port: port,
+ generic_fonts: generic_fonts,
+ local_families: HashMap::new(),
+ web_families: HashMap::new(),
+ font_context: FontContextHandle::new(),
+ resource_task: resource_task,
+ };
+
+ cache.refresh_local_families();
+ cache.run();
+ });
+
+ FontCacheTask {
+ chan: chan,
+ }
+ }
+
+ pub fn get_font_template(&self, family: String, desc: FontTemplateDescriptor)
+ -> Arc<FontTemplateData> {
+
+ let (response_chan, response_port) = channel();
+ self.chan.send(GetFontTemplate(family, desc, response_chan));
+
+ let reply = response_port.recv();
+
+ match reply {
+ GetFontTemplateReply(data) => {
+ data
+ }
+ }
+ }
+
+ pub fn add_web_font(&self, family: String, url: Url) {
+ let (response_chan, response_port) = channel();
+ self.chan.send(AddWebFont(family, url, response_chan));
+ response_port.recv();
+ }
+
+ pub fn exit(&self) {
+ let (response_chan, response_port) = channel();
+ self.chan.send(Exit(response_chan));
+ response_port.recv();
+ }
+}
diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs
new file mode 100644
index 00000000000..0a1ef69e0ce
--- /dev/null
+++ b/components/gfx/font_context.rs
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use font::{Font, FontGroup};
+use font::SpecifiedFontStyle;
+use platform::font_context::FontContextHandle;
+use style::computed_values::font_style;
+
+use font_cache_task::FontCacheTask;
+use font_template::FontTemplateDescriptor;
+use platform::font_template::FontTemplateData;
+use font::FontHandleMethods;
+use platform::font::FontHandle;
+use servo_util::cache::HashCache;
+
+use std::rc::{Rc, Weak};
+use std::cell::RefCell;
+use sync::Arc;
+
+use azure::AzFloat;
+use azure::azure_hl::BackendType;
+use azure::scaled_font::ScaledFont;
+
+#[cfg(target_os="linux")]
+#[cfg(target_os="android")]
+use azure::scaled_font::FontData;
+
+#[cfg(target_os="linux")]
+#[cfg(target_os="android")]
+fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont {
+ ScaledFont::new(backend, FontData(&template.bytes), pt_size as AzFloat)
+}
+
+#[cfg(target_os="macos")]
+fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont {
+ let cgfont = template.ctfont.get_ref().copy_to_CGFont();
+ ScaledFont::new(backend, &cgfont, pt_size as AzFloat)
+}
+
+/// A cached azure font (per render task) that
+/// can be shared by multiple text runs.
+struct RenderFontCacheEntry {
+ pt_size: f64,
+ identifier: String,
+ font: Rc<RefCell<ScaledFont>>,
+}
+
+/// The FontContext represents the per-thread/task state necessary for
+/// working with fonts. It is the public API used by the layout and
+/// render code. It talks directly to the font cache task where
+/// required.
+pub struct FontContext {
+ platform_handle: FontContextHandle,
+ font_cache_task: FontCacheTask,
+
+ /// Weak reference as the layout FontContext is persistent.
+ layout_font_cache: Vec<Weak<RefCell<Font>>>,
+
+ /// Strong reference as the render FontContext is (for now) recycled
+ /// per frame. TODO: Make this weak when incremental redraw is done.
+ render_font_cache: Vec<RenderFontCacheEntry>,
+}
+
+impl FontContext {
+ pub fn new(font_cache_task: FontCacheTask) -> FontContext {
+ let handle = FontContextHandle::new();
+ FontContext {
+ platform_handle: handle,
+ font_cache_task: font_cache_task,
+ layout_font_cache: vec!(),
+ render_font_cache: vec!(),
+ }
+ }
+
+ /// Create a font for use in layout calculations.
+ fn create_layout_font(&self, template: Arc<FontTemplateData>,
+ descriptor: FontTemplateDescriptor, pt_size: f64) -> Font {
+
+ let handle: FontHandle = FontHandleMethods::new_from_template(&self.platform_handle, template, Some(pt_size)).unwrap();
+ let metrics = handle.get_metrics();
+
+ Font {
+ handle: handle,
+ shaper: None,
+ descriptor: descriptor,
+ pt_size: pt_size,
+ metrics: metrics,
+ shape_cache: HashCache::new(),
+ glyph_advance_cache: HashCache::new(),
+ }
+ }
+
+ /// Create a group of fonts for use in layout calculations. May return
+ /// a cached font if this font instance has already been used by
+ /// this context.
+ pub fn get_layout_font_group_for_style(&mut self, style: &SpecifiedFontStyle) -> FontGroup {
+ // Remove all weak pointers that have been dropped.
+ self.layout_font_cache.retain(|maybe_font| {
+ maybe_font.upgrade().is_some()
+ });
+
+ let mut fonts: Vec<Rc<RefCell<Font>>> = vec!();
+
+ for family in style.families.iter() {
+ let desc = FontTemplateDescriptor::new(style.weight, style.style == font_style::italic);
+
+ // GWTODO: Check on real pages if this is faster as Vec() or HashMap().
+ let mut cache_hit = false;
+ for maybe_cached_font in self.layout_font_cache.iter() {
+ let cached_font = maybe_cached_font.upgrade().unwrap();
+ if cached_font.borrow().descriptor == desc {
+ fonts.push(cached_font.clone());
+ cache_hit = true;
+ break;
+ }
+ }
+
+ if !cache_hit {
+ let font_template = self.font_cache_task.get_font_template(family.clone(), desc.clone());
+ let layout_font = Rc::new(RefCell::new(self.create_layout_font(font_template, desc.clone(), style.pt_size)));
+ self.layout_font_cache.push(layout_font.downgrade());
+ fonts.push(layout_font);
+ }
+ }
+
+ FontGroup::new(fonts)
+ }
+
+ /// Create a render font for use with azure. May return a cached
+ /// reference if already used by this font context.
+ pub fn get_render_font_from_template(&mut self, template: &Arc<FontTemplateData>, pt_size: f64, backend: BackendType) -> Rc<RefCell<ScaledFont>> {
+ for cached_font in self.render_font_cache.iter() {
+ if cached_font.pt_size == pt_size &&
+ cached_font.identifier == template.identifier {
+ return cached_font.font.clone();
+ }
+ }
+
+ let render_font = Rc::new(RefCell::new(create_scaled_font(backend, template, pt_size)));
+ self.render_font_cache.push(RenderFontCacheEntry{
+ font: render_font.clone(),
+ pt_size: pt_size,
+ identifier: template.identifier.clone(),
+ });
+ render_font
+ }
+}
diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs
new file mode 100644
index 00000000000..3f4916b69c5
--- /dev/null
+++ b/components/gfx/font_template.rs
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use style::computed_values::font_weight;
+use platform::font_context::FontContextHandle;
+use platform::font::FontHandle;
+use platform::font_template::FontTemplateData;
+
+use sync::{Arc, Weak};
+use font::FontHandleMethods;
+
+/// Describes how to select a font from a given family.
+/// This is very basic at the moment and needs to be
+/// expanded or refactored when we support more of the
+/// font styling parameters.
+#[deriving(Clone)]
+pub struct FontTemplateDescriptor {
+ pub weight: font_weight::T,
+ pub italic: bool,
+}
+
+impl FontTemplateDescriptor {
+ pub fn new(weight: font_weight::T, italic: bool) -> FontTemplateDescriptor {
+ FontTemplateDescriptor {
+ weight: weight,
+ italic: italic,
+ }
+ }
+}
+
+impl PartialEq for FontTemplateDescriptor {
+ fn eq(&self, other: &FontTemplateDescriptor) -> bool {
+ self.weight.is_bold() == other.weight.is_bold() &&
+ self.italic == other.italic
+ }
+}
+
+/// This describes all the information needed to create
+/// font instance handles. It contains a unique
+/// FontTemplateData structure that is platform specific.
+pub struct FontTemplate {
+ identifier: String,
+ descriptor: Option<FontTemplateDescriptor>,
+ weak_ref: Option<Weak<FontTemplateData>>,
+ strong_ref: Option<Arc<FontTemplateData>>, // GWTODO: Add code path to unset the strong_ref for web fonts!
+ is_valid: bool,
+}
+
+/// Holds all of the template information for a font that
+/// is common, regardless of the number of instances of
+/// this font handle per thread.
+impl FontTemplate {
+ pub fn new(identifier: &str, maybe_bytes: Option<Vec<u8>>) -> FontTemplate {
+ let maybe_data = match maybe_bytes {
+ Some(_) => Some(FontTemplateData::new(identifier, maybe_bytes)),
+ None => None,
+ };
+
+ let maybe_strong_ref = match maybe_data {
+ Some(data) => Some(Arc::new(data)),
+ None => None,
+ };
+
+ let maybe_weak_ref = match maybe_strong_ref {
+ Some(ref strong_ref) => Some(strong_ref.downgrade()),
+ None => None,
+ };
+
+ FontTemplate {
+ identifier: identifier.to_string(),
+ descriptor: None,
+ weak_ref: maybe_weak_ref,
+ strong_ref: maybe_strong_ref,
+ is_valid: true,
+ }
+ }
+
+ pub fn identifier<'a>(&'a self) -> &'a str {
+ self.identifier.as_slice()
+ }
+
+ /// Get the data for creating a font if it matches a given descriptor.
+ pub fn get_if_matches(&mut self, fctx: &FontContextHandle,
+ requested_desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> {
+ // The font template data can be unloaded when nothing is referencing
+ // it (via the Weak reference to the Arc above). However, if we have
+ // already loaded a font, store the style information about it separately,
+ // so that we can do font matching against it again in the future
+ // without having to reload the font (unless it is an actual match).
+ match self.descriptor {
+ Some(actual_desc) => {
+ if *requested_desc == actual_desc {
+ Some(self.get_data())
+ } else {
+ None
+ }
+ },
+ None => {
+ if self.is_valid {
+ let data = self.get_data();
+ let handle: Result<FontHandle, ()> = FontHandleMethods::new_from_template(fctx, data.clone(), None);
+ match handle {
+ Ok(handle) => {
+ let actual_desc = FontTemplateDescriptor::new(handle.boldness(),
+ handle.is_italic());
+ let desc_match = actual_desc == *requested_desc;
+
+ self.descriptor = Some(actual_desc);
+ self.is_valid = true;
+ if desc_match {
+ Some(data)
+ } else {
+ None
+ }
+ }
+ Err(()) => {
+ self.is_valid = false;
+ debug!("Unable to create a font from template {}", self.identifier);
+ None
+ }
+ }
+ } else {
+ None
+ }
+ }
+ }
+ }
+
+ /// Get the data for creating a font.
+ pub fn get(&mut self) -> Option<Arc<FontTemplateData>> {
+ match self.is_valid {
+ true => Some(self.get_data()),
+ false => None
+ }
+ }
+
+ /// Get the font template data. If any strong references still
+ /// exist, it will return a clone, otherwise it will load the
+ /// font data and store a weak reference to it internally.
+ pub fn get_data(&mut self) -> Arc<FontTemplateData> {
+ let maybe_data = match self.weak_ref {
+ Some(ref data) => data.upgrade(),
+ None => None,
+ };
+
+ match maybe_data {
+ Some(data) => data,
+ None => {
+ assert!(self.strong_ref.is_none());
+ let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice(), None));
+ self.weak_ref = Some(template_data.downgrade());
+ template_data
+ }
+ }
+ }
+}
diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs
new file mode 100644
index 00000000000..838b20f71f8
--- /dev/null
+++ b/components/gfx/lib.rs
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![feature(globs, macro_rules, phase, unsafe_destructor)]
+
+#![feature(phase)]
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+extern crate azure;
+extern crate collections;
+extern crate geom;
+extern crate layers;
+extern crate libc;
+extern crate native;
+extern crate rustrt;
+extern crate stb_image;
+extern crate png;
+extern crate serialize;
+#[phase(plugin)]
+extern crate servo_macros = "macros";
+extern crate servo_net = "net";
+#[phase(plugin, link)]
+extern crate servo_util = "util";
+extern crate servo_msg = "msg";
+extern crate style;
+extern crate sync;
+extern crate url;
+
+// Eventually we would like the shaper to be pluggable, as many operating systems have their own
+// shapers. For now, however, this is a hard dependency.
+extern crate harfbuzz;
+
+// Linux and Android-specific library dependencies
+#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate fontconfig;
+#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate freetype;
+
+// Mac OS-specific library dependencies
+#[cfg(target_os="macos")] extern crate core_foundation;
+#[cfg(target_os="macos")] extern crate core_graphics;
+#[cfg(target_os="macos")] extern crate core_text;
+
+pub use render_context::RenderContext;
+
+// Private rendering modules
+mod render_context;
+
+// Rendering
+pub mod color;
+#[path="display_list/mod.rs"]
+pub mod display_list;
+pub mod render_task;
+
+// Fonts
+pub mod font;
+pub mod font_context;
+pub mod font_cache_task;
+pub mod font_template;
+
+// Misc.
+mod buffer_map;
+
+// Platform-specific implementations.
+#[path="platform/mod.rs"]
+pub mod platform;
+
+// Text
+#[path = "text/mod.rs"]
+pub mod text;
+
diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs
new file mode 100644
index 00000000000..7e58b850e2b
--- /dev/null
+++ b/components/gfx/platform/freetype/font.rs
@@ -0,0 +1,297 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate freetype;
+
+use font::{FontHandleMethods, FontMetrics, FontTableMethods};
+use font::{FontTableTag, FractionalPixel};
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use platform::font_context::FontContextHandle;
+use text::glyph::GlyphId;
+use text::util::{float_to_fixed, fixed_to_float};
+use style::computed_values::font_weight;
+use platform::font_template::FontTemplateData;
+
+use freetype::freetype::{FT_Get_Char_Index, FT_Get_Postscript_Name};
+use freetype::freetype::{FT_Load_Glyph, FT_Set_Char_Size};
+use freetype::freetype::{FT_Get_Kerning, FT_Get_Sfnt_Table};
+use freetype::freetype::{FT_New_Memory_Face, FT_Done_Face};
+use freetype::freetype::{FTErrorMethods, FT_F26Dot6, FT_Face, FT_FaceRec};
+use freetype::freetype::{FT_GlyphSlot, FT_Library, FT_Long, FT_ULong};
+use freetype::freetype::{FT_KERNING_DEFAULT, FT_STYLE_FLAG_ITALIC, FT_STYLE_FLAG_BOLD};
+use freetype::freetype::{FT_SizeRec, FT_UInt, FT_Size_Metrics, struct_FT_Vector_};
+use freetype::freetype::{ft_sfnt_os2};
+use freetype::tt_os2::TT_OS2;
+
+use std::mem;
+use std::ptr;
+use std::string;
+
+use sync::Arc;
+
+fn float_to_fixed_ft(f: f64) -> i32 {
+ float_to_fixed(6, f)
+}
+
+fn fixed_to_float_ft(f: i32) -> f64 {
+ fixed_to_float(6, f)
+}
+
+pub struct FontTable;
+
+impl FontTableMethods for FontTable {
+ fn with_buffer(&self, _blk: |*const u8, uint|) {
+ fail!()
+ }
+}
+
+pub struct FontHandle {
+ // The font binary. This must stay valid for the lifetime of the font,
+ // if the font is created using FT_Memory_Face.
+ pub font_data: Arc<FontTemplateData>,
+ pub face: FT_Face,
+ pub handle: FontContextHandle
+}
+
+#[unsafe_destructor]
+impl Drop for FontHandle {
+ fn drop(&mut self) {
+ assert!(self.face.is_not_null());
+ unsafe {
+ if !FT_Done_Face(self.face).succeeded() {
+ fail!("FT_Done_Face failed");
+ }
+ }
+ }
+}
+
+impl FontHandleMethods for FontHandle {
+ fn new_from_template(fctx: &FontContextHandle,
+ template: Arc<FontTemplateData>,
+ pt_size: Option<f64>)
+ -> Result<FontHandle, ()> {
+ let ft_ctx: FT_Library = fctx.ctx.ctx;
+ if ft_ctx.is_null() { return Err(()); }
+
+ let bytes = &template.deref().bytes;
+ let face_result = create_face_from_buffer(ft_ctx, bytes.as_ptr(), bytes.len(), pt_size);
+
+ // TODO: this could be more simply written as result::chain
+ // and moving buf into the struct ctor, but cant' move out of
+ // captured binding.
+ return match face_result {
+ Ok(face) => {
+ let handle = FontHandle {
+ face: face,
+ font_data: template.clone(),
+ handle: fctx.clone()
+ };
+ Ok(handle)
+ }
+ Err(()) => Err(())
+ };
+
+ fn create_face_from_buffer(lib: FT_Library, cbuf: *const u8, cbuflen: uint, pt_size: Option<f64>)
+ -> Result<FT_Face, ()> {
+ unsafe {
+ let mut face: FT_Face = ptr::mut_null();
+ let face_index = 0 as FT_Long;
+ let result = FT_New_Memory_Face(lib, cbuf, cbuflen as FT_Long,
+ face_index, &mut face);
+
+ if !result.succeeded() || face.is_null() {
+ return Err(());
+ }
+ match pt_size {
+ Some(s) => {
+ match FontHandle::set_char_size(face, s) {
+ Ok(_) => Ok(face),
+ Err(_) => Err(()),
+ }
+ }
+ None => Ok(face),
+ }
+ }
+ }
+ }
+ fn get_template(&self) -> Arc<FontTemplateData> {
+ self.font_data.clone()
+ }
+ fn family_name(&self) -> String {
+ unsafe { string::raw::from_buf(&*(*self.face).family_name as *const i8 as *const u8) }
+ }
+ fn face_name(&self) -> String {
+ unsafe { string::raw::from_buf(&*FT_Get_Postscript_Name(self.face) as *const i8 as *const u8) }
+ }
+ fn is_italic(&self) -> bool {
+ unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC != 0 }
+ }
+ fn boldness(&self) -> font_weight::T {
+ let default_weight = font_weight::Weight400;
+ if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_BOLD == 0 } {
+ default_weight
+ } else {
+ unsafe {
+ let os2 = FT_Get_Sfnt_Table(self.face, ft_sfnt_os2) as *mut TT_OS2;
+ let valid = os2.is_not_null() && (*os2).version != 0xffff;
+ if valid {
+ let weight =(*os2).usWeightClass;
+ match weight {
+ 1 | 100..199 => font_weight::Weight100,
+ 2 | 200..299 => font_weight::Weight200,
+ 3 | 300..399 => font_weight::Weight300,
+ 4 | 400..499 => font_weight::Weight400,
+ 5 | 500..599 => font_weight::Weight500,
+ 6 | 600..699 => font_weight::Weight600,
+ 7 | 700..799 => font_weight::Weight700,
+ 8 | 800..899 => font_weight::Weight800,
+ 9 | 900..999 => font_weight::Weight900,
+ _ => default_weight
+ }
+ } else {
+ default_weight
+ }
+ }
+ }
+ }
+
+ fn glyph_index(&self,
+ codepoint: char) -> Option<GlyphId> {
+ assert!(self.face.is_not_null());
+ unsafe {
+ let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong);
+ return if idx != 0 as FT_UInt {
+ Some(idx as GlyphId)
+ } else {
+ debug!("Invalid codepoint: {}", codepoint);
+ None
+ };
+ }
+ }
+
+ fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId)
+ -> FractionalPixel {
+ assert!(self.face.is_not_null());
+ let mut delta = struct_FT_Vector_ { x: 0, y: 0 };
+ unsafe {
+ FT_Get_Kerning(self.face, first_glyph, second_glyph, FT_KERNING_DEFAULT, &mut delta);
+ }
+ fixed_to_float_ft(delta.x as i32)
+ }
+
+ fn glyph_h_advance(&self,
+ glyph: GlyphId) -> Option<FractionalPixel> {
+ assert!(self.face.is_not_null());
+ unsafe {
+ let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0);
+ if res.succeeded() {
+ let void_glyph = (*self.face).glyph;
+ let slot: FT_GlyphSlot = mem::transmute(void_glyph);
+ assert!(slot.is_not_null());
+ debug!("metrics: {:?}", (*slot).metrics);
+ let advance = (*slot).metrics.horiAdvance;
+ debug!("h_advance for {} is {}", glyph, advance);
+ let advance = advance as i32;
+ return Some(fixed_to_float_ft(advance) as FractionalPixel);
+ } else {
+ debug!("Unable to load glyph {}. reason: {}", glyph, res);
+ return None;
+ }
+ }
+ }
+
+ fn get_metrics(&self) -> FontMetrics {
+ /* TODO(Issue #76): complete me */
+ let face = self.get_face_rec();
+
+ let underline_size = self.font_units_to_au(face.underline_thickness as f64);
+ let underline_offset = self.font_units_to_au(face.underline_position as f64);
+ let em_size = self.font_units_to_au(face.units_per_EM as f64);
+ let ascent = self.font_units_to_au(face.ascender as f64);
+ let descent = self.font_units_to_au(face.descender as f64);
+ let max_advance = self.font_units_to_au(face.max_advance_width as f64);
+
+ // 'leading' is supposed to be the vertical distance between two baselines,
+ // reflected by the height attibute in freetype. On OS X (w/ CTFont),
+ // leading represents the distance between the bottom of a line descent to
+ // the top of the next line's ascent or: (line_height - ascent - descent),
+ // see http://stackoverflow.com/a/5635981 for CTFont implementation.
+ // Convert using a formular similar to what CTFont returns for consistency.
+ let height = self.font_units_to_au(face.height as f64);
+ let leading = height - (ascent + descent);
+
+ let mut strikeout_size = geometry::from_pt(0.0);
+ let mut strikeout_offset = geometry::from_pt(0.0);
+ let mut x_height = geometry::from_pt(0.0);
+ unsafe {
+ let os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2) as *mut TT_OS2;
+ let valid = os2.is_not_null() && (*os2).version != 0xffff;
+ if valid {
+ strikeout_size = self.font_units_to_au((*os2).yStrikeoutSize as f64);
+ strikeout_offset = self.font_units_to_au((*os2).yStrikeoutPosition as f64);
+ x_height = self.font_units_to_au((*os2).sxHeight as f64);
+ }
+ }
+
+ let metrics = FontMetrics {
+ underline_size: underline_size,
+ underline_offset: underline_offset,
+ strikeout_size: strikeout_size,
+ strikeout_offset: strikeout_offset,
+ leading: leading,
+ x_height: x_height,
+ em_size: em_size,
+ ascent: ascent,
+ descent: -descent, // linux font's seem to use the opposite sign from mac
+ max_advance: max_advance,
+ line_gap: height,
+ };
+
+ debug!("Font metrics (@{:f} pt): {:?}", geometry::to_pt(em_size), metrics);
+ return metrics;
+ }
+
+ fn get_table_for_tag(&self, _: FontTableTag) -> Option<FontTable> {
+ None
+ }
+}
+
+impl<'a> FontHandle {
+ fn set_char_size(face: FT_Face, pt_size: f64) -> Result<(), ()>{
+ let char_width = float_to_fixed_ft(pt_size) as FT_F26Dot6;
+ let char_height = float_to_fixed_ft(pt_size) as FT_F26Dot6;
+ let h_dpi = 72;
+ let v_dpi = 72;
+
+ unsafe {
+ let result = FT_Set_Char_Size(face, char_width, char_height, h_dpi, v_dpi);
+ if result.succeeded() { Ok(()) } else { Err(()) }
+ }
+ }
+
+ fn get_face_rec(&'a self) -> &'a mut FT_FaceRec {
+ unsafe {
+ &mut (*self.face)
+ }
+ }
+
+ fn font_units_to_au(&self, value: f64) -> Au {
+ let face = self.get_face_rec();
+
+ // face.size is a *c_void in the bindings, presumably to avoid
+ // recursive structural types
+ let size: &FT_SizeRec = unsafe { mem::transmute(&(*face.size)) };
+ let metrics: &FT_Size_Metrics = &(*size).metrics;
+
+ let em_size = face.units_per_EM as f64;
+ let x_scale = (metrics.x_ppem as f64) / em_size as f64;
+
+ // If this isn't true then we're scaling one of the axes wrong
+ assert!(metrics.x_ppem == metrics.y_ppem);
+
+ return geometry::from_frac_px(value * x_scale);
+ }
+}
+
diff --git a/components/gfx/platform/freetype/font_context.rs b/components/gfx/platform/freetype/font_context.rs
new file mode 100644
index 00000000000..b6e8222dc61
--- /dev/null
+++ b/components/gfx/platform/freetype/font_context.rs
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use freetype::freetype::FTErrorMethods;
+use freetype::freetype::FT_Add_Default_Modules;
+use freetype::freetype::FT_Done_FreeType;
+use freetype::freetype::FT_Library;
+use freetype::freetype::FT_Memory;
+use freetype::freetype::FT_New_Library;
+use freetype::freetype::struct_FT_MemoryRec_;
+
+use std::ptr;
+use std::rc::Rc;
+
+use libc;
+use libc::{c_void, c_long, size_t, malloc};
+use std::mem;
+
+extern fn ft_alloc(_mem: FT_Memory, size: c_long) -> *mut c_void {
+ unsafe {
+ let ptr = libc::malloc(size as size_t);
+ ptr as *mut c_void
+ }
+}
+
+extern fn ft_free(_mem: FT_Memory, block: *mut c_void) {
+ unsafe {
+ libc::free(block);
+ }
+}
+
+extern fn ft_realloc(_mem: FT_Memory, _cur_size: c_long, new_size: c_long, block: *mut c_void) -> *mut c_void {
+ unsafe {
+ let ptr = libc::realloc(block, new_size as size_t);
+ ptr as *mut c_void
+ }
+}
+
+#[deriving(Clone)]
+pub struct FreeTypeLibraryHandle {
+ pub ctx: FT_Library,
+}
+
+#[deriving(Clone)]
+pub struct FontContextHandle {
+ pub ctx: Rc<FreeTypeLibraryHandle>,
+}
+
+impl Drop for FreeTypeLibraryHandle {
+ fn drop(&mut self) {
+ assert!(self.ctx.is_not_null());
+ unsafe { FT_Done_FreeType(self.ctx) };
+ }
+}
+
+impl FontContextHandle {
+ pub fn new() -> FontContextHandle {
+ unsafe {
+
+ let ptr = libc::malloc(mem::size_of::<struct_FT_MemoryRec_>() as size_t);
+ let allocator: &mut struct_FT_MemoryRec_ = mem::transmute(ptr);
+ ptr::write(allocator, struct_FT_MemoryRec_ {
+ user: ptr::mut_null(),
+ alloc: ft_alloc,
+ free: ft_free,
+ realloc: ft_realloc,
+ });
+
+ let mut ctx: FT_Library = ptr::mut_null();
+
+ let result = FT_New_Library(ptr as FT_Memory, &mut ctx);
+ if !result.succeeded() { fail!("Unable to initialize FreeType library"); }
+
+ FT_Add_Default_Modules(ctx);
+
+ FontContextHandle {
+ ctx: Rc::new(FreeTypeLibraryHandle { ctx: ctx }),
+ }
+ }
+ }
+}
diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs
new file mode 100644
index 00000000000..87ce446381d
--- /dev/null
+++ b/components/gfx/platform/freetype/font_list.rs
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(uppercase_variables)]
+
+extern crate freetype;
+extern crate fontconfig;
+
+use fontconfig::fontconfig::{FcChar8, FcResultMatch, FcSetSystem};
+use fontconfig::fontconfig::{
+ FcConfigGetCurrent, FcConfigGetFonts, FcPatternGetString,
+ FcPatternDestroy, FcFontSetDestroy,
+ FcPatternCreate, FcPatternAddString,
+ FcFontSetList, FcObjectSetCreate, FcObjectSetDestroy,
+ FcObjectSetAdd, FcPatternGetInteger
+};
+
+use libc;
+use libc::c_int;
+use std::ptr;
+use std::string;
+
+pub fn get_available_families(callback: |String|) {
+ unsafe {
+ let config = FcConfigGetCurrent();
+ let fontSet = FcConfigGetFonts(config, FcSetSystem);
+ for i in range(0, (*fontSet).nfont as int) {
+ let font = (*fontSet).fonts.offset(i);
+ let mut family: *mut FcChar8 = ptr::mut_null();
+ let mut v: c_int = 0;
+ let mut FC_FAMILY_C = "family".to_c_str();
+ let FC_FAMILY = FC_FAMILY_C.as_mut_ptr();
+ while FcPatternGetString(*font, FC_FAMILY, v, &mut family) == FcResultMatch {
+ let family_name = string::raw::from_buf(family as *const i8 as *const u8);
+ callback(family_name);
+ v += 1;
+ }
+ }
+ }
+}
+
+pub fn get_variations_for_family(family_name: &str, callback: |String|) {
+ debug!("getting variations for {}", family_name);
+ unsafe {
+ let config = FcConfigGetCurrent();
+ let mut font_set = FcConfigGetFonts(config, FcSetSystem);
+ let font_set_array_ptr = &mut font_set;
+ let pattern = FcPatternCreate();
+ assert!(pattern.is_not_null());
+ let mut FC_FAMILY_C = "family".to_c_str();
+ let FC_FAMILY = FC_FAMILY_C.as_mut_ptr();
+ let mut family_name_c = family_name.to_c_str();
+ let family_name = family_name_c.as_mut_ptr();
+ let ok = FcPatternAddString(pattern, FC_FAMILY, family_name as *mut FcChar8);
+ assert!(ok != 0);
+
+ let object_set = FcObjectSetCreate();
+ assert!(object_set.is_not_null());
+
+ let mut FC_FILE_C = "file".to_c_str();
+ let FC_FILE = FC_FILE_C.as_mut_ptr();
+ FcObjectSetAdd(object_set, FC_FILE);
+ let mut FC_INDEX_C = "index".to_c_str();
+ let FC_INDEX = FC_INDEX_C.as_mut_ptr();
+ FcObjectSetAdd(object_set, FC_INDEX);
+
+ let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set);
+
+ debug!("found {} variations", (*matches).nfont);
+
+ for i in range(0, (*matches).nfont as int) {
+ let font = (*matches).fonts.offset(i);
+ let mut FC_FILE_C = "file".to_c_str();
+ let FC_FILE = FC_FILE_C.as_mut_ptr();
+ let mut file: *mut FcChar8 = ptr::mut_null();
+ let file = if FcPatternGetString(*font, FC_FILE, 0, &mut file) == FcResultMatch {
+ string::raw::from_buf(file as *const i8 as *const u8)
+ } else {
+ fail!();
+ };
+ let mut FC_INDEX_C = "index".to_c_str();
+ let FC_INDEX = FC_INDEX_C.as_mut_ptr();
+ let mut index: libc::c_int = 0;
+ let index = if FcPatternGetInteger(*font, FC_INDEX, 0, &mut index) == FcResultMatch {
+ index
+ } else {
+ fail!();
+ };
+
+ debug!("variation file: {}", file);
+ debug!("variation index: {}", index);
+
+ callback(file);
+ }
+
+ FcFontSetDestroy(matches);
+ FcPatternDestroy(pattern);
+ FcObjectSetDestroy(object_set);
+ }
+}
+
+#[cfg(target_os="linux")]
+pub fn get_last_resort_font_families() -> Vec<String> {
+ vec!(
+ "Fira Sans".to_string(),
+ "DejaVu Sans".to_string(),
+ "Arial".to_string()
+ )
+}
+
+#[cfg(target_os="android")]
+pub fn get_last_resort_font_families() -> Vec<String> {
+ vec!("Roboto".to_string())
+}
diff --git a/components/gfx/platform/freetype/font_template.rs b/components/gfx/platform/freetype/font_template.rs
new file mode 100644
index 00000000000..663ea64ab29
--- /dev/null
+++ b/components/gfx/platform/freetype/font_template.rs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::io;
+use std::io::File;
+
+/// Platform specific font representation for Linux.
+/// The identifier is an absolute path, and the bytes
+/// field is the loaded data that can be passed to
+/// freetype and azure directly.
+pub struct FontTemplateData {
+ pub bytes: Vec<u8>,
+ pub identifier: String,
+}
+
+impl FontTemplateData {
+ pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
+ let bytes = match font_data {
+ Some(bytes) => {
+ bytes
+ },
+ None => {
+ // TODO: Handle file load failure!
+ let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap();
+ file.read_to_end().unwrap()
+ },
+ };
+
+ FontTemplateData {
+ bytes: bytes,
+ identifier: identifier.to_string(),
+ }
+ }
+}
diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs
new file mode 100644
index 00000000000..f616ef328bd
--- /dev/null
+++ b/components/gfx/platform/macos/font.rs
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Implementation of Quartz (CoreGraphics) fonts.
+
+extern crate core_foundation;
+extern crate core_graphics;
+extern crate core_text;
+
+use font::{FontHandleMethods, FontMetrics, FontTableMethods};
+use font::FontTableTag;
+use font::FractionalPixel;
+use servo_util::geometry::{Au, px_to_pt};
+use servo_util::geometry;
+use platform::macos::font_context::FontContextHandle;
+use text::glyph::GlyphId;
+use style::computed_values::font_weight;
+use platform::font_template::FontTemplateData;
+
+use core_foundation::base::CFIndex;
+use core_foundation::data::CFData;
+use core_foundation::string::UniChar;
+use core_graphics::font::CGGlyph;
+use core_graphics::geometry::CGRect;
+use core_text::font::CTFont;
+use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors};
+use core_text::font_descriptor::{kCTFontDefaultOrientation};
+
+use std::ptr;
+use sync::Arc;
+
+pub struct FontTable {
+ data: CFData,
+}
+
+// Noncopyable.
+impl Drop for FontTable {
+ fn drop(&mut self) {}
+}
+
+impl FontTable {
+ pub fn wrap(data: CFData) -> FontTable {
+ FontTable { data: data }
+ }
+}
+
+impl FontTableMethods for FontTable {
+ fn with_buffer(&self, blk: |*const u8, uint|) {
+ blk(self.data.bytes().as_ptr(), self.data.len() as uint);
+ }
+}
+
+pub struct FontHandle {
+ pub font_data: Arc<FontTemplateData>,
+ pub ctfont: CTFont,
+}
+
+impl FontHandleMethods for FontHandle {
+ fn new_from_template(_fctx: &FontContextHandle,
+ template: Arc<FontTemplateData>,
+ pt_size: Option<f64>)
+ -> Result<FontHandle, ()> {
+ let size = match pt_size {
+ Some(s) => s,
+ None => 0.0
+ };
+ match template.ctfont {
+ Some(ref ctfont) => {
+ Ok(FontHandle {
+ font_data: template.clone(),
+ ctfont: ctfont.clone_with_font_size(size),
+ })
+ }
+ None => {
+ Err(())
+ }
+ }
+ }
+
+ fn get_template(&self) -> Arc<FontTemplateData> {
+ self.font_data.clone()
+ }
+
+ fn family_name(&self) -> String {
+ self.ctfont.family_name()
+ }
+
+ fn face_name(&self) -> String {
+ self.ctfont.face_name()
+ }
+
+ fn is_italic(&self) -> bool {
+ self.ctfont.symbolic_traits().is_italic()
+ }
+
+ fn boldness(&self) -> font_weight::T {
+ // -1.0 to 1.0
+ let normalized = self.ctfont.all_traits().normalized_weight();
+ // 0.0 to 9.0
+ let normalized = (normalized + 1.0) / 2.0 * 9.0;
+ if normalized < 1.0 { return font_weight::Weight100; }
+ if normalized < 2.0 { return font_weight::Weight200; }
+ if normalized < 3.0 { return font_weight::Weight300; }
+ if normalized < 4.0 { return font_weight::Weight400; }
+ if normalized < 5.0 { return font_weight::Weight500; }
+ if normalized < 6.0 { return font_weight::Weight600; }
+ if normalized < 7.0 { return font_weight::Weight700; }
+ if normalized < 8.0 { return font_weight::Weight800; }
+ return font_weight::Weight900;
+ }
+
+ fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
+ let characters: [UniChar, ..1] = [codepoint as UniChar];
+ let mut glyphs: [CGGlyph, ..1] = [0 as CGGlyph];
+ let count: CFIndex = 1;
+
+ let result = self.ctfont.get_glyphs_for_characters(&characters[0],
+ &mut glyphs[0],
+ count);
+
+ if !result {
+ // No glyph for this character
+ return None;
+ }
+
+ assert!(glyphs[0] != 0); // FIXME: error handling
+ return Some(glyphs[0] as GlyphId);
+ }
+
+ fn glyph_h_kerning(&self, _first_glyph: GlyphId, _second_glyph: GlyphId)
+ -> FractionalPixel {
+ // TODO: Implement on mac
+ 0.0
+ }
+
+ fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
+ let glyphs = [glyph as CGGlyph];
+ let advance = self.ctfont.get_advances_for_glyphs(kCTFontDefaultOrientation,
+ &glyphs[0],
+ ptr::mut_null(),
+ 1);
+ Some(advance as FractionalPixel)
+ }
+
+ fn get_metrics(&self) -> FontMetrics {
+ let bounding_rect: CGRect = self.ctfont.bounding_box();
+ let ascent = self.ctfont.ascent() as f64;
+ let descent = self.ctfont.descent() as f64;
+ let em_size = Au::from_frac_px(self.ctfont.pt_size() as f64);
+ let leading = self.ctfont.leading() as f64;
+
+ let scale = px_to_pt(self.ctfont.pt_size() as f64) / (ascent + descent);
+ let line_gap = (ascent + descent + leading + 0.5).floor();
+
+ let metrics = FontMetrics {
+ underline_size: Au::from_pt(self.ctfont.underline_thickness() as f64),
+ // TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table
+ // directly.
+ //
+ // see also: https://bugs.webkit.org/show_bug.cgi?id=16768
+ // see also: https://bugreports.qt-project.org/browse/QTBUG-13364
+ underline_offset: Au::from_pt(self.ctfont.underline_position() as f64),
+ strikeout_size: geometry::from_pt(0.0), // FIXME(Issue #942)
+ strikeout_offset: geometry::from_pt(0.0), // FIXME(Issue #942)
+ leading: Au::from_pt(leading),
+ x_height: Au::from_pt(self.ctfont.x_height() as f64),
+ em_size: em_size,
+ ascent: Au::from_pt(ascent * scale),
+ descent: Au::from_pt(descent * scale),
+ max_advance: Au::from_pt(bounding_rect.size.width as f64),
+ line_gap: Au::from_frac_px(line_gap),
+ };
+ debug!("Font metrics (@{:f} pt): {:?}", self.ctfont.pt_size() as f64, metrics);
+ return metrics;
+ }
+
+ fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
+ let result: Option<CFData> = self.ctfont.get_font_table(tag);
+ result.and_then(|data| {
+ Some(FontTable::wrap(data))
+ })
+ }
+}
+
diff --git a/components/gfx/platform/macos/font_context.rs b/components/gfx/platform/macos/font_context.rs
new file mode 100644
index 00000000000..94730641c3d
--- /dev/null
+++ b/components/gfx/platform/macos/font_context.rs
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[deriving(Clone)]
+pub struct FontContextHandle {
+ ctx: ()
+}
+
+#[deriving(Clone)]
+impl FontContextHandle {
+ // this is a placeholder until NSFontManager or whatever is bound in here.
+ pub fn new() -> FontContextHandle {
+ FontContextHandle { ctx: () }
+ }
+}
diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs
new file mode 100644
index 00000000000..4ec319ec6b2
--- /dev/null
+++ b/components/gfx/platform/macos/font_list.rs
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use core_foundation::base::TCFType;
+use core_foundation::string::{CFString, CFStringRef};
+use core_text::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef};
+use core_text;
+use std::mem;
+
+pub fn get_available_families(callback: |String|) {
+ let family_names = core_text::font_collection::get_family_names();
+ for strref in family_names.iter() {
+ let family_name_ref: CFStringRef = unsafe { mem::transmute(strref) };
+ let family_name_cf: CFString = unsafe { TCFType::wrap_under_get_rule(family_name_ref) };
+ let family_name = family_name_cf.to_string();
+ callback(family_name);
+ }
+}
+
+pub fn get_variations_for_family(family_name: &str, callback: |String|) {
+ debug!("Looking for faces of family: {:s}", family_name);
+
+ let family_collection =
+ core_text::font_collection::create_for_family(family_name.as_slice());
+ let family_descriptors = family_collection.get_descriptors();
+ for descref in family_descriptors.iter() {
+ let descref: CTFontDescriptorRef = unsafe { mem::transmute(descref) };
+ let desc: CTFontDescriptor = unsafe { TCFType::wrap_under_get_rule(descref) };
+ let postscript_name = desc.font_name();
+ callback(postscript_name);
+ }
+}
+
+pub fn get_last_resort_font_families() -> Vec<String> {
+ vec!("Arial Unicode MS".to_string(), "Arial".to_string())
+}
diff --git a/components/gfx/platform/macos/font_template.rs b/components/gfx/platform/macos/font_template.rs
new file mode 100644
index 00000000000..8641d491523
--- /dev/null
+++ b/components/gfx/platform/macos/font_template.rs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use core_graphics::data_provider::CGDataProvider;
+use core_graphics::font::CGFont;
+use core_text::font::CTFont;
+use core_text;
+
+/// Platform specific font representation for mac.
+/// The identifier is a PostScript font name. The
+/// CTFont object is cached here for use by the
+/// render functions that create CGFont references.
+pub struct FontTemplateData {
+ pub ctfont: Option<CTFont>,
+ pub identifier: String,
+}
+
+impl FontTemplateData {
+ pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData {
+ let ctfont = match font_data {
+ Some(bytes) => {
+ let fontprov = CGDataProvider::from_buffer(bytes.as_slice());
+ let cgfont_result = CGFont::from_data_provider(fontprov);
+ match cgfont_result {
+ Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)),
+ Err(_) => None
+ }
+ },
+ None => {
+ Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap())
+ }
+ };
+
+ FontTemplateData {
+ ctfont: ctfont,
+ identifier: identifier.to_string(),
+ }
+ }
+}
diff --git a/components/gfx/platform/mod.rs b/components/gfx/platform/mod.rs
new file mode 100644
index 00000000000..ded6f3888e8
--- /dev/null
+++ b/components/gfx/platform/mod.rs
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[cfg(target_os="linux")]
+#[cfg(target_os="android")]
+pub use platform::freetype::{font, font_context, font_list, font_template};
+
+#[cfg(target_os="macos")]
+pub use platform::macos::{font, font_context, font_list, font_template};
+
+#[cfg(target_os="linux")]
+#[cfg(target_os="android")]
+pub mod freetype {
+ pub mod font;
+ pub mod font_context;
+ pub mod font_list;
+ pub mod font_template;
+}
+
+#[cfg(target_os="macos")]
+pub mod macos {
+ pub mod font;
+ pub mod font_context;
+ pub mod font_list;
+ pub mod font_template;
+}
diff --git a/components/gfx/render_context.rs b/components/gfx/render_context.rs
new file mode 100644
index 00000000000..68449cdff19
--- /dev/null
+++ b/components/gfx/render_context.rs
@@ -0,0 +1,419 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use font_context::FontContext;
+use style::computed_values::border_style;
+
+use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions, DrawTarget};
+use azure::azure_hl::{Linear, SourceOp, StrokeOptions};
+use azure::AZ_CAP_BUTT;
+use azure::AzFloat;
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use geom::side_offsets::SideOffsets2D;
+use libc::types::common::c99::uint16_t;
+use libc::size_t;
+use png::{RGB8, RGBA8, K8, KA8};
+use servo_net::image::base::Image;
+use servo_util::geometry::Au;
+use servo_util::opts::Opts;
+use sync::Arc;
+
+pub struct RenderContext<'a> {
+ pub draw_target: &'a DrawTarget,
+ pub font_ctx: &'a mut Box<FontContext>,
+ pub opts: &'a Opts,
+ /// The rectangle that this context encompasses in page coordinates.
+ pub page_rect: Rect<f32>,
+ /// The rectangle that this context encompasses in screen coordinates (pixels).
+ pub screen_rect: Rect<uint>,
+}
+
+enum Direction {
+ Top,
+ Left,
+ Right,
+ Bottom
+}
+
+enum DashSize {
+ DottedBorder = 1,
+ DashedBorder = 3
+}
+
+impl<'a> RenderContext<'a> {
+ pub fn get_draw_target(&self) -> &'a DrawTarget {
+ self.draw_target
+ }
+
+ pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
+ self.draw_target.make_current();
+ self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern::new(color), None);
+ }
+
+ pub fn draw_border(&self,
+ bounds: &Rect<Au>,
+ border: SideOffsets2D<Au>,
+ color: SideOffsets2D<Color>,
+ style: SideOffsets2D<border_style::T>) {
+ let border = border.to_float_px();
+ self.draw_target.make_current();
+
+ self.draw_border_segment(Top, bounds, border, color, style);
+ self.draw_border_segment(Right, bounds, border, color, style);
+ self.draw_border_segment(Bottom, bounds, border, color, style);
+ self.draw_border_segment(Left, bounds, border, color, style);
+ }
+
+ pub fn draw_line(&self,
+ bounds: &Rect<Au>,
+ color: Color,
+ style: border_style::T) {
+ self.draw_target.make_current();
+
+ self.draw_line_segment(bounds, color, style);
+ }
+
+ pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
+ let rect = bounds.to_azure_rect();
+ let path_builder = self.draw_target.create_path_builder();
+
+ let left_top = Point2D(rect.origin.x, rect.origin.y);
+ let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
+ let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
+ let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
+
+ path_builder.move_to(left_top);
+ path_builder.line_to(right_top);
+ path_builder.line_to(right_bottom);
+ path_builder.line_to(left_bottom);
+
+ let path = path_builder.finish();
+ self.draw_target.push_clip(&path);
+ }
+
+ pub fn draw_pop_clip(&self) {
+ self.draw_target.pop_clip();
+ }
+
+ pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<Box<Image>>) {
+ let size = Size2D(image.width as i32, image.height as i32);
+ let (pixel_width, pixels, source_format) = match image.pixels {
+ RGBA8(ref pixels) => (4, pixels.as_slice(), B8G8R8A8),
+ K8(ref pixels) => (1, pixels.as_slice(), A8),
+ RGB8(_) => fail!("RGB8 color type not supported"),
+ KA8(_) => fail!("KA8 color type not supported"),
+ };
+ let stride = image.width * pixel_width;
+
+ self.draw_target.make_current();
+ let draw_target_ref = &self.draw_target;
+ let azure_surface = draw_target_ref.create_source_surface_from_data(pixels,
+ size,
+ stride as i32,
+ source_format);
+ let source_rect = Rect(Point2D(0u as AzFloat, 0u as AzFloat),
+ Size2D(image.width as AzFloat, image.height as AzFloat));
+ let dest_rect = bounds.to_azure_rect();
+ let draw_surface_options = DrawSurfaceOptions::new(Linear, true);
+ let draw_options = DrawOptions::new(1.0f64 as AzFloat, 0);
+ draw_target_ref.draw_surface(azure_surface,
+ dest_rect,
+ source_rect,
+ draw_surface_options,
+ draw_options);
+ }
+
+ pub fn clear(&self) {
+ let pattern = ColorPattern::new(Color::new(0.0, 0.0, 0.0, 0.0));
+ let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat,
+ self.page_rect.origin.y as AzFloat),
+ Size2D(self.screen_rect.size.width as AzFloat,
+ self.screen_rect.size.height as AzFloat));
+ let mut draw_options = DrawOptions::new(1.0, 0);
+ draw_options.set_composition_op(SourceOp);
+ self.draw_target.make_current();
+ self.draw_target.fill_rect(&rect, &pattern, Some(&draw_options));
+ }
+
+ fn draw_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: SideOffsets2D<Color>, style: SideOffsets2D<border_style::T>) {
+ let (style_select, color_select) = match direction {
+ Top => (style.top, color.top),
+ Left => (style.left, color.left),
+ Right => (style.right, color.right),
+ Bottom => (style.bottom, color.bottom)
+ };
+
+ match style_select{
+ border_style::none => {
+ }
+ border_style::hidden => {
+ }
+ //FIXME(sammykim): This doesn't work with dash_pattern and cap_style well. I referred firefox code.
+ border_style::dotted => {
+ self.draw_dashed_border_segment(direction, bounds, border, color_select, DottedBorder);
+ }
+ border_style::dashed => {
+ self.draw_dashed_border_segment(direction, bounds, border, color_select, DashedBorder);
+ }
+ border_style::solid => {
+ self.draw_solid_border_segment(direction,bounds,border,color_select);
+ }
+ border_style::double => {
+ self.draw_double_border_segment(direction, bounds, border, color_select);
+ }
+ border_style::groove | border_style::ridge => {
+ self.draw_groove_ridge_border_segment(direction, bounds, border, color_select, style_select);
+ }
+ border_style::inset | border_style::outset => {
+ self.draw_inset_outset_border_segment(direction, bounds, border, style_select, color_select);
+ }
+ }
+ }
+
+ fn draw_line_segment(&self, bounds: &Rect<Au>, color: Color, style: border_style::T) {
+ let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_px();
+
+ match style{
+ border_style::none | border_style::hidden => {}
+ border_style::dotted => {
+ self.draw_dashed_border_segment(Right, bounds, border, color, DottedBorder);
+ }
+ border_style::dashed => {
+ self.draw_dashed_border_segment(Right, bounds, border, color, DashedBorder);
+ }
+ border_style::solid => {
+ self.draw_solid_border_segment(Right,bounds,border,color);
+ }
+ border_style::double => {
+ self.draw_double_border_segment(Right, bounds, border, color);
+ }
+ border_style::groove | border_style::ridge => {
+ self.draw_groove_ridge_border_segment(Right, bounds, border, color, style);
+ }
+ border_style::inset | border_style::outset => {
+ self.draw_inset_outset_border_segment(Right, bounds, border, style, color);
+ }
+ }
+ }
+
+ fn draw_border_path(&self,
+ bounds: Rect<f32>,
+ direction: Direction,
+ border: SideOffsets2D<f32>,
+ color: Color) {
+ let left_top = bounds.origin;
+ let right_top = left_top + Point2D(bounds.size.width, 0.0);
+ let left_bottom = left_top + Point2D(0.0, bounds.size.height);
+ let right_bottom = left_top + Point2D(bounds.size.width, bounds.size.height);
+ let draw_opts = DrawOptions::new(1.0, 0);
+ let path_builder = self.draw_target.create_path_builder();
+ match direction {
+ Top => {
+ path_builder.move_to(left_top);
+ path_builder.line_to(right_top);
+ path_builder.line_to(right_top + Point2D(-border.right, border.top));
+ path_builder.line_to(left_top + Point2D(border.left, border.top));
+ }
+ Left => {
+ path_builder.move_to(left_top);
+ path_builder.line_to(left_top + Point2D(border.left, border.top));
+ path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
+ path_builder.line_to(left_bottom);
+ }
+ Right => {
+ path_builder.move_to(right_top);
+ path_builder.line_to(right_bottom);
+ path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
+ path_builder.line_to(right_top + Point2D(-border.right, border.top));
+ }
+ Bottom => {
+ path_builder.move_to(left_bottom);
+ path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom));
+ path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom));
+ path_builder.line_to(right_bottom);
+ }
+ }
+ let path = path_builder.finish();
+ self.draw_target.fill(&path, &ColorPattern::new(color), &draw_opts);
+
+ }
+
+ fn draw_dashed_border_segment(&self,
+ direction: Direction,
+ bounds: &Rect<Au>,
+ border: SideOffsets2D<f32>,
+ color: Color,
+ dash_size: DashSize) {
+ let rect = bounds.to_azure_rect();
+ let draw_opts = DrawOptions::new(1u as AzFloat, 0 as uint16_t);
+ let mut stroke_opts = StrokeOptions::new(0u as AzFloat, 10u as AzFloat);
+ let mut dash: [AzFloat, ..2] = [0u as AzFloat, 0u as AzFloat];
+
+ stroke_opts.set_cap_style(AZ_CAP_BUTT as u8);
+
+ let border_width = match direction {
+ Top => border.top,
+ Left => border.left,
+ Right => border.right,
+ Bottom => border.bottom
+ };
+
+ stroke_opts.line_width = border_width;
+ dash[0] = border_width * (dash_size as int) as AzFloat;
+ dash[1] = border_width * (dash_size as int) as AzFloat;
+ stroke_opts.mDashPattern = dash.as_mut_ptr();
+ stroke_opts.mDashLength = dash.len() as size_t;
+
+ let (start, end) = match direction {
+ Top => {
+ let y = rect.origin.y + border.top * 0.5;
+ let start = Point2D(rect.origin.x, y);
+ let end = Point2D(rect.origin.x + rect.size.width, y);
+ (start, end)
+ }
+ Left => {
+ let x = rect.origin.x + border.left * 0.5;
+ let start = Point2D(x, rect.origin.y + rect.size.height);
+ let end = Point2D(x, rect.origin.y + border.top);
+ (start, end)
+ }
+ Right => {
+ let x = rect.origin.x + rect.size.width - border.right * 0.5;
+ let start = Point2D(x, rect.origin.y);
+ let end = Point2D(x, rect.origin.y + rect.size.height);
+ (start, end)
+ }
+ Bottom => {
+ let y = rect.origin.y + rect.size.height - border.bottom * 0.5;
+ let start = Point2D(rect.origin.x + rect.size.width, y);
+ let end = Point2D(rect.origin.x + border.left, y);
+ (start, end)
+ }
+ };
+
+ self.draw_target.stroke_line(start,
+ end,
+ &ColorPattern::new(color),
+ &stroke_opts,
+ &draw_opts);
+ }
+
+ fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
+ let rect = bounds.to_azure_rect();
+ self.draw_border_path(rect, direction, border, color);
+ }
+
+ fn get_scaled_bounds(&self,
+ bounds: &Rect<Au>,
+ border: SideOffsets2D<f32>,
+ shrink_factor: f32) -> Rect<f32> {
+ let rect = bounds.to_azure_rect();
+ let scaled_border = SideOffsets2D::new(shrink_factor * border.top,
+ shrink_factor * border.right,
+ shrink_factor * border.bottom,
+ shrink_factor * border.left);
+ let left_top = Point2D(rect.origin.x, rect.origin.y);
+ let scaled_left_top = left_top + Point2D(scaled_border.left,
+ scaled_border.top);
+ return Rect(scaled_left_top,
+ Size2D(rect.size.width - 2.0 * scaled_border.right, rect.size.height - 2.0 * scaled_border.bottom));
+ }
+
+ fn scale_color(&self, color: Color, scale_factor: f32) -> Color {
+ return Color::new(color.r * scale_factor, color.g * scale_factor, color.b * scale_factor, color.a);
+ }
+
+ fn draw_double_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) {
+ let scaled_border = SideOffsets2D::new((1.0/3.0) * border.top,
+ (1.0/3.0) * border.right,
+ (1.0/3.0) * border.bottom,
+ (1.0/3.0) * border.left);
+ let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 2.0/3.0);
+ // draw the outer portion of the double border.
+ self.draw_solid_border_segment(direction, bounds, scaled_border, color);
+ // draw the inner portion of the double border.
+ self.draw_border_path(inner_scaled_bounds, direction, scaled_border, color);
+ }
+
+ fn draw_groove_ridge_border_segment(&self,
+ direction: Direction,
+ bounds: &Rect<Au>,
+ border: SideOffsets2D<f32>,
+ color: Color,
+ style: border_style::T) {
+ // original bounds as a Rect<f32>, with no scaling.
+ let original_bounds = self.get_scaled_bounds(bounds, border, 0.0);
+ // shrink the bounds by 1/2 of the border, leaving the innermost 1/2 of the border
+ let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 0.5);
+ let scaled_border = SideOffsets2D::new(0.5 * border.top,
+ 0.5 * border.right,
+ 0.5 * border.bottom,
+ 0.5 * border.left);
+ let is_groove = match style {
+ border_style::groove => true,
+ border_style::ridge => false,
+ _ => fail!("invalid border style")
+ };
+ let darker_color = self.scale_color(color, if is_groove { 1.0/3.0 } else { 2.0/3.0 });
+ let (outer_color, inner_color) = match (direction, is_groove) {
+ (Top, true) | (Left, true) | (Right, false) | (Bottom, false) => (darker_color, color),
+ (Top, false) | (Left, false) | (Right, true) | (Bottom, true) => (color, darker_color)
+ };
+ // outer portion of the border
+ self.draw_border_path(original_bounds, direction, scaled_border, outer_color);
+ // inner portion of the border
+ self.draw_border_path(inner_scaled_bounds, direction, scaled_border, inner_color);
+ }
+
+ fn draw_inset_outset_border_segment(&self,
+ direction: Direction,
+ bounds: &Rect<Au>,
+ border: SideOffsets2D<f32>,
+ style: border_style::T,
+ color: Color) {
+ let is_inset = match style {
+ border_style::inset => true,
+ border_style::outset => false,
+ _ => fail!("invalid border style")
+ };
+ // original bounds as a Rect<f32>
+ let original_bounds = self.get_scaled_bounds(bounds, border, 0.0);
+ // select and scale the color appropriately.
+ let scaled_color = match direction {
+ Top => self.scale_color(color, if is_inset { 2.0/3.0 } else { 1.0 }),
+ Left => self.scale_color(color, if is_inset { 1.0/6.0 } else { 0.5 }),
+ Right | Bottom => self.scale_color(color, if is_inset { 1.0 } else { 2.0/3.0 })
+ };
+ self.draw_border_path(original_bounds, direction, border, scaled_color);
+ }
+
+}
+
+trait ToAzureRect {
+ fn to_azure_rect(&self) -> Rect<AzFloat>;
+}
+
+impl ToAzureRect for Rect<Au> {
+ fn to_azure_rect(&self) -> Rect<AzFloat> {
+ Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat,
+ self.origin.y.to_nearest_px() as AzFloat),
+ Size2D(self.size.width.to_nearest_px() as AzFloat,
+ self.size.height.to_nearest_px() as AzFloat))
+ }
+}
+
+trait ToSideOffsetsPx {
+ fn to_float_px(&self) -> SideOffsets2D<AzFloat>;
+}
+
+impl ToSideOffsetsPx for SideOffsets2D<Au> {
+ fn to_float_px(&self) -> SideOffsets2D<AzFloat> {
+ SideOffsets2D::new(self.top.to_nearest_px() as AzFloat,
+ self.right.to_nearest_px() as AzFloat,
+ self.bottom.to_nearest_px() as AzFloat,
+ self.left.to_nearest_px() as AzFloat)
+ }
+}
diff --git a/components/gfx/render_task.rs b/components/gfx/render_task.rs
new file mode 100644
index 00000000000..2d7b8b5e2d7
--- /dev/null
+++ b/components/gfx/render_task.rs
@@ -0,0 +1,443 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The task that handles all rendering/painting.
+
+use buffer_map::BufferMap;
+use display_list::optimizer::DisplayListOptimizer;
+use display_list::DisplayList;
+use font_context::FontContext;
+use render_context::RenderContext;
+
+use azure::azure_hl::{B8G8R8A8, Color, DrawTarget, StolenGLResources};
+use azure::AzFloat;
+use geom::matrix2d::Matrix2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface};
+use layers::platform::surface::{NativeSurfaceMethods};
+use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet};
+use layers;
+use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerId};
+use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy};
+use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
+use servo_msg::constellation_msg::{RendererReadyMsg};
+use servo_msg::platform::surface::NativeSurfaceAzureMethods;
+use servo_util::geometry;
+use servo_util::opts::Opts;
+use servo_util::smallvec::{SmallVec, SmallVec1};
+use servo_util::task::spawn_named_with_send_on_failure;
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use std::comm::{Receiver, Sender, channel};
+use sync::Arc;
+use font_cache_task::FontCacheTask;
+
+/// Information about a layer that layout sends to the painting task.
+pub struct RenderLayer {
+ /// A per-pipeline ID describing this layer that should be stable across reflows.
+ pub id: LayerId,
+ /// The display list describing the contents of this layer.
+ pub display_list: Arc<DisplayList>,
+ /// The position of the layer in pixels.
+ pub position: Rect<uint>,
+ /// The color of the background in this layer. Used for unrendered content.
+ pub background_color: Color,
+ /// The scrolling policy of this layer.
+ pub scroll_policy: ScrollPolicy,
+}
+
+pub struct RenderRequest {
+ pub buffer_requests: Vec<BufferRequest>,
+ pub scale: f32,
+ pub layer_id: LayerId,
+ pub epoch: Epoch,
+}
+
+pub enum Msg {
+ RenderInitMsg(SmallVec1<RenderLayer>),
+ RenderMsg(Vec<RenderRequest>),
+ UnusedBufferMsg(Vec<Box<LayerBuffer>>),
+ PaintPermissionGranted,
+ PaintPermissionRevoked,
+ ExitMsg(Option<Sender<()>>),
+}
+
+#[deriving(Clone)]
+pub struct RenderChan(Sender<Msg>);
+
+impl RenderChan {
+ pub fn new() -> (Receiver<Msg>, RenderChan) {
+ let (chan, port) = channel();
+ (port, RenderChan(chan))
+ }
+
+ pub fn send(&self, msg: Msg) {
+ let &RenderChan(ref chan) = self;
+ assert!(chan.send_opt(msg).is_ok(), "RenderChan.send: render port closed")
+ }
+
+ pub fn send_opt(&self, msg: Msg) -> Result<(), Msg> {
+ let &RenderChan(ref chan) = self;
+ chan.send_opt(msg)
+ }
+}
+
+/// If we're using GPU rendering, this provides the metadata needed to create a GL context that
+/// is compatible with that of the main thread.
+pub enum GraphicsContext {
+ CpuGraphicsContext,
+ GpuGraphicsContext,
+}
+
+pub struct RenderTask<C> {
+ id: PipelineId,
+ port: Receiver<Msg>,
+ compositor: C,
+ constellation_chan: ConstellationChan,
+ font_ctx: Box<FontContext>,
+ opts: Opts,
+
+ /// A channel to the time profiler.
+ time_profiler_chan: TimeProfilerChan,
+
+ /// The graphics context to use.
+ graphics_context: GraphicsContext,
+
+ /// The native graphics context.
+ native_graphics_context: Option<NativePaintingGraphicsContext>,
+
+ /// The layers to be rendered.
+ render_layers: SmallVec1<RenderLayer>,
+
+ /// Permission to send paint messages to the compositor
+ paint_permission: bool,
+
+ /// A counter for epoch messages
+ epoch: Epoch,
+
+ /// A data structure to store unused LayerBuffers
+ buffer_map: BufferMap,
+}
+
+// If we implement this as a function, we get borrowck errors from borrowing
+// the whole RenderTask struct.
+macro_rules! native_graphics_context(
+ ($task:expr) => (
+ $task.native_graphics_context.as_ref().expect("Need a graphics context to do rendering")
+ )
+)
+
+fn initialize_layers<C:RenderListener>(
+ compositor: &mut C,
+ pipeline_id: PipelineId,
+ epoch: Epoch,
+ render_layers: &[RenderLayer]) {
+ let metadata = render_layers.iter().map(|render_layer| {
+ LayerMetadata {
+ id: render_layer.id,
+ position: render_layer.position,
+ background_color: render_layer.background_color,
+ scroll_policy: render_layer.scroll_policy,
+ }
+ }).collect();
+ compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch);
+}
+
+impl<C:RenderListener + Send> RenderTask<C> {
+ pub fn create(id: PipelineId,
+ port: Receiver<Msg>,
+ compositor: C,
+ constellation_chan: ConstellationChan,
+ font_cache_task: FontCacheTask,
+ failure_msg: Failure,
+ opts: Opts,
+ time_profiler_chan: TimeProfilerChan,
+ shutdown_chan: Sender<()>) {
+
+ let ConstellationChan(c) = constellation_chan.clone();
+ let fc = font_cache_task.clone();
+
+ spawn_named_with_send_on_failure("RenderTask", proc() {
+ { // Ensures RenderTask and graphics context are destroyed before shutdown msg
+ let native_graphics_context = compositor.get_graphics_metadata().map(
+ |md| NativePaintingGraphicsContext::from_metadata(&md));
+ let cpu_painting = opts.cpu_painting;
+
+ // FIXME: rust/#5967
+ let mut render_task = RenderTask {
+ id: id,
+ port: port,
+ compositor: compositor,
+ constellation_chan: constellation_chan,
+ font_ctx: box FontContext::new(fc.clone()),
+ opts: opts,
+ time_profiler_chan: time_profiler_chan,
+
+ graphics_context: if cpu_painting {
+ CpuGraphicsContext
+ } else {
+ GpuGraphicsContext
+ },
+
+ native_graphics_context: native_graphics_context,
+
+ render_layers: SmallVec1::new(),
+
+ paint_permission: false,
+ epoch: Epoch(0),
+ buffer_map: BufferMap::new(10000000),
+ };
+
+ render_task.start();
+
+ // Destroy all the buffers.
+ match render_task.native_graphics_context.as_ref() {
+ Some(ctx) => render_task.buffer_map.clear(ctx),
+ None => (),
+ }
+ }
+
+ debug!("render_task: shutdown_chan send");
+ shutdown_chan.send(());
+ }, FailureMsg(failure_msg), c, true);
+ }
+
+ fn start(&mut self) {
+ debug!("render_task: beginning rendering loop");
+
+ loop {
+ match self.port.recv() {
+ RenderInitMsg(render_layers) => {
+ self.epoch.next();
+ self.render_layers = render_layers;
+
+ if !self.paint_permission {
+ debug!("render_task: render ready msg");
+ let ConstellationChan(ref mut c) = self.constellation_chan;
+ c.send(RendererReadyMsg(self.id));
+ continue;
+ }
+
+ initialize_layers(&mut self.compositor,
+ self.id,
+ self.epoch,
+ self.render_layers.as_slice());
+ }
+ RenderMsg(requests) => {
+ if !self.paint_permission {
+ debug!("render_task: render ready msg");
+ let ConstellationChan(ref mut c) = self.constellation_chan;
+ c.send(RendererReadyMsg(self.id));
+ self.compositor.render_msg_discarded();
+ continue;
+ }
+
+ self.compositor.set_render_state(RenderingRenderState);
+
+ let mut replies = Vec::new();
+ for RenderRequest { buffer_requests, scale, layer_id, epoch }
+ in requests.move_iter() {
+ if self.epoch == epoch {
+ self.render(&mut replies, buffer_requests, scale, layer_id);
+ } else {
+ debug!("renderer epoch mismatch: {:?} != {:?}", self.epoch, epoch);
+ }
+ }
+
+ self.compositor.set_render_state(IdleRenderState);
+
+ debug!("render_task: returning surfaces");
+ self.compositor.paint(self.id, self.epoch, replies);
+ }
+ UnusedBufferMsg(unused_buffers) => {
+ for buffer in unused_buffers.move_iter().rev() {
+ self.buffer_map.insert(native_graphics_context!(self), buffer);
+ }
+ }
+ PaintPermissionGranted => {
+ self.paint_permission = true;
+
+ // Here we assume that the main layer—the layer responsible for the page size—
+ // is the first layer. This is a pretty fragile assumption. It will be fixed
+ // once we use the layers-based scrolling infrastructure for all scrolling.
+ if self.render_layers.len() > 1 {
+ self.epoch.next();
+ initialize_layers(&mut self.compositor,
+ self.id,
+ self.epoch,
+ self.render_layers.as_slice());
+ }
+ }
+ PaintPermissionRevoked => {
+ self.paint_permission = false;
+ }
+ ExitMsg(response_ch) => {
+ debug!("render_task: exitmsg response send");
+ response_ch.map(|ch| ch.send(()));
+ break;
+ }
+ }
+ }
+ }
+
+ /// Renders one layer and sends the tiles back to the layer.
+ fn render(&mut self,
+ replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>,
+ tiles: Vec<BufferRequest>,
+ scale: f32,
+ layer_id: LayerId) {
+ time::profile(time::RenderingCategory, self.time_profiler_chan.clone(), || {
+ // FIXME: Try not to create a new array here.
+ let mut new_buffers = vec!();
+
+ // Find the appropriate render layer.
+ let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) {
+ Some(render_layer) => render_layer,
+ None => return,
+ };
+
+ // Divide up the layer into tiles.
+ for tile in tiles.iter() {
+ // Optimize the display list for this tile.
+ let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect);
+ let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(),
+ page_rect_au);
+ let display_list = optimizer.optimize();
+
+ let width = tile.screen_rect.size.width;
+ let height = tile.screen_rect.size.height;
+
+ let size = Size2D(width as i32, height as i32);
+ let draw_target = match self.graphics_context {
+ CpuGraphicsContext => {
+ DrawTarget::new(self.opts.render_backend, size, B8G8R8A8)
+ }
+ GpuGraphicsContext => {
+ // FIXME(pcwalton): Cache the components of draw targets
+ // (texture color buffer, renderbuffers) instead of recreating them.
+ let draw_target =
+ DrawTarget::new_with_fbo(self.opts.render_backend,
+ native_graphics_context!(self),
+ size,
+ B8G8R8A8);
+ draw_target.make_current();
+ draw_target
+ }
+ };
+
+ {
+ // Build the render context.
+ let mut ctx = RenderContext {
+ draw_target: &draw_target,
+ font_ctx: &mut self.font_ctx,
+ opts: &self.opts,
+ page_rect: tile.page_rect,
+ screen_rect: tile.screen_rect,
+ };
+
+ // Apply the translation to render the tile we want.
+ let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
+ let matrix = matrix.scale(scale as AzFloat, scale as AzFloat);
+ let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat,
+ -(tile.page_rect.origin.y) as AzFloat);
+ let matrix = matrix.translate(-(render_layer.position.origin.x as AzFloat),
+ -(render_layer.position.origin.y as AzFloat));
+
+ ctx.draw_target.set_transform(&matrix);
+
+ // Clear the buffer.
+ ctx.clear();
+
+ // Draw the display list.
+ profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || {
+ display_list.draw_into_context(&mut ctx, &matrix);
+ ctx.draw_target.flush();
+ });
+ }
+
+ // Extract the texture from the draw target and place it into its slot in the
+ // buffer. If using CPU rendering, upload it first.
+ //
+ // FIXME(pcwalton): We should supply the texture and native surface *to* the
+ // draw target in GPU rendering mode, so that it doesn't have to recreate it.
+ let buffer = match self.graphics_context {
+ CpuGraphicsContext => {
+ let mut buffer = match self.buffer_map.find(tile.screen_rect.size) {
+ Some(buffer) => {
+ let mut buffer = buffer;
+ buffer.rect = tile.page_rect;
+ buffer.screen_pos = tile.screen_rect;
+ buffer.resolution = scale;
+ buffer.native_surface.mark_wont_leak();
+ buffer.painted_with_cpu = true;
+ buffer.content_age = tile.content_age;
+ buffer
+ }
+ None => {
+ // Create an empty native surface. We mark it as not leaking
+ // in case it dies in transit to the compositor task.
+ let mut native_surface: NativeSurface =
+ layers::platform::surface::NativeSurfaceMethods::new(
+ native_graphics_context!(self),
+ Size2D(width as i32, height as i32),
+ width as i32 * 4);
+ native_surface.mark_wont_leak();
+
+ box LayerBuffer {
+ native_surface: native_surface,
+ rect: tile.page_rect,
+ screen_pos: tile.screen_rect,
+ resolution: scale,
+ stride: (width * 4) as uint,
+ painted_with_cpu: true,
+ content_age: tile.content_age,
+ }
+ }
+ };
+
+ draw_target.snapshot().get_data_surface().with_data(|data| {
+ buffer.native_surface.upload(native_graphics_context!(self), data);
+ debug!("RENDERER uploading to native surface {:d}",
+ buffer.native_surface.get_id() as int);
+ });
+
+ buffer
+ }
+ GpuGraphicsContext => {
+ draw_target.make_current();
+ let StolenGLResources {
+ surface: native_surface
+ } = draw_target.steal_gl_resources().unwrap();
+
+ // We mark the native surface as not leaking in case the surfaces
+ // die on their way to the compositor task.
+ let mut native_surface: NativeSurface =
+ NativeSurfaceAzureMethods::from_azure_surface(native_surface);
+ native_surface.mark_wont_leak();
+
+ box LayerBuffer {
+ native_surface: native_surface,
+ rect: tile.page_rect,
+ screen_pos: tile.screen_rect,
+ resolution: scale,
+ stride: (width * 4) as uint,
+ painted_with_cpu: false,
+ content_age: tile.content_age,
+ }
+ }
+ };
+
+ new_buffers.push(buffer);
+ }
+
+ let layer_buffer_set = box LayerBufferSet {
+ buffers: new_buffers,
+ };
+
+ replies.push((render_layer.id, layer_buffer_set));
+ })
+ }
+}
+
diff --git a/components/gfx/text/glyph.rs b/components/gfx/text/glyph.rs
new file mode 100644
index 00000000000..2ea2d7c5d2e
--- /dev/null
+++ b/components/gfx/text/glyph.rs
@@ -0,0 +1,752 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use servo_util::vec::*;
+use servo_util::range;
+use servo_util::range::{Range, RangeIndex, IntRangeIndex, EachIndex};
+use servo_util::geometry::Au;
+
+use std::cmp::{PartialOrd, PartialEq};
+use std::num::{NumCast, Zero};
+use std::mem;
+use std::u16;
+use std::vec::Vec;
+use geom::point::Point2D;
+
+/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
+///
+/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph
+/// per character), we pack glyph advance, glyph id, and some flags into a single u32.
+///
+/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or
+/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information
+/// in DetailedGlyphStore.
+#[deriving(Clone)]
+struct GlyphEntry {
+ value: u32,
+}
+
+impl GlyphEntry {
+ fn new(value: u32) -> GlyphEntry {
+ GlyphEntry {
+ value: value,
+ }
+ }
+
+ fn initial() -> GlyphEntry {
+ GlyphEntry::new(0)
+ }
+
+ // Creates a GlyphEntry for the common case
+ fn simple(id: GlyphId, advance: Au) -> GlyphEntry {
+ assert!(is_simple_glyph_id(id));
+ assert!(is_simple_advance(advance));
+
+ let id_mask = id as u32;
+ let Au(advance) = advance;
+ let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT as uint;
+
+ GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH)
+ }
+
+ // Create a GlyphEntry for uncommon case; should be accompanied by
+ // initialization of the actual DetailedGlyph data in DetailedGlyphStore
+ fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: int) -> GlyphEntry {
+ assert!(glyph_count <= u16::MAX as int);
+
+ debug!("creating complex glyph entry: starts_cluster={}, starts_ligature={}, \
+ glyph_count={}",
+ starts_cluster,
+ starts_ligature,
+ glyph_count);
+
+ let mut val = FLAG_NOT_MISSING;
+
+ if !starts_cluster {
+ val |= FLAG_NOT_CLUSTER_START;
+ }
+ if !starts_ligature {
+ val |= FLAG_NOT_LIGATURE_GROUP_START;
+ }
+ val |= (glyph_count as u32) << GLYPH_COUNT_SHIFT as uint;
+
+ GlyphEntry::new(val)
+ }
+
+ /// Create a GlyphEntry for the case where glyphs couldn't be found for the specified
+ /// character.
+ fn missing(glyph_count: int) -> GlyphEntry {
+ assert!(glyph_count <= u16::MAX as int);
+
+ GlyphEntry::new((glyph_count as u32) << GLYPH_COUNT_SHIFT as uint)
+ }
+}
+
+/// The id of a particular glyph within a font
+pub type GlyphId = u32;
+
+// TODO: unify with bit flags?
+#[deriving(PartialEq)]
+pub enum BreakType {
+ BreakTypeNone,
+ BreakTypeNormal,
+ BreakTypeHyphen,
+}
+
+static BREAK_TYPE_NONE: u8 = 0x0;
+static BREAK_TYPE_NORMAL: u8 = 0x1;
+static BREAK_TYPE_HYPHEN: u8 = 0x2;
+
+fn break_flag_to_enum(flag: u8) -> BreakType {
+ if (flag & BREAK_TYPE_NORMAL) != 0 {
+ BreakTypeNormal
+ } else if (flag & BREAK_TYPE_HYPHEN) != 0 {
+ BreakTypeHyphen
+ } else {
+ BreakTypeNone
+ }
+}
+
+fn break_enum_to_flag(e: BreakType) -> u8 {
+ match e {
+ BreakTypeNone => BREAK_TYPE_NONE,
+ BreakTypeNormal => BREAK_TYPE_NORMAL,
+ BreakTypeHyphen => BREAK_TYPE_HYPHEN,
+ }
+}
+
+// TODO: make this more type-safe.
+
+static FLAG_CHAR_IS_SPACE: u32 = 0x10000000;
+// These two bits store some BREAK_TYPE_* flags
+static FLAG_CAN_BREAK_MASK: u32 = 0x60000000;
+static FLAG_CAN_BREAK_SHIFT: u32 = 29;
+static FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000;
+
+// glyph advance; in Au's.
+static GLYPH_ADVANCE_MASK: u32 = 0x0FFF0000;
+static GLYPH_ADVANCE_SHIFT: u32 = 16;
+static GLYPH_ID_MASK: u32 = 0x0000FFFF;
+
+// Non-simple glyphs (more than one glyph per char; missing glyph,
+// newline, tab, large advance, or nonzero x/y offsets) may have one
+// or more detailed glyphs associated with them. They are stored in a
+// side array so that there is a 1:1 mapping of GlyphEntry to
+// unicode char.
+
+// The number of detailed glyphs for this char. If the char couldn't
+// be mapped to a glyph (!FLAG_NOT_MISSING), then this actually holds
+// the UTF8 code point instead.
+static GLYPH_COUNT_MASK: u32 = 0x00FFFF00;
+static GLYPH_COUNT_SHIFT: u32 = 8;
+// N.B. following Gecko, these are all inverted so that a lot of
+// missing chars can be memset with zeros in one fell swoop.
+static FLAG_NOT_MISSING: u32 = 0x00000001;
+static FLAG_NOT_CLUSTER_START: u32 = 0x00000002;
+static FLAG_NOT_LIGATURE_GROUP_START: u32 = 0x00000004;
+
+static FLAG_CHAR_IS_TAB: u32 = 0x00000008;
+static FLAG_CHAR_IS_NEWLINE: u32 = 0x00000010;
+//static FLAG_CHAR_IS_LOW_SURROGATE: u32 = 0x00000020;
+//static CHAR_IDENTITY_FLAGS_MASK: u32 = 0x00000038;
+
+fn is_simple_glyph_id(id: GlyphId) -> bool {
+ ((id as u32) & GLYPH_ID_MASK) == id
+}
+
+fn is_simple_advance(advance: Au) -> bool {
+ let unsignedAu = advance.to_u32().unwrap();
+ (unsignedAu & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT as uint)) == unsignedAu
+}
+
+type DetailedGlyphCount = u16;
+
+// Getters and setters for GlyphEntry. Setter methods are functional,
+// because GlyphEntry is immutable and only a u32 in size.
+impl GlyphEntry {
+ // getter methods
+ #[inline(always)]
+ fn advance(&self) -> Au {
+ NumCast::from((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT as uint).unwrap()
+ }
+
+ fn id(&self) -> GlyphId {
+ self.value & GLYPH_ID_MASK
+ }
+
+ fn is_ligature_start(&self) -> bool {
+ self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START)
+ }
+
+ fn is_cluster_start(&self) -> bool {
+ self.has_flag(!FLAG_NOT_CLUSTER_START)
+ }
+
+ // True if original char was normal (U+0020) space. Other chars may
+ // map to space glyph, but this does not account for them.
+ fn char_is_space(&self) -> bool {
+ self.has_flag(FLAG_CHAR_IS_SPACE)
+ }
+
+ fn char_is_tab(&self) -> bool {
+ !self.is_simple() && self.has_flag(FLAG_CHAR_IS_TAB)
+ }
+
+ fn char_is_newline(&self) -> bool {
+ !self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE)
+ }
+
+ fn can_break_before(&self) -> BreakType {
+ let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT as uint) as u8;
+ break_flag_to_enum(flag)
+ }
+
+ // setter methods
+ #[inline(always)]
+ fn set_char_is_space(&self) -> GlyphEntry {
+ GlyphEntry::new(self.value | FLAG_CHAR_IS_SPACE)
+ }
+
+ #[inline(always)]
+ fn set_char_is_tab(&self) -> GlyphEntry {
+ assert!(!self.is_simple());
+ GlyphEntry::new(self.value | FLAG_CHAR_IS_TAB)
+ }
+
+ #[inline(always)]
+ fn set_char_is_newline(&self) -> GlyphEntry {
+ assert!(!self.is_simple());
+ GlyphEntry::new(self.value | FLAG_CHAR_IS_NEWLINE)
+ }
+
+ #[inline(always)]
+ fn set_can_break_before(&self, e: BreakType) -> GlyphEntry {
+ let flag = (break_enum_to_flag(e) as u32) << FLAG_CAN_BREAK_SHIFT as uint;
+ GlyphEntry::new(self.value | flag)
+ }
+
+ // helper methods
+
+ fn glyph_count(&self) -> u16 {
+ assert!(!self.is_simple());
+ ((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT as uint) as u16
+ }
+
+ #[inline(always)]
+ fn is_simple(&self) -> bool {
+ self.has_flag(FLAG_IS_SIMPLE_GLYPH)
+ }
+
+ #[inline(always)]
+ fn has_flag(&self, flag: u32) -> bool {
+ (self.value & flag) != 0
+ }
+
+ #[inline(always)]
+ fn adapt_character_flags_of_entry(&self, other: GlyphEntry) -> GlyphEntry {
+ GlyphEntry { value: self.value | other.value }
+ }
+}
+
+// Stores data for a detailed glyph, in the case that several glyphs
+// correspond to one character, or the glyph's data couldn't be packed.
+#[deriving(Clone)]
+struct DetailedGlyph {
+ id: GlyphId,
+ // glyph's advance, in the text's direction (RTL or RTL)
+ advance: Au,
+ // glyph's offset from the font's em-box (from top-left)
+ offset: Point2D<Au>,
+}
+
+impl DetailedGlyph {
+ fn new(id: GlyphId, advance: Au, offset: Point2D<Au>) -> DetailedGlyph {
+ DetailedGlyph {
+ id: id,
+ advance: advance,
+ offset: offset,
+ }
+ }
+}
+
+#[deriving(PartialEq, Clone, Eq)]
+struct DetailedGlyphRecord {
+ // source string offset/GlyphEntry offset in the TextRun
+ entry_offset: CharIndex,
+ // offset into the detailed glyphs buffer
+ detail_offset: int,
+}
+
+impl PartialOrd for DetailedGlyphRecord {
+ fn partial_cmp(&self, other: &DetailedGlyphRecord) -> Option<Ordering> {
+ self.entry_offset.partial_cmp(&other.entry_offset)
+ }
+}
+
+impl Ord for DetailedGlyphRecord {
+ fn cmp(&self, other: &DetailedGlyphRecord) -> Ordering {
+ self.entry_offset.cmp(&other.entry_offset)
+ }
+}
+
+// Manages the lookup table for detailed glyphs. Sorting is deferred
+// until a lookup is actually performed; this matches the expected
+// usage pattern of setting/appending all the detailed glyphs, and
+// then querying without setting.
+struct DetailedGlyphStore {
+ // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
+ // optimization.
+ detail_buffer: Vec<DetailedGlyph>,
+ // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
+ // optimization.
+ detail_lookup: Vec<DetailedGlyphRecord>,
+ lookup_is_sorted: bool,
+}
+
+impl<'a> DetailedGlyphStore {
+ fn new() -> DetailedGlyphStore {
+ DetailedGlyphStore {
+ detail_buffer: vec!(), // TODO: default size?
+ detail_lookup: vec!(),
+ lookup_is_sorted: false,
+ }
+ }
+
+ fn add_detailed_glyphs_for_entry(&mut self, entry_offset: CharIndex, glyphs: &[DetailedGlyph]) {
+ let entry = DetailedGlyphRecord {
+ entry_offset: entry_offset,
+ detail_offset: self.detail_buffer.len() as int,
+ };
+
+ debug!("Adding entry[off={}] for detailed glyphs: {:?}", entry_offset, glyphs);
+
+ /* TODO: don't actually assert this until asserts are compiled
+ in/out based on severity, debug/release, etc. This assertion
+ would wreck the complexity of the lookup.
+
+ See Rust Issue #3647, #2228, #3627 for related information.
+
+ do self.detail_lookup.borrow |arr| {
+ assert !arr.contains(entry)
+ }
+ */
+
+ self.detail_lookup.push(entry);
+ self.detail_buffer.push_all(glyphs);
+ self.lookup_is_sorted = false;
+ }
+
+ fn get_detailed_glyphs_for_entry(&'a self, entry_offset: CharIndex, count: u16)
+ -> &'a [DetailedGlyph] {
+ debug!("Requesting detailed glyphs[n={}] for entry[off={}]", count, entry_offset);
+
+ // FIXME: Is this right? --pcwalton
+ // TODO: should fix this somewhere else
+ if count == 0 {
+ return self.detail_buffer.slice(0, 0);
+ }
+
+ assert!((count as uint) <= self.detail_buffer.len());
+ assert!(self.lookup_is_sorted);
+
+ let key = DetailedGlyphRecord {
+ entry_offset: entry_offset,
+ detail_offset: 0, // unused
+ };
+
+ let i = self.detail_lookup.as_slice().binary_search_index(&key)
+ .expect("Invalid index not found in detailed glyph lookup table!");
+
+ assert!(i + (count as uint) <= self.detail_buffer.len());
+ // return a slice into the buffer
+ self.detail_buffer.slice(i, i + count as uint)
+ }
+
+ fn get_detailed_glyph_with_index(&'a self,
+ entry_offset: CharIndex,
+ detail_offset: u16)
+ -> &'a DetailedGlyph {
+ assert!((detail_offset as uint) <= self.detail_buffer.len());
+ assert!(self.lookup_is_sorted);
+
+ let key = DetailedGlyphRecord {
+ entry_offset: entry_offset,
+ detail_offset: 0, // unused
+ };
+
+ let i = self.detail_lookup.as_slice().binary_search_index(&key)
+ .expect("Invalid index not found in detailed glyph lookup table!");
+
+ assert!(i + (detail_offset as uint) < self.detail_buffer.len());
+ &self.detail_buffer[i + (detail_offset as uint)]
+ }
+
+ fn ensure_sorted(&mut self) {
+ if self.lookup_is_sorted {
+ return;
+ }
+
+ // Sorting a unique vector is surprisingly hard. The follwing
+ // code is a good argument for using DVecs, but they require
+ // immutable locations thus don't play well with freezing.
+
+ // Thar be dragons here. You have been warned. (Tips accepted.)
+ let mut unsorted_records: Vec<DetailedGlyphRecord> = vec!();
+ mem::swap(&mut self.detail_lookup, &mut unsorted_records);
+ let mut mut_records : Vec<DetailedGlyphRecord> = unsorted_records;
+ mut_records.sort_by(|a, b| {
+ if a < b {
+ Less
+ } else {
+ Greater
+ }
+ });
+ let mut sorted_records = mut_records;
+ mem::swap(&mut self.detail_lookup, &mut sorted_records);
+
+ self.lookup_is_sorted = true;
+ }
+}
+
+// This struct is used by GlyphStore clients to provide new glyph data.
+// It should be allocated on the stack and passed by reference to GlyphStore.
+pub struct GlyphData {
+ id: GlyphId,
+ advance: Au,
+ offset: Point2D<Au>,
+ is_missing: bool,
+ cluster_start: bool,
+ ligature_start: bool,
+}
+
+impl GlyphData {
+ pub fn new(id: GlyphId,
+ advance: Au,
+ offset: Option<Point2D<Au>>,
+ is_missing: bool,
+ cluster_start: bool,
+ ligature_start: bool)
+ -> GlyphData {
+ GlyphData {
+ id: id,
+ advance: advance,
+ offset: offset.unwrap_or(Zero::zero()),
+ is_missing: is_missing,
+ cluster_start: cluster_start,
+ ligature_start: ligature_start,
+ }
+ }
+}
+
+// This enum is a proxy that's provided to GlyphStore clients when iterating
+// through glyphs (either for a particular TextRun offset, or all glyphs).
+// Rather than eagerly assembling and copying glyph data, it only retrieves
+// values as they are needed from the GlyphStore, using provided offsets.
+pub enum GlyphInfo<'a> {
+ SimpleGlyphInfo(&'a GlyphStore, CharIndex),
+ DetailGlyphInfo(&'a GlyphStore, CharIndex, u16),
+}
+
+impl<'a> GlyphInfo<'a> {
+ pub fn id(self) -> GlyphId {
+ match self {
+ SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].id(),
+ DetailGlyphInfo(store, entry_i, detail_j) => {
+ store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).id
+ }
+ }
+ }
+
+ #[inline(always)]
+ // FIXME: Resolution conflicts with IteratorUtil trait so adding trailing _
+ pub fn advance(self) -> Au {
+ match self {
+ SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].advance(),
+ DetailGlyphInfo(store, entry_i, detail_j) => {
+ store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).advance
+ }
+ }
+ }
+
+ pub fn offset(self) -> Option<Point2D<Au>> {
+ match self {
+ SimpleGlyphInfo(_, _) => None,
+ DetailGlyphInfo(store, entry_i, detail_j) => {
+ Some(store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).offset)
+ }
+ }
+ }
+}
+
+/// Stores the glyph data belonging to a text run.
+///
+/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are
+/// stored as pointers into the `detail_store`.
+///
+/// ~~~
+/// +- GlyphStore --------------------------------+
+/// | +---+---+---+---+---+---+---+ |
+/// | entry_buffer: | | s | | s | | s | s | | d = detailed
+/// | +-|-+---+-|-+---+-|-+---+---+ | s = simple
+/// | | | | |
+/// | | +---+-------+ |
+/// | | | |
+/// | +-V-+-V-+ |
+/// | detail_store: | d | d | |
+/// | +---+---+ |
+/// +---------------------------------------------+
+/// ~~~
+pub struct GlyphStore {
+ // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
+ // optimization.
+ /// A buffer of glyphs within the text run, in the order in which they
+ /// appear in the input text
+ entry_buffer: Vec<GlyphEntry>,
+ /// A store of the detailed glyph data. Detailed glyphs contained in the
+ /// `entry_buffer` point to locations in this data structure.
+ detail_store: DetailedGlyphStore,
+
+ is_whitespace: bool,
+}
+
+int_range_index! {
+ #[deriving(Encodable)]
+ #[doc = "An index that refers to a character in a text run. This could \
+ point to the middle of a glyph."]
+ struct CharIndex(int)
+}
+
+impl<'a> GlyphStore {
+ // Initializes the glyph store, but doesn't actually shape anything.
+ // Use the set_glyph, set_glyphs() methods to store glyph data.
+ pub fn new(length: int, is_whitespace: bool) -> GlyphStore {
+ assert!(length > 0);
+
+ GlyphStore {
+ entry_buffer: Vec::from_elem(length as uint, GlyphEntry::initial()),
+ detail_store: DetailedGlyphStore::new(),
+ is_whitespace: is_whitespace,
+ }
+ }
+
+ pub fn char_len(&self) -> CharIndex {
+ CharIndex(self.entry_buffer.len() as int)
+ }
+
+ pub fn is_whitespace(&self) -> bool {
+ self.is_whitespace
+ }
+
+ pub fn finalize_changes(&mut self) {
+ self.detail_store.ensure_sorted();
+ }
+
+ pub fn add_glyph_for_char_index(&mut self, i: CharIndex, data: &GlyphData) {
+ fn glyph_is_compressible(data: &GlyphData) -> bool {
+ is_simple_glyph_id(data.id)
+ && is_simple_advance(data.advance)
+ && data.offset.is_zero()
+ && data.cluster_start // others are stored in detail buffer
+ }
+
+ assert!(data.ligature_start); // can't compress ligature continuation glyphs.
+ assert!(i < self.char_len());
+
+ let entry = match (data.is_missing, glyph_is_compressible(data)) {
+ (true, _) => GlyphEntry::missing(1),
+ (false, true) => GlyphEntry::simple(data.id, data.advance),
+ (false, false) => {
+ let glyph = [DetailedGlyph::new(data.id, data.advance, data.offset)];
+ self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
+ GlyphEntry::complex(data.cluster_start, data.ligature_start, 1)
+ }
+ }.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
+
+ *self.entry_buffer.get_mut(i.to_uint()) = entry;
+ }
+
+ pub fn add_glyphs_for_char_index(&mut self, i: CharIndex, data_for_glyphs: &[GlyphData]) {
+ assert!(i < self.char_len());
+ assert!(data_for_glyphs.len() > 0);
+
+ let glyph_count = data_for_glyphs.len() as int;
+
+ let first_glyph_data = data_for_glyphs[0];
+ let entry = match first_glyph_data.is_missing {
+ true => GlyphEntry::missing(glyph_count),
+ false => {
+ let glyphs_vec = Vec::from_fn(glyph_count as uint, |i| {
+ DetailedGlyph::new(data_for_glyphs[i].id,
+ data_for_glyphs[i].advance,
+ data_for_glyphs[i].offset)
+ });
+
+ self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec.as_slice());
+ GlyphEntry::complex(first_glyph_data.cluster_start,
+ first_glyph_data.ligature_start,
+ glyph_count)
+ }
+ }.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
+
+ debug!("Adding multiple glyphs[idx={}, count={}]: {:?}", i, glyph_count, entry);
+
+ *self.entry_buffer.get_mut(i.to_uint()) = entry;
+ }
+
+ // used when a character index has no associated glyph---for example, a ligature continuation.
+ pub fn add_nonglyph_for_char_index(&mut self, i: CharIndex, cluster_start: bool, ligature_start: bool) {
+ assert!(i < self.char_len());
+
+ let entry = GlyphEntry::complex(cluster_start, ligature_start, 0);
+ debug!("adding spacer for chracter without associated glyph[idx={}]", i);
+
+ *self.entry_buffer.get_mut(i.to_uint()) = entry;
+ }
+
+ pub fn iter_glyphs_for_char_index(&'a self, i: CharIndex) -> GlyphIterator<'a> {
+ self.iter_glyphs_for_char_range(&Range::new(i, CharIndex(1)))
+ }
+
+ #[inline]
+ pub fn iter_glyphs_for_char_range(&'a self, rang: &Range<CharIndex>) -> GlyphIterator<'a> {
+ if rang.begin() >= self.char_len() {
+ fail!("iter_glyphs_for_range: range.begin beyond length!");
+ }
+ if rang.end() > self.char_len() {
+ fail!("iter_glyphs_for_range: range.end beyond length!");
+ }
+
+ GlyphIterator {
+ store: self,
+ char_index: rang.begin(),
+ char_range: rang.each_index(),
+ glyph_range: None,
+ }
+ }
+
+ #[inline]
+ pub fn advance_for_char_range(&self, rang: &Range<CharIndex>) -> Au {
+ self.iter_glyphs_for_char_range(rang)
+ .fold(Au(0), |advance, (_, glyph)| advance + glyph.advance())
+ }
+
+ // getter methods
+ pub fn char_is_space(&self, i: CharIndex) -> bool {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].char_is_space()
+ }
+
+ pub fn char_is_tab(&self, i: CharIndex) -> bool {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].char_is_tab()
+ }
+
+ pub fn char_is_newline(&self, i: CharIndex) -> bool {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].char_is_newline()
+ }
+
+ pub fn is_ligature_start(&self, i: CharIndex) -> bool {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].is_ligature_start()
+ }
+
+ pub fn is_cluster_start(&self, i: CharIndex) -> bool {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].is_cluster_start()
+ }
+
+ pub fn can_break_before(&self, i: CharIndex) -> BreakType {
+ assert!(i < self.char_len());
+ self.entry_buffer[i.to_uint()].can_break_before()
+ }
+
+ // setter methods
+ pub fn set_char_is_space(&mut self, i: CharIndex) {
+ assert!(i < self.char_len());
+ let entry = self.entry_buffer[i.to_uint()];
+ *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_space();
+ }
+
+ pub fn set_char_is_tab(&mut self, i: CharIndex) {
+ assert!(i < self.char_len());
+ let entry = self.entry_buffer[i.to_uint()];
+ *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_tab();
+ }
+
+ pub fn set_char_is_newline(&mut self, i: CharIndex) {
+ assert!(i < self.char_len());
+ let entry = self.entry_buffer[i.to_uint()];
+ *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_newline();
+ }
+
+ pub fn set_can_break_before(&mut self, i: CharIndex, t: BreakType) {
+ assert!(i < self.char_len());
+ let entry = self.entry_buffer[i.to_uint()];
+ *self.entry_buffer.get_mut(i.to_uint()) = entry.set_can_break_before(t);
+ }
+}
+
+/// An iterator over the glyphs in a character range in a `GlyphStore`.
+pub struct GlyphIterator<'a> {
+ store: &'a GlyphStore,
+ char_index: CharIndex,
+ char_range: EachIndex<int, CharIndex>,
+ glyph_range: Option<EachIndex<int, CharIndex>>,
+}
+
+impl<'a> GlyphIterator<'a> {
+ // Slow path when there is a glyph range.
+ #[inline(never)]
+ fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> {
+ match self.glyph_range.get_mut_ref().next() {
+ Some(j) => Some((self.char_index,
+ DetailGlyphInfo(self.store, self.char_index, j.get() as u16 /* ??? */))),
+ None => {
+ // No more glyphs for current character. Try to get another.
+ self.glyph_range = None;
+ self.next()
+ }
+ }
+ }
+
+ // Slow path when there is a complex glyph.
+ #[inline(never)]
+ fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: CharIndex)
+ -> Option<(CharIndex, GlyphInfo<'a>)> {
+ let glyphs = self.store.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count());
+ self.glyph_range = Some(range::each_index(CharIndex(0), CharIndex(glyphs.len() as int)));
+ self.next()
+ }
+}
+
+impl<'a> Iterator<(CharIndex, GlyphInfo<'a>)> for GlyphIterator<'a> {
+ // I tried to start with something simpler and apply FlatMap, but the
+ // inability to store free variables in the FlatMap struct was problematic.
+ //
+ // This function consists of the fast path and is designed to be inlined into its caller. The
+ // slow paths, which should not be inlined, are `next_glyph_range()` and
+ // `next_complex_glyph()`.
+ #[inline(always)]
+ fn next(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> {
+ // Would use 'match' here but it borrows contents in a way that
+ // interferes with mutation.
+ if self.glyph_range.is_some() {
+ self.next_glyph_range()
+ } else {
+ // No glyph range. Look at next character.
+ self.char_range.next().and_then(|i| {
+ self.char_index = i;
+ assert!(i < self.store.char_len());
+ let entry = self.store.entry_buffer[i.to_uint()];
+ if entry.is_simple() {
+ Some((self.char_index, SimpleGlyphInfo(self.store, i)))
+ } else {
+ // Fall back to the slow path.
+ self.next_complex_glyph(&entry, i)
+ }
+ })
+ }
+ }
+}
diff --git a/components/gfx/text/mod.rs b/components/gfx/text/mod.rs
new file mode 100644
index 00000000000..f705347c441
--- /dev/null
+++ b/components/gfx/text/mod.rs
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This file exists just to make it easier to import things inside of
+ ./text/ without specifying the file they came out of imports.
+
+Note that you still must define each of the files as a module in
+servo.rc. This is not ideal and may be changed in the future. */
+
+pub use text::shaping::Shaper;
+pub use text::text_run::TextRun;
+
+pub mod glyph;
+#[path="shaping/mod.rs"] pub mod shaping;
+pub mod text_run;
+pub mod util;
+
diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs
new file mode 100644
index 00000000000..789126e767d
--- /dev/null
+++ b/components/gfx/text/shaping/harfbuzz.rs
@@ -0,0 +1,541 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate harfbuzz;
+
+use font::{Font, FontHandleMethods, FontTableMethods, FontTableTag};
+use platform::font::FontTable;
+use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData};
+use text::shaping::ShaperMethods;
+use text::util::{float_to_fixed, fixed_to_float};
+
+use geom::Point2D;
+use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR};
+use harfbuzz::{hb_blob_create, hb_face_create_for_tables};
+use harfbuzz::{hb_blob_t};
+use harfbuzz::{hb_bool_t};
+use harfbuzz::{hb_buffer_add_utf8};
+use harfbuzz::{hb_buffer_destroy};
+use harfbuzz::{hb_buffer_get_glyph_positions};
+use harfbuzz::{hb_buffer_set_direction};
+use harfbuzz::{hb_face_destroy};
+use harfbuzz::{hb_face_t, hb_font_t};
+use harfbuzz::{hb_font_create};
+use harfbuzz::{hb_font_destroy, hb_buffer_create};
+use harfbuzz::{hb_font_funcs_create};
+use harfbuzz::{hb_font_funcs_destroy};
+use harfbuzz::{hb_font_funcs_set_glyph_func};
+use harfbuzz::{hb_font_funcs_set_glyph_h_advance_func};
+use harfbuzz::{hb_font_funcs_set_glyph_h_kerning_func};
+use harfbuzz::{hb_font_funcs_t, hb_buffer_t, hb_codepoint_t};
+use harfbuzz::{hb_font_set_funcs};
+use harfbuzz::{hb_font_set_ppem};
+use harfbuzz::{hb_font_set_scale};
+use harfbuzz::{hb_glyph_info_t};
+use harfbuzz::{hb_glyph_position_t};
+use harfbuzz::{hb_position_t, hb_tag_t};
+use harfbuzz::{hb_shape, hb_buffer_get_glyph_infos};
+use libc::{c_uint, c_int, c_void, c_char};
+use servo_util::geometry::Au;
+use servo_util::range::Range;
+use std::mem;
+use std::char;
+use std::cmp;
+use std::ptr;
+
+static NO_GLYPH: i32 = -1;
+static CONTINUATION_BYTE: i32 = -2;
+
+pub struct ShapedGlyphData {
+ count: int,
+ glyph_infos: *mut hb_glyph_info_t,
+ pos_infos: *mut hb_glyph_position_t,
+}
+
+pub struct ShapedGlyphEntry {
+ codepoint: GlyphId,
+ advance: Au,
+ offset: Option<Point2D<Au>>,
+}
+
+impl ShapedGlyphData {
+ pub fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData {
+ unsafe {
+ let mut glyph_count = 0;
+ let glyph_infos = hb_buffer_get_glyph_infos(buffer, &mut glyph_count);
+ let glyph_count = glyph_count as int;
+ assert!(glyph_infos.is_not_null());
+ let mut pos_count = 0;
+ let pos_infos = hb_buffer_get_glyph_positions(buffer, &mut pos_count);
+ let pos_count = pos_count as int;
+ assert!(pos_infos.is_not_null());
+ assert!(glyph_count == pos_count);
+
+ ShapedGlyphData {
+ count: glyph_count,
+ glyph_infos: glyph_infos,
+ pos_infos: pos_infos,
+ }
+ }
+ }
+
+ #[inline(always)]
+ fn byte_offset_of_glyph(&self, i: int) -> int {
+ assert!(i < self.count);
+
+ unsafe {
+ let glyph_info_i = self.glyph_infos.offset(i);
+ (*glyph_info_i).cluster as int
+ }
+ }
+
+ pub fn len(&self) -> int {
+ self.count
+ }
+
+ /// Returns shaped glyph data for one glyph, and updates the y-position of the pen.
+ pub fn get_entry_for_glyph(&self, i: int, y_pos: &mut Au) -> ShapedGlyphEntry {
+ assert!(i < self.count);
+
+ unsafe {
+ let glyph_info_i = self.glyph_infos.offset(i);
+ let pos_info_i = self.pos_infos.offset(i);
+ let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset);
+ let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset);
+ let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance);
+ let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance);
+
+ let x_offset = Au::from_frac_px(x_offset);
+ let y_offset = Au::from_frac_px(y_offset);
+ let x_advance = Au::from_frac_px(x_advance);
+ let y_advance = Au::from_frac_px(y_advance);
+
+ let offset = if x_offset == Au(0) && y_offset == Au(0) && y_advance == Au(0) {
+ None
+ } else {
+ // adjust the pen..
+ if y_advance > Au(0) {
+ *y_pos = *y_pos - y_advance;
+ }
+
+ Some(Point2D(x_offset, *y_pos - y_offset))
+ };
+
+ ShapedGlyphEntry {
+ codepoint: (*glyph_info_i).codepoint as GlyphId,
+ advance: x_advance,
+ offset: offset,
+ }
+ }
+ }
+}
+
+pub struct Shaper {
+ hb_face: *mut hb_face_t,
+ hb_font: *mut hb_font_t,
+ hb_funcs: *mut hb_font_funcs_t,
+}
+
+#[unsafe_destructor]
+impl Drop for Shaper {
+ fn drop(&mut self) {
+ unsafe {
+ assert!(self.hb_face.is_not_null());
+ hb_face_destroy(self.hb_face);
+
+ assert!(self.hb_font.is_not_null());
+ hb_font_destroy(self.hb_font);
+
+ assert!(self.hb_funcs.is_not_null());
+ hb_font_funcs_destroy(self.hb_funcs);
+ }
+ }
+}
+
+impl Shaper {
+ pub fn new(font: &mut Font) -> Shaper {
+ unsafe {
+ // Indirection for Rust Issue #6248, dynamic freeze scope artifically extended
+ let font_ptr = font as *mut Font;
+ let hb_face: *mut hb_face_t = hb_face_create_for_tables(get_font_table_func,
+ font_ptr as *mut c_void,
+ None);
+ let hb_font: *mut hb_font_t = hb_font_create(hb_face);
+
+ // Set points-per-em. if zero, performs no hinting in that direction.
+ let pt_size = font.pt_size;
+ hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
+
+ // Set scaling. Note that this takes 16.16 fixed point.
+ hb_font_set_scale(hb_font,
+ Shaper::float_to_fixed(pt_size) as c_int,
+ Shaper::float_to_fixed(pt_size) as c_int);
+
+ // configure static function callbacks.
+ // NB. This funcs structure could be reused globally, as it never changes.
+ let hb_funcs: *mut hb_font_funcs_t = hb_font_funcs_create();
+ hb_font_funcs_set_glyph_func(hb_funcs, glyph_func, ptr::mut_null(), None);
+ hb_font_funcs_set_glyph_h_advance_func(hb_funcs, glyph_h_advance_func, ptr::mut_null(), None);
+ hb_font_funcs_set_glyph_h_kerning_func(hb_funcs, glyph_h_kerning_func, ptr::mut_null(), ptr::mut_null());
+ hb_font_set_funcs(hb_font, hb_funcs, font_ptr as *mut c_void, None);
+
+ Shaper {
+ hb_face: hb_face,
+ hb_font: hb_font,
+ hb_funcs: hb_funcs,
+ }
+ }
+ }
+
+ fn float_to_fixed(f: f64) -> i32 {
+ float_to_fixed(16, f)
+ }
+
+ fn fixed_to_float(i: hb_position_t) -> f64 {
+ fixed_to_float(16, i)
+ }
+}
+
+impl ShaperMethods for Shaper {
+ /// Calculate the layout metrics associated with the given text when rendered in a specific
+ /// font.
+ fn shape_text(&self, text: &str, glyphs: &mut GlyphStore) {
+ unsafe {
+ let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
+ hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR);
+
+ hb_buffer_add_utf8(hb_buffer,
+ text.as_ptr() as *const c_char,
+ text.len() as c_int,
+ 0,
+ text.len() as c_int);
+
+ hb_shape(self.hb_font, hb_buffer, ptr::mut_null(), 0);
+ self.save_glyph_results(text, glyphs, hb_buffer);
+ hb_buffer_destroy(hb_buffer);
+ }
+ }
+}
+
+impl Shaper {
+ fn save_glyph_results(&self, text: &str, glyphs: &mut GlyphStore, buffer: *mut hb_buffer_t) {
+ let glyph_data = ShapedGlyphData::new(buffer);
+ let glyph_count = glyph_data.len();
+ let byte_max = text.len() as int;
+ let char_max = text.char_len() as int;
+
+ // GlyphStore records are indexed by character, not byte offset.
+ // so, we must be careful to increment this when saving glyph entries.
+ let mut char_idx = CharIndex(0);
+
+ assert!(glyph_count <= char_max);
+
+ debug!("Shaped text[char count={}], got back {} glyph info records.",
+ char_max,
+ glyph_count);
+
+ if char_max != glyph_count {
+ debug!("NOTE: Since these are not equal, we probably have been given some complex \
+ glyphs.");
+ }
+
+ // make map of what chars have glyphs
+ let mut byteToGlyph: Vec<i32>;
+
+ // fast path: all chars are single-byte.
+ if byte_max == char_max {
+ byteToGlyph = Vec::from_elem(byte_max as uint, NO_GLYPH);
+ } else {
+ byteToGlyph = Vec::from_elem(byte_max as uint, CONTINUATION_BYTE);
+ for (i, _) in text.char_indices() {
+ *byteToGlyph.get_mut(i) = NO_GLYPH;
+ }
+ }
+
+ debug!("(glyph idx) -> (text byte offset)");
+ for i in range(0, glyph_data.len()) {
+ // loc refers to a *byte* offset within the utf8 string.
+ let loc = glyph_data.byte_offset_of_glyph(i);
+ if loc < byte_max {
+ assert!(*byteToGlyph.get(loc as uint) != CONTINUATION_BYTE);
+ *byteToGlyph.get_mut(loc as uint) = i as i32;
+ } else {
+ debug!("ERROR: tried to set out of range byteToGlyph: idx={}, glyph idx={}",
+ loc,
+ i);
+ }
+ debug!("{} -> {}", i, loc);
+ }
+
+ debug!("text: {:s}", text);
+ debug!("(char idx): char->(glyph index):");
+ for (i, ch) in text.char_indices() {
+ debug!("{}: {} --> {:d}", i, ch, *byteToGlyph.get(i) as int);
+ }
+
+ // some helpers
+ let mut glyph_span: Range<int> = Range::empty();
+ // this span contains first byte of first char, to last byte of last char in range.
+ // so, end() points to first byte of last+1 char, if it's less than byte_max.
+ let mut char_byte_span: Range<int> = Range::empty();
+ let mut y_pos = Au(0);
+
+ // main loop over each glyph. each iteration usually processes 1 glyph and 1+ chars.
+ // in cases with complex glyph-character assocations, 2+ glyphs and 1+ chars can be
+ // processed.
+ while glyph_span.begin() < glyph_count {
+ // start by looking at just one glyph.
+ glyph_span.extend_by(1);
+ debug!("Processing glyph at idx={}", glyph_span.begin());
+
+ let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin());
+ char_byte_span.reset(char_byte_start, 0);
+
+ // find a range of chars corresponding to this glyph, plus
+ // any trailing chars that do not have associated glyphs.
+ while char_byte_span.end() < byte_max {
+ let range = text.char_range_at(char_byte_span.end() as uint);
+ drop(range.ch);
+ char_byte_span.extend_to(range.next as int);
+
+ debug!("Processing char byte span: off={}, len={} for glyph idx={}",
+ char_byte_span.begin(), char_byte_span.length(), glyph_span.begin());
+
+ while char_byte_span.end() != byte_max &&
+ byteToGlyph[char_byte_span.end() as uint] == NO_GLYPH {
+ debug!("Extending char byte span to include byte offset={} with no associated \
+ glyph", char_byte_span.end());
+ let range = text.char_range_at(char_byte_span.end() as uint);
+ drop(range.ch);
+ char_byte_span.extend_to(range.next as int);
+ }
+
+ // extend glyph range to max glyph index covered by char_span,
+ // in cases where one char made several glyphs and left some unassociated chars.
+ let mut max_glyph_idx = glyph_span.end();
+ for i in char_byte_span.each_index() {
+ if byteToGlyph[i as uint] > NO_GLYPH {
+ max_glyph_idx = cmp::max(byteToGlyph[i as uint] as int + 1, max_glyph_idx);
+ }
+ }
+
+ if max_glyph_idx > glyph_span.end() {
+ glyph_span.extend_to(max_glyph_idx);
+ debug!("Extended glyph span (off={}, len={}) to cover char byte span's max \
+ glyph index",
+ glyph_span.begin(), glyph_span.length());
+ }
+
+
+ // if there's just one glyph, then we don't need further checks.
+ if glyph_span.length() == 1 { break; }
+
+ // if no glyphs were found yet, extend the char byte range more.
+ if glyph_span.length() == 0 { continue; }
+
+ debug!("Complex (multi-glyph to multi-char) association found. This case \
+ probably doesn't work.");
+
+ let mut all_glyphs_are_within_cluster: bool = true;
+ for j in glyph_span.each_index() {
+ let loc = glyph_data.byte_offset_of_glyph(j);
+ if !char_byte_span.contains(loc) {
+ all_glyphs_are_within_cluster = false;
+ break
+ }
+ }
+
+ debug!("All glyphs within char_byte_span cluster?: {}",
+ all_glyphs_are_within_cluster);
+
+ // found a valid range; stop extending char_span.
+ if all_glyphs_are_within_cluster {
+ break
+ }
+ }
+
+ // character/glyph clump must contain characters.
+ assert!(char_byte_span.length() > 0);
+ // character/glyph clump must contain glyphs.
+ assert!(glyph_span.length() > 0);
+
+ // now char_span is a ligature clump, formed by the glyphs in glyph_span.
+ // we need to find the chars that correspond to actual glyphs (char_extended_span),
+ //and set glyph info for those and empty infos for the chars that are continuations.
+
+ // a simple example:
+ // chars: 'f' 't' 't'
+ // glyphs: 'ftt' '' ''
+ // cgmap: t f f
+ // gspan: [-]
+ // cspan: [-]
+ // covsp: [---------------]
+
+ let mut covered_byte_span = char_byte_span.clone();
+ // extend, clipping at end of text range.
+ while covered_byte_span.end() < byte_max
+ && byteToGlyph[covered_byte_span.end() as uint] == NO_GLYPH {
+ let range = text.char_range_at(covered_byte_span.end() as uint);
+ drop(range.ch);
+ covered_byte_span.extend_to(range.next as int);
+ }
+
+ if covered_byte_span.begin() >= byte_max {
+ // oops, out of range. clip and forget this clump.
+ let end = glyph_span.end(); // FIXME: borrow checker workaround
+ glyph_span.reset(end, 0);
+ let end = char_byte_span.end(); // FIXME: borrow checker workaround
+ char_byte_span.reset(end, 0);
+ }
+
+ // clamp to end of text. (I don't think this will be necessary, but..)
+ let end = covered_byte_span.end(); // FIXME: borrow checker workaround
+ covered_byte_span.extend_to(cmp::min(end, byte_max));
+
+ // fast path: 1-to-1 mapping of single char and single glyph.
+ if glyph_span.length() == 1 {
+ // TODO(Issue #214): cluster ranges need to be computed before
+ // shaping, and then consulted here.
+ // for now, just pretend that every character is a cluster start.
+ // (i.e., pretend there are no combining character sequences).
+ // 1-to-1 mapping of character to glyph also treated as ligature start.
+ let shape = glyph_data.get_entry_for_glyph(glyph_span.begin(), &mut y_pos);
+ let data = GlyphData::new(shape.codepoint,
+ shape.advance,
+ shape.offset,
+ false,
+ true,
+ true);
+ glyphs.add_glyph_for_char_index(char_idx, &data);
+ } else {
+ // collect all glyphs to be assigned to the first character.
+ let mut datas = vec!();
+
+ for glyph_i in glyph_span.each_index() {
+ let shape = glyph_data.get_entry_for_glyph(glyph_i, &mut y_pos);
+ datas.push(GlyphData::new(shape.codepoint,
+ shape.advance,
+ shape.offset,
+ false, // not missing
+ true, // treat as cluster start
+ glyph_i > glyph_span.begin()));
+ // all but first are ligature continuations
+ }
+
+ // now add the detailed glyph entry.
+ glyphs.add_glyphs_for_char_index(char_idx, datas.as_slice());
+
+ // set the other chars, who have no glyphs
+ let mut i = covered_byte_span.begin();
+ loop {
+ let range = text.char_range_at(i as uint);
+ drop(range.ch);
+ i = range.next as int;
+ if i >= covered_byte_span.end() { break; }
+ char_idx = char_idx + CharIndex(1);
+ glyphs.add_nonglyph_for_char_index(char_idx, false, false);
+ }
+ }
+
+ // shift up our working spans past things we just handled.
+ let end = glyph_span.end(); // FIXME: borrow checker workaround
+ glyph_span.reset(end, 0);
+ let end = char_byte_span.end();; // FIXME: borrow checker workaround
+ char_byte_span.reset(end, 0);
+ char_idx = char_idx + CharIndex(1);
+ }
+
+ // this must be called after adding all glyph data; it sorts the
+ // lookup table for finding detailed glyphs by associated char index.
+ glyphs.finalize_changes();
+ }
+}
+
+/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
+extern fn glyph_func(_: *mut hb_font_t,
+ font_data: *mut c_void,
+ unicode: hb_codepoint_t,
+ _: hb_codepoint_t,
+ glyph: *mut hb_codepoint_t,
+ _: *mut c_void)
+ -> hb_bool_t {
+ let font: *const Font = font_data as *const Font;
+ assert!(font.is_not_null());
+
+ unsafe {
+ match (*font).glyph_index(char::from_u32(unicode).unwrap()) {
+ Some(g) => {
+ *glyph = g as hb_codepoint_t;
+ true as hb_bool_t
+ }
+ None => false as hb_bool_t
+ }
+ }
+}
+
+extern fn glyph_h_advance_func(_: *mut hb_font_t,
+ font_data: *mut c_void,
+ glyph: hb_codepoint_t,
+ _: *mut c_void)
+ -> hb_position_t {
+ let font: *mut Font = font_data as *mut Font;
+ assert!(font.is_not_null());
+
+ unsafe {
+ let advance = (*font).glyph_h_advance(glyph as GlyphId);
+ Shaper::float_to_fixed(advance)
+ }
+}
+
+extern fn glyph_h_kerning_func(_: *mut hb_font_t,
+ font_data: *mut c_void,
+ first_glyph: hb_codepoint_t,
+ second_glyph: hb_codepoint_t,
+ _: *mut c_void)
+ -> hb_position_t {
+ let font: *mut Font = font_data as *mut Font;
+ assert!(font.is_not_null());
+
+ unsafe {
+ let advance = (*font).glyph_h_kerning(first_glyph as GlyphId, second_glyph as GlyphId);
+ Shaper::float_to_fixed(advance)
+ }
+}
+
+// Callback to get a font table out of a font.
+extern fn get_font_table_func(_: *mut hb_face_t, tag: hb_tag_t, user_data: *mut c_void) -> *mut hb_blob_t {
+ unsafe {
+ let font: *const Font = user_data as *const Font;
+ assert!(font.is_not_null());
+
+ // TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
+ match (*font).get_table_for_tag(tag as FontTableTag) {
+ None => ptr::mut_null(),
+ Some(ref font_table) => {
+ let skinny_font_table_ptr: *const FontTable = font_table; // private context
+
+ let mut blob: *mut hb_blob_t = ptr::mut_null();
+ (*skinny_font_table_ptr).with_buffer(|buf: *const u8, len: uint| {
+ // HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed.
+ blob = hb_blob_create(buf as *const c_char,
+ len as c_uint,
+ HB_MEMORY_MODE_READONLY,
+ mem::transmute(skinny_font_table_ptr),
+ destroy_blob_func);
+ });
+
+ assert!(blob.is_not_null());
+ blob
+ }
+ }
+ }
+}
+
+// TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
+// In particular, we'll need to cast to a boxed, rather than owned, FontTable.
+
+// even better, should cache the harfbuzz blobs directly instead of recreating a lot.
+extern fn destroy_blob_func(_: *mut c_void) {
+ // TODO: Previous code here was broken. Rewrite.
+}
diff --git a/components/gfx/text/shaping/mod.rs b/components/gfx/text/shaping/mod.rs
new file mode 100644
index 00000000000..ef4bc2088f0
--- /dev/null
+++ b/components/gfx/text/shaping/mod.rs
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Shaper encapsulates a specific shaper, such as Harfbuzz,
+//! Uniscribe, Pango, or Coretext.
+//!
+//! Currently, only harfbuzz bindings are implemented.
+
+use text::glyph::GlyphStore;
+
+pub use Shaper = text::shaping::harfbuzz::Shaper;
+
+pub mod harfbuzz;
+
+pub trait ShaperMethods {
+ fn shape_text(&self, text: &str, glyphs: &mut GlyphStore);
+}
+
diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs
new file mode 100644
index 00000000000..70c10f1c64c
--- /dev/null
+++ b/components/gfx/text/text_run.rs
@@ -0,0 +1,271 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use font::{Font, RunMetrics, FontMetrics};
+use servo_util::geometry::Au;
+use servo_util::range::Range;
+use servo_util::vec::{Comparator, FullBinarySearchMethods};
+use std::slice::Items;
+use sync::Arc;
+use text::glyph::{CharIndex, GlyphStore};
+use font::FontHandleMethods;
+use platform::font_template::FontTemplateData;
+
+/// A single "paragraph" of text in one font size and style.
+#[deriving(Clone)]
+pub struct TextRun {
+ pub text: Arc<String>,
+ pub font_template: Arc<FontTemplateData>,
+ pub pt_size: f64,
+ pub font_metrics: FontMetrics,
+ /// The glyph runs that make up this text run.
+ pub glyphs: Arc<Vec<GlyphRun>>,
+}
+
+/// A single series of glyphs within a text run.
+#[deriving(Clone)]
+pub struct GlyphRun {
+ /// The glyphs.
+ glyph_store: Arc<GlyphStore>,
+ /// The range of characters in the containing run.
+ range: Range<CharIndex>,
+}
+
+pub struct SliceIterator<'a> {
+ glyph_iter: Items<'a, GlyphRun>,
+ range: Range<CharIndex>,
+}
+
+struct CharIndexComparator;
+
+impl Comparator<CharIndex,GlyphRun> for CharIndexComparator {
+ fn compare(&self, key: &CharIndex, value: &GlyphRun) -> Ordering {
+ if *key < value.range.begin() {
+ Less
+ } else if *key >= value.range.end() {
+ Greater
+ } else {
+ Equal
+ }
+ }
+}
+
+impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterator<'a> {
+ // inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
+ #[inline(always)]
+ fn next(&mut self) -> Option<(&'a GlyphStore, CharIndex, Range<CharIndex>)> {
+ let slice_glyphs = self.glyph_iter.next();
+ if slice_glyphs.is_none() {
+ return None;
+ }
+ let slice_glyphs = slice_glyphs.unwrap();
+
+ let mut char_range = self.range.intersect(&slice_glyphs.range);
+ let slice_range_begin = slice_glyphs.range.begin();
+ char_range.shift_by(-slice_range_begin);
+ if !char_range.is_empty() {
+ return Some((&*slice_glyphs.glyph_store, slice_range_begin, char_range))
+ }
+
+ return None;
+ }
+}
+
+pub struct LineIterator<'a> {
+ range: Range<CharIndex>,
+ clump: Option<Range<CharIndex>>,
+ slices: SliceIterator<'a>,
+}
+
+impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
+ fn next(&mut self) -> Option<Range<CharIndex>> {
+ // Loop until we hit whitespace and are in a clump.
+ loop {
+ match self.slices.next() {
+ Some((glyphs, offset, slice_range)) => {
+ match (glyphs.is_whitespace(), self.clump) {
+ (false, Some(ref mut c)) => {
+ c.extend_by(slice_range.length());
+ }
+ (false, None) => {
+ let mut c = slice_range;
+ c.shift_by(offset);
+ self.clump = Some(c);
+ }
+ (true, None) => { /* chomp whitespace */ }
+ (true, Some(c)) => {
+ self.clump = None;
+ // The final whitespace clump is not included.
+ return Some(c);
+ }
+ }
+ },
+ None => {
+ // flush any remaining chars as a line
+ if self.clump.is_some() {
+ let mut c = self.clump.take_unwrap();
+ c.extend_to(self.range.end());
+ return Some(c);
+ } else {
+ return None;
+ }
+ }
+ }
+ }
+ }
+}
+
+impl<'a> TextRun {
+ pub fn new(font: &mut Font, text: String) -> TextRun {
+ let glyphs = TextRun::break_and_shape(font, text.as_slice());
+ let run = TextRun {
+ text: Arc::new(text),
+ font_metrics: font.metrics.clone(),
+ font_template: font.handle.get_template(),
+ pt_size: font.pt_size,
+ glyphs: Arc::new(glyphs),
+ };
+ return run;
+ }
+
+ pub fn break_and_shape(font: &mut Font, text: &str) -> Vec<GlyphRun> {
+ // TODO(Issue #230): do a better job. See Gecko's LineBreaker.
+ let mut glyphs = vec!();
+ let (mut byte_i, mut char_i) = (0u, CharIndex(0));
+ let mut cur_slice_is_whitespace = false;
+ let (mut byte_last_boundary, mut char_last_boundary) = (0, CharIndex(0));
+ while byte_i < text.len() {
+ let range = text.char_range_at(byte_i);
+ let ch = range.ch;
+ let next = range.next;
+
+ // Slices alternate between whitespace and non-whitespace,
+ // representing line break opportunities.
+ let can_break_before = if cur_slice_is_whitespace {
+ match ch {
+ ' ' | '\t' | '\n' => false,
+ _ => {
+ cur_slice_is_whitespace = false;
+ true
+ }
+ }
+ } else {
+ match ch {
+ ' ' | '\t' | '\n' => {
+ cur_slice_is_whitespace = true;
+ true
+ },
+ _ => false
+ }
+ };
+
+ // Create a glyph store for this slice if it's nonempty.
+ if can_break_before && byte_i > byte_last_boundary {
+ let slice = text.slice(byte_last_boundary, byte_i).to_string();
+ debug!("creating glyph store for slice {} (ws? {}), {} - {} in run {}",
+ slice, !cur_slice_is_whitespace, byte_last_boundary, byte_i, text);
+ glyphs.push(GlyphRun {
+ glyph_store: font.shape_text(slice, !cur_slice_is_whitespace),
+ range: Range::new(char_last_boundary, char_i - char_last_boundary),
+ });
+ byte_last_boundary = byte_i;
+ char_last_boundary = char_i;
+ }
+
+ byte_i = next;
+ char_i = char_i + CharIndex(1);
+ }
+
+ // Create a glyph store for the final slice if it's nonempty.
+ if byte_i > byte_last_boundary {
+ let slice = text.slice_from(byte_last_boundary).to_string();
+ debug!("creating glyph store for final slice {} (ws? {}), {} - {} in run {}",
+ slice, cur_slice_is_whitespace, byte_last_boundary, text.len(), text);
+ glyphs.push(GlyphRun {
+ glyph_store: font.shape_text(slice, cur_slice_is_whitespace),
+ range: Range::new(char_last_boundary, char_i - char_last_boundary),
+ });
+ }
+
+ glyphs
+ }
+
+ pub fn char_len(&self) -> CharIndex {
+ match self.glyphs.last() {
+ None => CharIndex(0),
+ Some(ref glyph_run) => glyph_run.range.end(),
+ }
+ }
+
+ pub fn glyphs(&'a self) -> &'a Vec<GlyphRun> {
+ &*self.glyphs
+ }
+
+ pub fn range_is_trimmable_whitespace(&self, range: &Range<CharIndex>) -> bool {
+ self.iter_slices_for_range(range).all(|(slice_glyphs, _, _)| {
+ slice_glyphs.is_whitespace()
+ })
+ }
+
+ pub fn ascent(&self) -> Au {
+ self.font_metrics.ascent
+ }
+
+ pub fn descent(&self) -> Au {
+ self.font_metrics.descent
+ }
+
+ pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au {
+ // TODO(Issue #199): alter advance direction for RTL
+ // TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
+ self.iter_slices_for_range(range)
+ .fold(Au(0), |advance, (glyphs, _, slice_range)| {
+ advance + glyphs.advance_for_char_range(&slice_range)
+ })
+ }
+
+ pub fn metrics_for_range(&self, range: &Range<CharIndex>) -> RunMetrics {
+ RunMetrics::new(self.advance_for_range(range),
+ self.font_metrics.ascent,
+ self.font_metrics.descent)
+ }
+
+ pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<CharIndex>) -> RunMetrics {
+ RunMetrics::new(glyphs.advance_for_char_range(slice_range),
+ self.font_metrics.ascent,
+ self.font_metrics.descent)
+ }
+
+ pub fn min_width_for_range(&self, range: &Range<CharIndex>) -> Au {
+ debug!("iterating outer range {:?}", range);
+ self.iter_slices_for_range(range).fold(Au(0), |max_piece_width, (_, offset, slice_range)| {
+ debug!("iterated on {:?}[{:?}]", offset, slice_range);
+ Au::max(max_piece_width, self.advance_for_range(&slice_range))
+ })
+ }
+
+ /// Returns the index of the first glyph run containing the given character index.
+ fn index_of_first_glyph_run_containing(&self, index: CharIndex) -> Option<uint> {
+ self.glyphs.as_slice().binary_search_index_by(&index, CharIndexComparator)
+ }
+
+ pub fn iter_slices_for_range(&'a self, range: &Range<CharIndex>) -> SliceIterator<'a> {
+ let index = match self.index_of_first_glyph_run_containing(range.begin()) {
+ None => self.glyphs.len(),
+ Some(index) => index,
+ };
+ SliceIterator {
+ glyph_iter: self.glyphs.slice_from(index).iter(),
+ range: *range,
+ }
+ }
+
+ pub fn iter_natural_lines_for_range(&'a self, range: &Range<CharIndex>) -> LineIterator<'a> {
+ LineIterator {
+ range: *range,
+ clump: None,
+ slices: self.iter_slices_for_range(range),
+ }
+ }
+}
diff --git a/components/gfx/text/util.rs b/components/gfx/text/util.rs
new file mode 100644
index 00000000000..c5059bbff10
--- /dev/null
+++ b/components/gfx/text/util.rs
@@ -0,0 +1,285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use text::glyph::CharIndex;
+
+#[deriving(PartialEq)]
+pub enum CompressionMode {
+ CompressNone,
+ CompressWhitespace,
+ CompressWhitespaceNewline,
+ DiscardNewline
+}
+
+// ported from Gecko's nsTextFrameUtils::TransformText.
+//
+// High level TODOs:
+//
+// * Issue #113: consider incoming text state (arabic, etc)
+// and propogate outgoing text state (dual of above)
+//
+// * Issue #114: record skipped and kept chars for mapping original to new text
+//
+// * Untracked: various edge cases for bidi, CJK, etc.
+pub fn transform_text(text: &str, mode: CompressionMode,
+ incoming_whitespace: bool,
+ new_line_pos: &mut Vec<CharIndex>) -> (String, bool) {
+ let mut out_str = String::new();
+ let out_whitespace = match mode {
+ CompressNone | DiscardNewline => {
+ let mut new_line_index = CharIndex(0);
+ for ch in text.chars() {
+ if is_discardable_char(ch, mode) {
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ if ch == '\t' {
+ // TODO: set "has tab" flag
+ } else if ch == '\n' {
+ // Save new-line's position for line-break
+ // This value is relative(not absolute)
+ new_line_pos.push(new_line_index);
+ new_line_index = CharIndex(0);
+ }
+
+ if ch != '\n' {
+ new_line_index = new_line_index + CharIndex(1);
+ }
+ out_str.push_char(ch);
+ }
+ }
+ text.len() > 0 && is_in_whitespace(text.char_at_reverse(0), mode)
+ },
+
+ CompressWhitespace | CompressWhitespaceNewline => {
+ let mut in_whitespace: bool = incoming_whitespace;
+ for ch in text.chars() {
+ // TODO: discard newlines between CJK chars
+ let mut next_in_whitespace: bool = is_in_whitespace(ch, mode);
+
+ if !next_in_whitespace {
+ if is_always_discardable_char(ch) {
+ // revert whitespace setting, since this char was discarded
+ next_in_whitespace = in_whitespace;
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ out_str.push_char(ch);
+ }
+ } else { /* next_in_whitespace; possibly add a space char */
+ if in_whitespace {
+ // TODO: record skipped char
+ } else {
+ // TODO: record kept char
+ out_str.push_char(' ');
+ }
+ }
+ // save whitespace context for next char
+ in_whitespace = next_in_whitespace;
+ } /* /for str::each_char */
+ in_whitespace
+ }
+ };
+
+ return (out_str.into_string(), out_whitespace);
+
+ fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool {
+ match (ch, mode) {
+ (' ', _) => true,
+ ('\t', _) => true,
+ ('\n', CompressWhitespaceNewline) => true,
+ (_, _) => false
+ }
+ }
+
+ fn is_discardable_char(ch: char, mode: CompressionMode) -> bool {
+ if is_always_discardable_char(ch) {
+ return true;
+ }
+ match mode {
+ DiscardNewline | CompressWhitespaceNewline => ch == '\n',
+ _ => false
+ }
+ }
+
+ fn is_always_discardable_char(_ch: char) -> bool {
+ // TODO: check for bidi control chars, soft hyphens.
+ false
+ }
+}
+
+pub fn float_to_fixed(before: int, f: f64) -> i32 {
+ (1i32 << before as uint) * (f as i32)
+}
+
+pub fn fixed_to_float(before: int, f: i32) -> f64 {
+ f as f64 * 1.0f64 / ((1i32 << before as uint) as f64)
+}
+
+pub fn fixed_to_rounded_int(before: int, f: i32) -> int {
+ let half = 1i32 << (before-1) as uint;
+ if f > 0i32 {
+ ((half + f) >> before as uint) as int
+ } else {
+ -((half - f) >> before as uint) as int
+ }
+}
+
+/* Generate a 32-bit TrueType tag from its 4 characters */
+pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 {
+ let a = a as u32;
+ let b = b as u32;
+ let c = c as u32;
+ let d = d as u32;
+ (a << 24 | b << 16 | c << 8 | d) as u32
+}
+
+#[test]
+fn test_true_type_tag() {
+ assert_eq!(true_type_tag('c', 'm', 'a', 'p'), 0x_63_6D_61_70_u32);
+}
+
+#[test]
+fn test_transform_compress_none() {
+ let test_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+ let mode = CompressNone;
+
+ for test in test_strs.iter() {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *test)
+ }
+}
+
+#[test]
+fn test_transform_discard_newline() {
+ let test_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+
+ let oracle_strs = vec!(
+ " foo bar",
+ "foo bar ",
+ "foo bar",
+ "foo bar",
+ " foo bar baz",
+ "foo bar baz",
+ "foobarbaz"
+ );
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = DiscardNewline;
+
+ for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *oracle)
+ }
+}
+
+/* FIXME: Fix and re-enable
+#[test]
+fn test_transform_compress_whitespace() {
+ let test_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ let oracle_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespace;
+
+ for i in range(0, test_strs.len()) {
+ let mut new_line_pos = ~[];
+ let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
+ assert_eq!(&trimmed_str, &oracle_strs[i])
+ }
+}
+
+#[test]
+fn test_transform_compress_whitespace_newline() {
+ let test_strs : ~[String] = ~[" foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo\n bar".to_string(),
+ "foo \nbar".to_string(),
+ " foo bar \nbaz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz\n\n".to_string()];
+
+ let oracle_strs : ~[String] = ~["foo bar".to_string(),
+ "foo bar ".to_string(),
+ "foo bar".to_string(),
+ "foo bar".to_string(),
+ " foo bar baz".to_string(),
+ "foo bar baz".to_string(),
+ "foobarbaz ".to_string()];
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespaceNewline;
+
+ for i in range(0, test_strs.len()) {
+ let mut new_line_pos = ~[];
+ let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos);
+ assert_eq!(&trimmed_str, &oracle_strs[i])
+ }
+}
+*/
+
+#[test]
+fn test_transform_compress_whitespace_newline_no_incoming() {
+ let test_strs = vec!(
+ " foo bar",
+ "\nfoo bar",
+ "foo bar ",
+ "foo\n bar",
+ "foo \nbar",
+ " foo bar \nbaz",
+ "foo bar baz",
+ "foobarbaz\n\n"
+ );
+
+ let oracle_strs = vec!(
+ " foo bar",
+ " foo bar",
+ "foo bar ",
+ "foo bar",
+ "foo bar",
+ " foo bar baz",
+ "foo bar baz",
+ "foobarbaz "
+ );
+
+ assert_eq!(test_strs.len(), oracle_strs.len());
+ let mode = CompressWhitespaceNewline;
+
+ for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) {
+ let mut new_line_pos = vec!();
+ let (trimmed_str, _out) = transform_text(*test, mode, false, &mut new_line_pos);
+ assert_eq!(trimmed_str.as_slice(), *oracle)
+ }
+}
diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml
new file mode 100644
index 00000000000..9d8e8abbdaa
--- /dev/null
+++ b/components/layout/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "layout"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "layout"
+path = "lib.rs"
+
+[dependencies.gfx]
+path = "../gfx"
+
+[dependencies.script]
+path = "../script"
+
+[dependencies.layout_traits]
+path = "../layout_traits"
+
+[dependencies.script_traits]
+path = "../script_traits"
+
+[dependencies.style]
+path = "../style"
+
+[dependencies.macros]
+path = "../macros"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
diff --git a/components/layout/block.rs b/components/layout/block.rs
new file mode 100644
index 00000000000..b84e0da50f7
--- /dev/null
+++ b/components/layout/block.rs
@@ -0,0 +1,2428 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS block formatting contexts.
+//!
+//! Terminology Note:
+//! As per the CSS Spec, the term 'absolute positioning' here refers to
+//! elements with position = 'absolute' or 'fixed'.
+//! The term 'positioned element' refers to elements with position =
+//! 'relative', 'absolute', or 'fixed'.
+//!
+//! CB: Containing Block of the current flow.
+
+#![deny(unsafe_block)]
+
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::{ClearBoth, ClearLeft, ClearRight, FloatKind, Floats, PlacementInfo};
+use flow::{BaseFlow, BlockFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow::{MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal, mut_base};
+use flow;
+use fragment::{Fragment, ImageFragment, ScannedTextFragment};
+use layout_debug;
+use model::{Auto, IntrinsicISizes, MarginCollapseInfo, MarginsCollapse};
+use model::{MarginsCollapseThrough, MaybeAuto, NoCollapsibleMargins, Specified, specified};
+use model::{specified_or_none};
+use wrapper::ThreadSafeLayoutNode;
+use style::ComputedValues;
+use style::computed_values::{clear, position};
+
+use collections::dlist::DList;
+use geom::{Size2D, Point2D, Rect};
+use gfx::color;
+use gfx::display_list::{BackgroundAndBorderLevel, BlockLevel, ContentStackingLevel, DisplayList};
+use gfx::display_list::{FloatStackingLevel, PositionedDescendantStackingLevel};
+use gfx::display_list::{RootOfStackingContextLevel};
+use gfx::render_task::RenderLayer;
+use servo_msg::compositor_msg::{FixedPosition, LayerId, Scrollable};
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
+use std::fmt;
+use std::mem;
+use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LPN_Length, LPN_None};
+use style::computed_values::{LPN_Percentage, LP_Length, LP_Percentage};
+use style::computed_values::{display, float, overflow};
+use sync::Arc;
+
+/// Information specific to floated blocks.
+#[deriving(Encodable)]
+pub struct FloatedBlockInfo {
+ pub containing_inline_size: Au,
+
+ /// Offset relative to where the parent tried to position this flow
+ pub rel_pos: LogicalPoint<Au>,
+
+ /// Index into the fragment list for inline floats
+ pub index: Option<uint>,
+
+ /// Left or right?
+ pub float_kind: FloatKind,
+}
+
+impl FloatedBlockInfo {
+ pub fn new(float_kind: FloatKind, writing_mode: WritingMode) -> FloatedBlockInfo {
+ FloatedBlockInfo {
+ containing_inline_size: Au(0),
+ rel_pos: LogicalPoint::new(writing_mode, Au(0), Au(0)),
+ index: None,
+ float_kind: float_kind,
+ }
+ }
+}
+
+/// The solutions for the block-size-and-margins constraint equation.
+struct BSizeConstraintSolution {
+ block_start: Au,
+ _block_end: Au,
+ block_size: Au,
+ margin_block_start: Au,
+ margin_block_end: Au
+}
+
+impl BSizeConstraintSolution {
+ fn new(block_start: Au, block_end: Au, block_size: Au, margin_block_start: Au, margin_block_end: Au)
+ -> BSizeConstraintSolution {
+ BSizeConstraintSolution {
+ block_start: block_start,
+ _block_end: block_end,
+ block_size: block_size,
+ margin_block_start: margin_block_start,
+ margin_block_end: margin_block_end,
+ }
+ }
+
+ /// Solve the vertical constraint equation for absolute non-replaced elements.
+ ///
+ /// CSS Section 10.6.4
+ /// Constraint equation:
+ /// block-start + block-end + block-size + margin-block-start + margin-block-end
+ /// = absolute containing block block-size - (vertical padding and border)
+ /// [aka available_block-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_vertical_constraints_abs_nonreplaced(block_size: MaybeAuto,
+ block_start_margin: MaybeAuto,
+ block_end_margin: MaybeAuto,
+ block_start: MaybeAuto,
+ block_end: MaybeAuto,
+ content_block_size: Au,
+ available_block_size: Au,
+ static_b_offset: Au)
+ -> BSizeConstraintSolution {
+ // Distance from the block-start edge of the Absolute Containing Block to the
+ // block-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_block_start = static_b_offset;
+
+ let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end, block_size) {
+ (Auto, Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ // Now it is the same situation as block-start Specified and block-end
+ // and block-size Auto.
+
+ let block_size = content_block_size;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end), Specified(block_size)) => {
+ match (block_start_margin, block_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_block_size - block_start - block_end - block_size;
+ (block_start, block_end, block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ (Specified(margin_block_start), Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (block_start, block_end, block_size, margin_block_start, available_block_size - sum)
+ }
+ (Auto, Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (block_start, block_end, block_size, available_block_size - sum, margin_block_end)
+ }
+ (Specified(margin_block_start), Specified(margin_block_end)) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ }
+ }
+
+ // For the rest of the cases, auto values for margin are set to 0
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(block_end), Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Auto, Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_end + margin_block_start + margin_block_end;
+ (block_start, block_end, available_block_size - sum, margin_block_start, margin_block_end)
+ }
+
+ // If block-size is auto, then block-size is content block-size. Solve for the
+ // non-auto value.
+ (Specified(block_start), Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_size = content_block_size;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Auto, Specified(block_end), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_size = content_block_size;
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+
+ (Auto, Auto, Specified(block_size)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ };
+ BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end)
+ }
+
+ /// Solve the vertical constraint equation for absolute replaced elements.
+ ///
+ /// Assumption: The used value for block-size has already been calculated.
+ ///
+ /// CSS Section 10.6.5
+ /// Constraint equation:
+ /// block-start + block-end + block-size + margin-block-start + margin-block-end
+ /// = absolute containing block block-size - (vertical padding and border)
+ /// [aka available_block-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_vertical_constraints_abs_replaced(block_size: Au,
+ block_start_margin: MaybeAuto,
+ block_end_margin: MaybeAuto,
+ block_start: MaybeAuto,
+ block_end: MaybeAuto,
+ _: Au,
+ available_block_size: Au,
+ static_b_offset: Au)
+ -> BSizeConstraintSolution {
+ // Distance from the block-start edge of the Absolute Containing Block to the
+ // block-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_block_start = static_b_offset;
+
+ let (block_start, block_end, block_size, margin_block_start, margin_block_end) = match (block_start, block_end) {
+ (Auto, Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let block_start = static_position_block_start;
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Specified(block_end)) => {
+ match (block_start_margin, block_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_block_size - block_start - block_end - block_size;
+ (block_start, block_end, block_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ (Specified(margin_block_start), Auto) => {
+ let sum = block_start + block_end + block_size + margin_block_start;
+ (block_start, block_end, block_size, margin_block_start, available_block_size - sum)
+ }
+ (Auto, Specified(margin_block_end)) => {
+ let sum = block_start + block_end + block_size + margin_block_end;
+ (block_start, block_end, block_size, available_block_size - sum, margin_block_end)
+ }
+ (Specified(margin_block_start), Specified(margin_block_end)) => {
+ // Values are over-constrained. Ignore value for 'block-end'.
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ }
+ }
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(block_end)) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_end + block_size + margin_block_start + margin_block_end;
+ (available_block_size - sum, block_end, block_size, margin_block_start, margin_block_end)
+ }
+ (Specified(block_start), Auto) => {
+ let margin_block_start = block_start_margin.specified_or_zero();
+ let margin_block_end = block_end_margin.specified_or_zero();
+ let sum = block_start + block_size + margin_block_start + margin_block_end;
+ (block_start, available_block_size - sum, block_size, margin_block_start, margin_block_end)
+ }
+ };
+ BSizeConstraintSolution::new(block_start, block_end, block_size, margin_block_start, margin_block_end)
+ }
+}
+
+/// Performs block-size calculations potentially multiple times, taking
+/// (assuming an horizontal writing mode) `height`, `min-height`, and `max-height`
+/// into account. After each call to `next()`, the caller must call `.try()` with the
+/// current calculated value of `height`.
+///
+/// See CSS 2.1 § 10.7.
+struct CandidateBSizeIterator {
+ block_size: MaybeAuto,
+ max_block_size: Option<Au>,
+ min_block_size: Au,
+ candidate_value: Au,
+ status: CandidateBSizeIteratorStatus,
+}
+
+impl CandidateBSizeIterator {
+ /// Creates a new candidate block-size iterator. `block_container_block-size` is `None` if the block-size
+ /// of the block container has not been determined yet. It will always be `Some` in the case of
+ /// absolutely-positioned containing blocks.
+ pub fn new(style: &ComputedValues, block_container_block_size: Option<Au>)
+ -> CandidateBSizeIterator {
+ // Per CSS 2.1 § 10.7, (assuming an horizontal writing mode,)
+ // percentages in `min-height` and `max-height` refer to the height of
+ // the containing block.
+ // If that is not determined yet by the time we need to resolve
+ // `min-height` and `max-height`, percentage values are ignored.
+
+ let block_size = match (style.content_block_size(), block_container_block_size) {
+ (LPA_Percentage(percent), Some(block_container_block_size)) => {
+ Specified(block_container_block_size.scale_by(percent))
+ }
+ (LPA_Percentage(_), None) | (LPA_Auto, _) => Auto,
+ (LPA_Length(length), _) => Specified(length),
+ };
+ let max_block_size = match (style.max_block_size(), block_container_block_size) {
+ (LPN_Percentage(percent), Some(block_container_block_size)) => {
+ Some(block_container_block_size.scale_by(percent))
+ }
+ (LPN_Percentage(_), None) | (LPN_None, _) => None,
+ (LPN_Length(length), _) => Some(length),
+ };
+ let min_block_size = match (style.min_block_size(), block_container_block_size) {
+ (LP_Percentage(percent), Some(block_container_block_size)) => {
+ block_container_block_size.scale_by(percent)
+ }
+ (LP_Percentage(_), None) => Au(0),
+ (LP_Length(length), _) => length,
+ };
+
+ CandidateBSizeIterator {
+ block_size: block_size,
+ max_block_size: max_block_size,
+ min_block_size: min_block_size,
+ candidate_value: Au(0),
+ status: InitialCandidateBSizeStatus,
+ }
+ }
+}
+
+impl Iterator<MaybeAuto> for CandidateBSizeIterator {
+ fn next(&mut self) -> Option<MaybeAuto> {
+ self.status = match self.status {
+ InitialCandidateBSizeStatus => TryingBSizeCandidateBSizeStatus,
+ TryingBSizeCandidateBSizeStatus => {
+ match self.max_block_size {
+ Some(max_block_size) if self.candidate_value > max_block_size => {
+ TryingMaxCandidateBSizeStatus
+ }
+ _ if self.candidate_value < self.min_block_size => TryingMinCandidateBSizeStatus,
+ _ => FoundCandidateBSizeStatus,
+ }
+ }
+ TryingMaxCandidateBSizeStatus => {
+ if self.candidate_value < self.min_block_size {
+ TryingMinCandidateBSizeStatus
+ } else {
+ FoundCandidateBSizeStatus
+ }
+ }
+ TryingMinCandidateBSizeStatus | FoundCandidateBSizeStatus => {
+ FoundCandidateBSizeStatus
+ }
+ };
+
+ match self.status {
+ TryingBSizeCandidateBSizeStatus => Some(self.block_size),
+ TryingMaxCandidateBSizeStatus => {
+ Some(Specified(self.max_block_size.unwrap()))
+ }
+ TryingMinCandidateBSizeStatus => {
+ Some(Specified(self.min_block_size))
+ }
+ FoundCandidateBSizeStatus => None,
+ InitialCandidateBSizeStatus => fail!(),
+ }
+ }
+}
+
+enum CandidateBSizeIteratorStatus {
+ InitialCandidateBSizeStatus,
+ TryingBSizeCandidateBSizeStatus,
+ TryingMaxCandidateBSizeStatus,
+ TryingMinCandidateBSizeStatus,
+ FoundCandidateBSizeStatus,
+}
+
+// A helper function used in block-size calculation.
+fn translate_including_floats(cur_b: &mut Au, delta: Au, floats: &mut Floats) {
+ *cur_b = *cur_b + delta;
+ let writing_mode = floats.writing_mode;
+ floats.translate(LogicalSize::new(writing_mode, Au(0), -delta));
+}
+
+/// The real assign-block-sizes traversal for flows with position 'absolute'.
+///
+/// This is a traversal of an Absolute Flow tree.
+/// - Relatively positioned flows and the Root flow start new Absolute flow trees.
+/// - The kids of a flow in this tree will be the flows for which it is the
+/// absolute Containing Block.
+/// - Thus, leaf nodes and inner non-root nodes are all Absolute Flows.
+///
+/// A Flow tree can have several Absolute Flow trees (depending on the number
+/// of relatively positioned flows it has).
+///
+/// Note that flows with position 'fixed' just form a flat list as they all
+/// have the Root flow as their CB.
+struct AbsoluteAssignBSizesTraversal<'a>(&'a LayoutContext<'a>);
+
+impl<'a> PreorderFlowTraversal for AbsoluteAssignBSizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let block_flow = flow.as_block();
+
+ // The root of the absolute flow tree is definitely not absolutely
+ // positioned. Nothing to process here.
+ if block_flow.is_root_of_absolute_flow_tree() {
+ return true;
+ }
+
+
+ let AbsoluteAssignBSizesTraversal(ref ctx) = *self;
+ block_flow.calculate_abs_block_size_and_margins(*ctx);
+ true
+ }
+}
+
+/// The store-overflow traversal particular to absolute flows.
+///
+/// Propagate overflow up the Absolute flow tree and update overflow up to and
+/// not including the root of the Absolute flow tree.
+/// After that, it is up to the normal store-overflow traversal to propagate
+/// it further up.
+struct AbsoluteStoreOverflowTraversal<'a>{
+ layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for AbsoluteStoreOverflowTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ // This will be taken care of by the normal store-overflow traversal.
+ if flow.is_root_of_absolute_flow_tree() {
+ return true;
+ }
+
+ flow.store_overflow(self.layout_context);
+ true
+ }
+}
+
+enum BlockType {
+ BlockReplacedType,
+ BlockNonReplacedType,
+ AbsoluteReplacedType,
+ AbsoluteNonReplacedType,
+ FloatReplacedType,
+ FloatNonReplacedType,
+}
+
+#[deriving(Clone, PartialEq)]
+pub enum MarginsMayCollapseFlag {
+ MarginsMayCollapse,
+ MarginsMayNotCollapse,
+}
+
+#[deriving(PartialEq)]
+enum FormattingContextType {
+ NonformattingContext,
+ BlockFormattingContext,
+ OtherFormattingContext,
+}
+
+// Propagates the `layers_needed_for_descendants` flag appropriately from a child. This is called
+// as part of block-size assignment.
+//
+// If any fixed descendants of kids are present, this kid needs a layer.
+//
+// FIXME(#2006, pcwalton): This is too layer-happy. Like WebKit, we shouldn't do this unless
+// the positioned descendants are actually on top of the fixed kids.
+//
+// TODO(#1244, #2007, pcwalton): Do this for CSS transforms and opacity too, at least if they're
+// animating.
+fn propagate_layer_flag_from_child(layers_needed_for_descendants: &mut bool, kid: &mut Flow) {
+ if kid.is_absolute_containing_block() {
+ let kid_base = flow::mut_base(kid);
+ if kid_base.flags.needs_layer() {
+ *layers_needed_for_descendants = true
+ }
+ } else {
+ let kid_base = flow::mut_base(kid);
+ if kid_base.flags.layers_needed_for_descendants() {
+ *layers_needed_for_descendants = true
+ }
+ }
+}
+
+// A block formatting context.
+#[deriving(Encodable)]
+pub struct BlockFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// The associated fragment.
+ pub fragment: Fragment,
+
+ /// TODO: is_root should be a bit field to conserve memory.
+ /// Whether this block flow is the root flow.
+ pub is_root: bool,
+
+ /// Static y offset of an absolute flow from its CB.
+ pub static_b_offset: Au,
+
+ /// The inline-size of the last float prior to this block. This is used to speculatively lay out
+ /// block formatting contexts.
+ previous_float_inline_size: Option<Au>,
+
+ /// Additional floating flow members.
+ pub float: Option<Box<FloatedBlockInfo>>
+}
+
+impl BlockFlow {
+ pub fn from_node(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> BlockFlow {
+ BlockFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: Fragment::new(constructor, node),
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: None
+ }
+ }
+
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> BlockFlow {
+ BlockFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: fragment,
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: None
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> BlockFlow {
+ let base = BaseFlow::new((*node).clone());
+ BlockFlow {
+ fragment: Fragment::new(constructor, node),
+ is_root: false,
+ static_b_offset: Au::new(0),
+ previous_float_inline_size: None,
+ float: Some(box FloatedBlockInfo::new(float_kind, base.writing_mode)),
+ base: base,
+ }
+ }
+
+ /// Return the type of this block.
+ ///
+ /// This determines the algorithm used to calculate inline-size, block-size, and the
+ /// relevant margins for this Block.
+ fn block_type(&self) -> BlockType {
+ if self.is_absolutely_positioned() {
+ if self.is_replaced_content() {
+ AbsoluteReplacedType
+ } else {
+ AbsoluteNonReplacedType
+ }
+ } else if self.is_float() {
+ if self.is_replaced_content() {
+ FloatReplacedType
+ } else {
+ FloatNonReplacedType
+ }
+ } else {
+ if self.is_replaced_content() {
+ BlockReplacedType
+ } else {
+ BlockNonReplacedType
+ }
+ }
+ }
+
+ /// Compute the used value of inline-size for this Block.
+ fn compute_used_inline_size(&mut self, ctx: &LayoutContext, containing_block_inline_size: Au) {
+ let block_type = self.block_type();
+ match block_type {
+ AbsoluteReplacedType => {
+ let inline_size_computer = AbsoluteReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ AbsoluteNonReplacedType => {
+ let inline_size_computer = AbsoluteNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ FloatReplacedType => {
+ let inline_size_computer = FloatReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ FloatNonReplacedType => {
+ let inline_size_computer = FloatNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ BlockReplacedType => {
+ let inline_size_computer = BlockReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ BlockNonReplacedType => {
+ let inline_size_computer = BlockNonReplaced;
+ inline_size_computer.compute_used_inline_size(self, ctx, containing_block_inline_size);
+ }
+ }
+ }
+
+ /// Return this flow's fragment.
+ pub fn fragment<'a>(&'a mut self) -> &'a mut Fragment {
+ &mut self.fragment
+ }
+
+ /// Return the static x offset from the appropriate Containing Block for this flow.
+ pub fn static_i_offset(&self) -> Au {
+ if self.is_fixed() {
+ self.base.fixed_static_i_offset
+ } else {
+ self.base.absolute_static_i_offset
+ }
+ }
+
+ /// Return the size of the Containing Block for this flow.
+ ///
+ /// Right now, this only gets the Containing Block size for absolutely
+ /// positioned elements.
+ /// Note: Assume this is called in a top-down traversal, so it is ok to
+ /// reference the CB.
+ #[inline]
+ pub fn containing_block_size(&mut self, viewport_size: Size2D<Au>) -> LogicalSize<Au> {
+ assert!(self.is_absolutely_positioned());
+ if self.is_fixed() {
+ // Initial containing block is the CB for the root
+ LogicalSize::from_physical(self.base.writing_mode, viewport_size)
+ } else {
+ self.base.absolute_cb.generated_containing_block_rect().size
+ }
+ }
+
+ /// Traverse the Absolute flow tree in preorder.
+ ///
+ /// Traverse all your direct absolute descendants, who will then traverse
+ /// their direct absolute descendants.
+ /// Also, set the static y offsets for each descendant (using the value
+ /// which was bubbled up during normal assign-block-size).
+ ///
+ /// Return true if the traversal is to continue or false to stop.
+ fn traverse_preorder_absolute_flows<T:PreorderFlowTraversal>(&mut self,
+ traversal: &mut T)
+ -> bool {
+ let flow = self as &mut Flow;
+ if traversal.should_prune(flow) {
+ return true
+ }
+
+ if !traversal.process(flow) {
+ return false
+ }
+
+ let cb_block_start_edge_offset = flow.generated_containing_block_rect().start.b;
+ let mut descendant_offset_iter = mut_base(flow).abs_descendants.iter_with_offset();
+ // Pass in the respective static y offset for each descendant.
+ for (ref mut descendant_link, ref y_offset) in descendant_offset_iter {
+ let block = descendant_link.as_block();
+ // The stored y_offset is wrt to the flow box.
+ // Translate it to the CB (which is the padding box).
+ block.static_b_offset = **y_offset - cb_block_start_edge_offset;
+ if !block.traverse_preorder_absolute_flows(traversal) {
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Traverse the Absolute flow tree in postorder.
+ ///
+ /// Return true if the traversal is to continue or false to stop.
+ fn traverse_postorder_absolute_flows<T:PostorderFlowTraversal>(&mut self,
+ traversal: &mut T)
+ -> bool {
+ let flow = self as &mut Flow;
+ if traversal.should_prune(flow) {
+ return true
+ }
+
+ for descendant_link in mut_base(flow).abs_descendants.iter() {
+ let block = descendant_link.as_block();
+ if !block.traverse_postorder_absolute_flows(traversal) {
+ return false
+ }
+ }
+
+ traversal.process(flow)
+ }
+
+ /// Return true if this has a replaced fragment.
+ ///
+ /// The only two types of replaced fragments currently are text fragments
+ /// and image fragments.
+ fn is_replaced_content(&self) -> bool {
+ match self.fragment.specific {
+ ScannedTextFragment(_) | ImageFragment(_) => true,
+ _ => false,
+ }
+ }
+
+ /// Return shrink-to-fit inline-size.
+ ///
+ /// This is where we use the preferred inline-sizes and minimum inline-sizes
+ /// calculated in the bubble-inline-sizes traversal.
+ fn get_shrink_to_fit_inline_size(&self, available_inline_size: Au) -> Au {
+ geometry::min(self.base.intrinsic_inline_sizes.preferred_inline_size,
+ geometry::max(self.base.intrinsic_inline_sizes.minimum_inline_size, available_inline_size))
+ }
+
+ /// Collect and update static y-offsets bubbled up by kids.
+ ///
+ /// This would essentially give us offsets of all absolutely positioned
+ /// direct descendants and all fixed descendants, in tree order.
+ ///
+ /// Assume that this is called in a bottom-up traversal (specifically, the
+ /// assign-block-size traversal). So, kids have their flow origin already set.
+ /// In the case of absolute flow kids, they have their hypothetical box
+ /// position already set.
+ fn collect_static_b_offsets_from_kids(&mut self) {
+ let mut abs_descendant_y_offsets = Vec::new();
+ for kid in self.base.child_iter() {
+ let mut gives_abs_offsets = true;
+ if kid.is_block_like() {
+ let kid_block = kid.as_block();
+ if kid_block.is_fixed() || kid_block.is_absolutely_positioned() {
+ // It won't contribute any offsets for descendants because it
+ // would be the CB for them.
+ gives_abs_offsets = false;
+ // Give the offset for the current absolute flow alone.
+ abs_descendant_y_offsets.push(kid_block.get_hypothetical_block_start_edge());
+ } else if kid_block.is_positioned() {
+ // It won't contribute any offsets because it would be the CB
+ // for the descendants.
+ gives_abs_offsets = false;
+ }
+ }
+
+ if gives_abs_offsets {
+ let kid_base = flow::mut_base(kid);
+ // Avoid copying the offset vector.
+ let offsets = mem::replace(&mut kid_base.abs_descendants.static_b_offsets, Vec::new());
+ // Consume all the static y-offsets bubbled up by kid.
+ for y_offset in offsets.move_iter() {
+ // The offsets are wrt the kid flow box. Translate them to current flow.
+ abs_descendant_y_offsets.push(y_offset + kid_base.position.start.b);
+ }
+ }
+ }
+ self.base.abs_descendants.static_b_offsets = abs_descendant_y_offsets;
+ }
+
+ /// If this is the root flow, shifts all kids down and adjusts our size to account for
+ /// root flow margins, which should never be collapsed according to CSS § 8.3.1.
+ ///
+ /// TODO(#2017, pcwalton): This is somewhat inefficient (traverses kids twice); can we do
+ /// better?
+ fn adjust_fragments_for_collapsed_margins_if_root(&mut self) {
+ if !self.is_root() {
+ return
+ }
+
+ let (block_start_margin_value, block_end_margin_value) = match self.base.collapsible_margins {
+ MarginsCollapseThrough(_) => fail!("Margins unexpectedly collapsed through root flow."),
+ MarginsCollapse(block_start_margin, block_end_margin) => {
+ (block_start_margin.collapse(), block_end_margin.collapse())
+ }
+ NoCollapsibleMargins(block_start, block_end) => (block_start, block_end),
+ };
+
+ // Shift all kids down (or up, if margins are negative) if necessary.
+ if block_start_margin_value != Au(0) {
+ for kid in self.base.child_iter() {
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.b = kid_base.position.start.b + block_start_margin_value
+ }
+ }
+
+ self.base.position.size.block = self.base.position.size.block + block_start_margin_value +
+ block_end_margin_value;
+ self.fragment.border_box.size.block = self.fragment.border_box.size.block + block_start_margin_value +
+ block_end_margin_value;
+ }
+
+ /// Assign block-size for current flow.
+ ///
+ /// * Collapse margins for flow's children and set in-flow child flows' y-coordinates now that
+ /// we know their block-sizes.
+ /// * Calculate and set the block-size of the current flow.
+ /// * Calculate block-size, vertical margins, and y-coordinate for the flow's box. Ideally, this
+ /// should be calculated using CSS § 10.6.7.
+ ///
+ /// For absolute flows, we store the calculated content block-size for the flow. We defer the
+ /// calculation of the other values until a later traversal.
+ ///
+ /// `inline(always)` because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ pub fn assign_block_size_block_base<'a>(&mut self,
+ layout_context: &'a LayoutContext<'a>,
+ margins_may_collapse: MarginsMayCollapseFlag) {
+ let _scope = layout_debug_scope!("assign_block_size_block_base {:s}", self.base.debug_id());
+
+ // Our current border-box position.
+ let mut cur_b = Au(0);
+
+ // Absolute positioning establishes a block formatting context. Don't propagate floats
+ // in or out. (But do propagate them between kids.)
+ if self.is_absolutely_positioned() {
+ self.base.floats = Floats::new(self.fragment.style.writing_mode);
+ }
+ if margins_may_collapse != MarginsMayCollapse {
+ self.base.floats = Floats::new(self.fragment.style.writing_mode);
+ }
+
+ let mut margin_collapse_info = MarginCollapseInfo::new();
+ self.base.floats.translate(LogicalSize::new(
+ self.fragment.style.writing_mode, -self.fragment.inline_start_offset(), Au(0)));
+
+ // The sum of our block-start border and block-start padding.
+ let block_start_offset = self.fragment.border_padding.block_start;
+ translate_including_floats(&mut cur_b, block_start_offset, &mut self.base.floats);
+
+ let can_collapse_block_start_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ self.fragment.border_padding.block_start == Au(0);
+ margin_collapse_info.initialize_block_start_margin(&self.fragment,
+ can_collapse_block_start_margin_with_kids);
+
+ // At this point, `cur_b` is at the content edge of our box. Now iterate over children.
+ let mut floats = self.base.floats.clone();
+ let mut layers_needed_for_descendants = false;
+ for kid in self.base.child_iter() {
+ if kid.is_absolutely_positioned() {
+ // Assume that the *hypothetical box* for an absolute flow starts immediately after
+ // the block-end border edge of the previous flow.
+ flow::mut_base(kid).position.start.b = cur_b;
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ // Skip the collapsing and float processing for absolute flow kids and continue
+ // with the next flow.
+ continue
+ }
+
+ // Assign block-size now for the child if it was impacted by floats and we couldn't before.
+ flow::mut_base(kid).floats = floats.clone();
+ if kid.is_float() {
+ // FIXME(pcwalton): Using `position.start.b` to mean the float ceiling is a
+ // bit of a hack.
+ flow::mut_base(kid).position.start.b =
+ margin_collapse_info.current_float_ceiling();
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ let kid_was_impacted_by_floats =
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+ assert!(kid_was_impacted_by_floats); // As it was a float itself...
+
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.b = cur_b;
+ floats = kid_base.floats.clone();
+ continue
+ }
+
+
+ // If we have clearance, assume there are no floats in.
+ //
+ // FIXME(#2008, pcwalton): This could be wrong if we have `clear: left` or `clear:
+ // right` and there are still floats to impact, of course. But this gets complicated
+ // with margin collapse. Possibly the right thing to do is to lay out the block again
+ // in this rare case. (Note that WebKit can lay blocks out twice; this may be related,
+ // although I haven't looked into it closely.)
+ if kid.float_clearance() != clear::none {
+ flow::mut_base(kid).floats = Floats::new(self.fragment.style.writing_mode)
+ }
+
+ // Lay the child out if this was an in-order traversal.
+ let kid_was_impacted_by_floats =
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ // Mark flows for layerization if necessary to handle painting order correctly.
+ propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid);
+
+ // Handle any (possibly collapsed) top margin.
+ let delta = margin_collapse_info.advance_block_start_margin(
+ &flow::base(kid).collapsible_margins);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // Clear past the floats that came in, if necessary.
+ let clearance = match kid.float_clearance() {
+ clear::none => Au(0),
+ clear::left => floats.clearance(ClearLeft),
+ clear::right => floats.clearance(ClearRight),
+ clear::both => floats.clearance(ClearBoth),
+ };
+ cur_b = cur_b + clearance;
+
+ // At this point, `cur_b` is at the border edge of the child.
+ flow::mut_base(kid).position.start.b = cur_b;
+
+ // Now pull out the child's outgoing floats. We didn't do this immediately after the
+ // `assign_block-size_for_inorder_child_if_necessary` call because clearance on a block
+ // operates on the floats that come *in*, not the floats that go *out*.
+ if kid_was_impacted_by_floats {
+ floats = flow::mut_base(kid).floats.clone()
+ }
+
+ // Move past the child's border box. Do not use the `translate_including_floats`
+ // function here because the child has already translated floats past its border box.
+ let kid_base = flow::mut_base(kid);
+ cur_b = cur_b + kid_base.position.size.block;
+
+ // Handle any (possibly collapsed) block-end margin.
+ let delta = margin_collapse_info.advance_block_end_margin(&kid_base.collapsible_margins);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+ }
+
+ // Mark ourselves for layerization if that will be necessary to paint in the proper order
+ // (CSS 2.1, Appendix E).
+ self.base.flags.set_layers_needed_for_descendants(layers_needed_for_descendants);
+
+ // Collect various offsets needed by absolutely positioned descendants.
+ self.collect_static_b_offsets_from_kids();
+
+ // Add in our block-end margin and compute our collapsible margins.
+ let can_collapse_block_end_margin_with_kids =
+ margins_may_collapse == MarginsMayCollapse &&
+ !self.is_absolutely_positioned() &&
+ self.fragment.border_padding.block_end == Au(0);
+ let (collapsible_margins, delta) =
+ margin_collapse_info.finish_and_compute_collapsible_margins(
+ &self.fragment,
+ can_collapse_block_end_margin_with_kids);
+ self.base.collapsible_margins = collapsible_margins;
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // FIXME(#2003, pcwalton): The max is taken here so that you can scroll the page, but this
+ // is not correct behavior according to CSS 2.1 § 10.5. Instead I think we should treat the
+ // root element as having `overflow: scroll` and use the layers-based scrolling
+ // infrastructure to make it scrollable.
+ let mut block_size = cur_b - block_start_offset;
+ if self.is_root() {
+ let screen_size = LogicalSize::from_physical(
+ self.fragment.style.writing_mode, layout_context.shared.screen_size);
+ block_size = Au::max(screen_size.block, block_size)
+ }
+
+ if self.is_absolutely_positioned() {
+ // The content block-size includes all the floats per CSS 2.1 § 10.6.7. The easiest way to
+ // handle this is to just treat this as clearance.
+ block_size = block_size + floats.clearance(ClearBoth);
+
+ // Fixed position layers get layers.
+ if self.is_fixed() {
+ self.base.flags.set_needs_layer(true)
+ }
+
+ // Store the content block-size for use in calculating the absolute flow's dimensions
+ // later.
+ self.fragment.border_box.size.block = block_size;
+ return
+ }
+
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(),
+ None);
+ for candidate_block_size in candidate_block_size_iterator {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ Auto => block_size,
+ Specified(value) => value
+ }
+ }
+
+ // Adjust `cur_b` as necessary to account for the explicitly-specified block-size.
+ block_size = candidate_block_size_iterator.candidate_value;
+ let delta = block_size - (cur_b - block_start_offset);
+ translate_including_floats(&mut cur_b, delta, &mut floats);
+
+ // Compute content block-size and noncontent block-size.
+ let block_end_offset = self.fragment.border_padding.block_end;
+ translate_including_floats(&mut cur_b, block_end_offset, &mut floats);
+
+ // Now that `cur_b` is at the block-end of the border box, compute the final border box
+ // position.
+ self.fragment.border_box.size.block = cur_b;
+ self.fragment.border_box.start.b = Au(0);
+ self.base.position.size.block = cur_b;
+
+ self.base.floats = floats.clone();
+ self.adjust_fragments_for_collapsed_margins_if_root();
+
+ if self.is_root_of_absolute_flow_tree() {
+ // Assign block-sizes for all flows in this Absolute flow tree.
+ // This is preorder because the block-size of an absolute flow may depend on
+ // the block-size of its CB, which may also be an absolute flow.
+ self.traverse_preorder_absolute_flows(&mut AbsoluteAssignBSizesTraversal(
+ layout_context));
+ // Store overflow for all absolute descendants.
+ self.traverse_postorder_absolute_flows(&mut AbsoluteStoreOverflowTraversal {
+ layout_context: layout_context,
+ });
+ }
+ }
+
+ /// Add placement information about current float flow for use by the parent.
+ ///
+ /// Also, use information given by parent about other floats to find out our relative position.
+ ///
+ /// This does not give any information about any float descendants because they do not affect
+ /// elements outside of the subtree rooted at this float.
+ ///
+ /// This function is called on a kid flow by a parent. Therefore, `assign_block-size_float` was
+ /// already called on this kid flow by the traversal function. So, the values used are
+ /// well-defined.
+ pub fn place_float(&mut self) {
+ let block_size = self.fragment.border_box.size.block;
+ let clearance = match self.fragment.clear() {
+ None => Au(0),
+ Some(clear) => self.base.floats.clearance(clear),
+ };
+
+ let margin_block_size = self.fragment.margin.block_start_end();
+ let info = PlacementInfo {
+ size: LogicalSize::new(
+ self.fragment.style.writing_mode,
+ self.base.position.size.inline + self.fragment.margin.inline_start_end() +
+ self.fragment.border_padding.inline_start_end(),
+ block_size + margin_block_size),
+ ceiling: clearance + self.base.position.start.b,
+ max_inline_size: self.float.get_ref().containing_inline_size,
+ kind: self.float.get_ref().float_kind,
+ };
+
+ // Place the float and return the `Floats` back to the parent flow.
+ // After, grab the position and use that to set our position.
+ self.base.floats.add_float(&info);
+
+ self.float.get_mut_ref().rel_pos = self.base.floats.last_float_pos().unwrap();
+ }
+
+ /// Assign block-size for current flow.
+ ///
+ /// + Set in-flow child flows' y-coordinates now that we know their
+ /// block-sizes. This _doesn't_ do any margin collapsing for its children.
+ /// + Calculate block-size and y-coordinate for the flow's box. Ideally, this
+ /// should be calculated using CSS Section 10.6.7
+ ///
+ /// It does not calculate the block-size of the flow itself.
+ pub fn assign_block_size_float<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ let _scope = layout_debug_scope!("assign_block_size_float {:s}", self.base.debug_id());
+
+ let mut floats = Floats::new(self.fragment.style.writing_mode);
+ for kid in self.base.child_iter() {
+ flow::mut_base(kid).floats = floats.clone();
+ kid.assign_block_size_for_inorder_child_if_necessary(ctx);
+ floats = flow::mut_base(kid).floats.clone();
+ }
+
+ let block_start_offset = self.fragment.margin.block_start + self.fragment.border_padding.block_start;
+ let mut cur_b = block_start_offset;
+
+ // cur_b is now at the block-start content edge
+
+ for kid in self.base.child_iter() {
+ let child_base = flow::mut_base(kid);
+ child_base.position.start.b = cur_b;
+ // cur_b is now at the block-end margin edge of kid
+ cur_b = cur_b + child_base.position.size.block;
+ }
+
+ // Intrinsic height should include floating descendants with a margin
+ // below the element's bottom edge (see CSS Section 10.6.7).
+ let content_block_size = geometry::max(
+ cur_b - block_start_offset,
+ floats.clearance(ClearBoth));
+
+ // Floats establish a block formatting context, so we discard the output floats here.
+ drop(floats);
+
+ // The associated fragment has the border box of this flow.
+ self.fragment.border_box.start.b = self.fragment.margin.block_start;
+
+ // Calculate content block-size, taking `min-block-size` and `max-block-size` into account.
+ let mut candidate_block_size_iterator = CandidateBSizeIterator::new(self.fragment.style(), None);
+ for candidate_block_size in candidate_block_size_iterator {
+ candidate_block_size_iterator.candidate_value = match candidate_block_size {
+ Auto => content_block_size,
+ Specified(value) => value,
+ }
+ }
+
+ let content_block_size = candidate_block_size_iterator.candidate_value;
+ let noncontent_block_size = self.fragment.border_padding.block_start_end();
+ debug!("assign_block_size_float -- block_size: {}", content_block_size + noncontent_block_size);
+ self.fragment.border_box.size.block = content_block_size + noncontent_block_size;
+ }
+
+ fn build_display_list_block_common(&mut self,
+ layout_context: &LayoutContext,
+ offset: LogicalPoint<Au>,
+ background_border_level: BackgroundAndBorderLevel) {
+ let rel_offset =
+ self.fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
+ // Add the box that starts the block context.
+ let mut display_list = DisplayList::new();
+ let mut accumulator = self.fragment.build_display_list(
+ &mut display_list,
+ layout_context,
+ self.base.abs_position + (offset + rel_offset).to_physical(
+ self.base.writing_mode, container_size),
+ background_border_level);
+
+ let mut child_layers = DList::new();
+ for kid in self.base.child_iter() {
+ if kid.is_absolutely_positioned() {
+ // All absolute flows will be handled by their containing block.
+ continue
+ }
+
+ accumulator.push_child(&mut display_list, kid);
+ child_layers.append(mem::replace(&mut flow::mut_base(kid).layers, DList::new()))
+ }
+
+ // Process absolute descendant links.
+ for abs_descendant_link in self.base.abs_descendants.iter() {
+ // TODO(pradeep): Send in our absolute position directly.
+ accumulator.push_child(&mut display_list, abs_descendant_link);
+ child_layers.append(mem::replace(&mut flow::mut_base(abs_descendant_link).layers,
+ DList::new()));
+ }
+
+ accumulator.finish(&mut *self, display_list);
+ self.base.layers = child_layers
+ }
+
+ /// Add display items for current block.
+ ///
+ /// Set the absolute position for children after doing any offsetting for
+ /// position: relative.
+ pub fn build_display_list_block(&mut self, layout_context: &LayoutContext) {
+ if self.is_float() {
+ // TODO(#2009, pcwalton): This is a pseudo-stacking context. We need to merge `z-index:
+ // auto` kids into the parent stacking context, when that is supported.
+ self.build_display_list_float(layout_context)
+ } else if self.is_absolutely_positioned() {
+ self.build_display_list_abs(layout_context)
+ } else {
+ let writing_mode = self.base.writing_mode;
+ self.build_display_list_block_common(
+ layout_context, LogicalPoint::zero(writing_mode), BlockLevel)
+ }
+ }
+
+ pub fn build_display_list_float(&mut self, layout_context: &LayoutContext) {
+ let float_offset = self.float.get_ref().rel_pos;
+ self.build_display_list_block_common(layout_context,
+ float_offset,
+ RootOfStackingContextLevel);
+ self.base.display_list = mem::replace(&mut self.base.display_list,
+ DisplayList::new()).flatten(FloatStackingLevel)
+ }
+
+ /// Calculate and set the block-size, offsets, etc. for absolutely positioned flow.
+ ///
+ /// The layout for its in-flow children has been done during normal layout.
+ /// This is just the calculation of:
+ /// + block-size for the flow
+ /// + y-coordinate of the flow wrt its Containing Block.
+ /// + block-size, vertical margins, and y-coordinate for the flow's box.
+ fn calculate_abs_block_size_and_margins(&mut self, ctx: &LayoutContext) {
+ let containing_block_block_size = self.containing_block_size(ctx.shared.screen_size).block;
+ let static_b_offset = self.static_b_offset;
+
+ // This is the stored content block-size value from assign-block-size
+ let content_block_size = self.fragment.content_box().size.block;
+
+ let mut solution = None;
+ {
+ // Non-auto margin-block-start and margin-block-end values have already been
+ // calculated during assign-inline-size.
+ let margin = self.fragment.style().logical_margin();
+ let margin_block_start = match margin.block_start {
+ LPA_Auto => Auto,
+ _ => Specified(self.fragment.margin.block_start)
+ };
+ let margin_block_end = match margin.block_end {
+ LPA_Auto => Auto,
+ _ => Specified(self.fragment.margin.block_end)
+ };
+
+ let block_start;
+ let block_end;
+ {
+ let position = self.fragment.style().logical_position();
+ block_start = MaybeAuto::from_style(position.block_start, containing_block_block_size);
+ block_end = MaybeAuto::from_style(position.block_end, containing_block_block_size);
+ }
+
+ let available_block_size = containing_block_block_size - self.fragment.border_padding.block_start_end();
+ if self.is_replaced_content() {
+ // Calculate used value of block-size just like we do for inline replaced elements.
+ // TODO: Pass in the containing block block-size when Fragment's
+ // assign-block-size can handle it correctly.
+ self.fragment.assign_replaced_block_size_if_necessary();
+ // TODO: Right now, this content block-size value includes the
+ // margin because of erroneous block-size calculation in fragment.
+ // Check this when that has been fixed.
+ let block_size_used_val = self.fragment.border_box.size.block;
+ solution = Some(BSizeConstraintSolution::solve_vertical_constraints_abs_replaced(
+ block_size_used_val,
+ margin_block_start,
+ margin_block_end,
+ block_start,
+ block_end,
+ content_block_size,
+ available_block_size,
+ static_b_offset));
+ } else {
+ let style = self.fragment.style();
+ let mut candidate_block_size_iterator =
+ CandidateBSizeIterator::new(style, Some(containing_block_block_size));
+
+ for block_size_used_val in candidate_block_size_iterator {
+ solution =
+ Some(BSizeConstraintSolution::solve_vertical_constraints_abs_nonreplaced(
+ block_size_used_val,
+ margin_block_start,
+ margin_block_end,
+ block_start,
+ block_end,
+ content_block_size,
+ available_block_size,
+ static_b_offset));
+
+ candidate_block_size_iterator.candidate_value = solution.unwrap().block_size
+ }
+ }
+ }
+
+ let solution = solution.unwrap();
+ self.fragment.margin.block_start = solution.margin_block_start;
+ self.fragment.margin.block_end = solution.margin_block_end;
+ self.fragment.border_box.start.b = Au(0);
+ self.fragment.border_box.size.block = solution.block_size + self.fragment.border_padding.block_start_end();
+
+ self.base.position.start.b = solution.block_start + self.fragment.margin.block_start;
+ self.base.position.size.block = solution.block_size + self.fragment.border_padding.block_start_end();
+ }
+
+ /// Add display items for Absolutely Positioned flow.
+ fn build_display_list_abs(&mut self, layout_context: &LayoutContext) {
+ let writing_mode = self.base.writing_mode;
+ self.build_display_list_block_common(layout_context,
+ LogicalPoint::zero(writing_mode),
+ RootOfStackingContextLevel);
+
+ if !self.base.absolute_position_info.layers_needed_for_positioned_flows &&
+ !self.base.flags.needs_layer() {
+ // We didn't need a layer.
+ //
+ // TODO(#781, pcwalton): `z-index`.
+ self.base.display_list =
+ mem::replace(&mut self.base.display_list,
+ DisplayList::new()).flatten(PositionedDescendantStackingLevel(0));
+ return
+ }
+
+ // If we got here, then we need a new layer.
+ let layer_rect = self.base.position.union(&self.base.overflow);
+ let size = Size2D(layer_rect.size.inline.to_nearest_px() as uint,
+ layer_rect.size.block.to_nearest_px() as uint);
+ let origin = Point2D(layer_rect.start.i.to_nearest_px() as uint,
+ layer_rect.start.b.to_nearest_px() as uint);
+ let scroll_policy = if self.is_fixed() {
+ FixedPosition
+ } else {
+ Scrollable
+ };
+ let display_list = mem::replace(&mut self.base.display_list, DisplayList::new());
+ let new_layer = RenderLayer {
+ id: self.layer_id(0),
+ display_list: Arc::new(display_list.flatten(ContentStackingLevel)),
+ position: Rect(origin, size),
+ background_color: color::rgba(1.0, 1.0, 1.0, 0.0),
+ scroll_policy: scroll_policy,
+ };
+ self.base.layers.push(new_layer)
+ }
+
+ /// Return the block-start outer edge of the hypothetical box for an absolute flow.
+ ///
+ /// This is wrt its parent flow box.
+ ///
+ /// During normal layout assign-block-size, the absolute flow's position is
+ /// roughly set to its static position (the position it would have had in
+ /// the normal flow).
+ fn get_hypothetical_block_start_edge(&self) -> Au {
+ self.base.position.start.b
+ }
+
+ /// Assigns the computed inline-start content edge and inline-size to all the children of this block flow.
+ /// Also computes whether each child will be impacted by floats.
+ ///
+ /// `#[inline(always)]` because this is called only from block or table inline-size assignment and
+ /// the code for block layout is significantly simpler.
+ #[inline(always)]
+ pub fn propagate_assigned_inline_size_to_children(&mut self,
+ inline_start_content_edge: Au,
+ content_inline_size: Au,
+ opt_col_inline_sizes: Option<Vec<Au>>) {
+ // Keep track of whether floats could impact each child.
+ let mut inline_start_floats_impact_child = self.base.flags.impacted_by_left_floats();
+ let mut inline_end_floats_impact_child = self.base.flags.impacted_by_right_floats();
+
+ let absolute_static_i_offset = if self.is_positioned() {
+ // This flow is the containing block. The static X offset will be the inline-start padding
+ // edge.
+ self.fragment.border_padding.inline_start
+ - self.fragment.style().logical_border_width().inline_start
+ } else {
+ // For kids, the inline-start margin edge will be at our inline-start content edge. The current static
+ // offset is at our inline-start margin edge. So move in to the inline-start content edge.
+ self.base.absolute_static_i_offset + inline_start_content_edge
+ };
+
+ let fixed_static_i_offset = self.base.fixed_static_i_offset + inline_start_content_edge;
+ let flags = self.base.flags.clone();
+
+ // This value is used only for table cells.
+ let mut inline_start_margin_edge = inline_start_content_edge;
+
+ // The inline-size of the last float, if there was one. This is used for estimating the inline-sizes of
+ // block formatting contexts. (We estimate that the inline-size of any block formatting context
+ // that we see will be based on the inline-size of the containing block as well as the last float
+ // seen before it.)
+ let mut last_float_inline_size = None;
+
+ for (i, kid) in self.base.child_iter().enumerate() {
+ if kid.is_block_flow() {
+ let kid_block = kid.as_block();
+ kid_block.base.absolute_static_i_offset = absolute_static_i_offset;
+ kid_block.base.fixed_static_i_offset = fixed_static_i_offset;
+
+ if kid_block.is_float() {
+ last_float_inline_size = Some(kid_block.base.intrinsic_inline_sizes.preferred_inline_size)
+ } else {
+ kid_block.previous_float_inline_size = last_float_inline_size
+ }
+ }
+
+ // The inline-start margin edge of the child flow is at our inline-start content edge, and its inline-size
+ // is our content inline-size.
+ flow::mut_base(kid).position.start.i = inline_start_content_edge;
+ flow::mut_base(kid).position.size.inline = content_inline_size;
+
+ // Determine float impaction.
+ match kid.float_clearance() {
+ clear::none => {}
+ clear::left => inline_start_floats_impact_child = false,
+ clear::right => inline_end_floats_impact_child = false,
+ clear::both => {
+ inline_start_floats_impact_child = false;
+ inline_end_floats_impact_child = false;
+ }
+ }
+ {
+ let kid_base = flow::mut_base(kid);
+ inline_start_floats_impact_child = inline_start_floats_impact_child ||
+ kid_base.flags.has_left_floated_descendants();
+ inline_end_floats_impact_child = inline_end_floats_impact_child ||
+ kid_base.flags.has_right_floated_descendants();
+ kid_base.flags.set_impacted_by_left_floats(inline_start_floats_impact_child);
+ kid_base.flags.set_impacted_by_right_floats(inline_end_floats_impact_child);
+ }
+
+ // Handle tables.
+ match opt_col_inline_sizes {
+ Some(ref col_inline_sizes) => {
+ propagate_column_inline_sizes_to_child(kid,
+ i,
+ content_inline_size,
+ col_inline_sizes.as_slice(),
+ &mut inline_start_margin_edge)
+ }
+ None => {}
+ }
+
+ // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
+ //
+ // TODO(#2018, pcwalton): Do this in the cascade instead.
+ flow::mut_base(kid).flags.propagate_text_alignment_from_parent(flags.clone())
+ }
+ }
+
+ /// Determines the type of formatting context this is. See the definition of
+ /// `FormattingContextType`.
+ fn formatting_context_type(&self) -> FormattingContextType {
+ let style = self.fragment.style();
+ if style.get_box().float != float::none {
+ return OtherFormattingContext
+ }
+ match style.get_box().display {
+ display::table_cell | display::table_caption | display::inline_block => {
+ OtherFormattingContext
+ }
+ _ if style.get_box().position == position::static_ &&
+ style.get_box().overflow != overflow::visible => {
+ BlockFormattingContext
+ }
+ _ => NonformattingContext,
+ }
+ }
+}
+
+impl Flow for BlockFlow {
+ fn class(&self) -> FlowClass {
+ BlockFlowClass
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ self
+ }
+
+ fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow {
+ self
+ }
+
+ /// Returns the direction that this flow clears floats in, if any.
+ fn float_clearance(&self) -> clear::T {
+ self.fragment.style().get_box().clear
+ }
+
+ /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
+ ///
+ /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When called on
+ /// this flow, all child flows have had their minimum and preferred inline-sizes set. This function
+ /// must decide minimum/preferred inline-sizes based on its children's inline-sizes and the dimensions of
+ /// any fragments it is responsible for flowing.
+ ///
+ /// TODO(pcwalton): Inline blocks.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("bubble_inline_sizes {:s}", self.base.debug_id());
+
+ let mut flags = self.base.flags;
+ flags.set_has_left_floated_descendants(false);
+ flags.set_has_right_floated_descendants(false);
+
+ // If this block has a fixed width, just use that for the minimum
+ // and preferred width, rather than bubbling up children inline
+ // width.
+ let fixed_width = match self.fragment.style().get_box().width {
+ LPA_Length(_) => true,
+ _ => false,
+ };
+
+ // Find the maximum inline-size from children.
+ let mut intrinsic_inline_sizes = IntrinsicISizes::new();
+ for child_ctx in self.base.child_iter() {
+ assert!(child_ctx.is_block_flow() ||
+ child_ctx.is_inline_flow() ||
+ child_ctx.is_table_kind());
+
+ let child_base = flow::mut_base(child_ctx);
+
+ if !fixed_width {
+ intrinsic_inline_sizes.minimum_inline_size =
+ geometry::max(intrinsic_inline_sizes.minimum_inline_size,
+ child_base.intrinsic_inline_sizes.total_minimum_inline_size());
+ intrinsic_inline_sizes.preferred_inline_size =
+ geometry::max(intrinsic_inline_sizes.preferred_inline_size,
+ child_base.intrinsic_inline_sizes.total_preferred_inline_size());
+ }
+
+ flags.union_floated_descendants_flags(child_base.flags);
+ }
+
+ let fragment_intrinsic_inline_sizes = self.fragment.intrinsic_inline_sizes();
+ intrinsic_inline_sizes.minimum_inline_size = geometry::max(intrinsic_inline_sizes.minimum_inline_size,
+ fragment_intrinsic_inline_sizes.minimum_inline_size);
+ intrinsic_inline_sizes.preferred_inline_size = geometry::max(intrinsic_inline_sizes.preferred_inline_size,
+ fragment_intrinsic_inline_sizes.preferred_inline_size);
+ intrinsic_inline_sizes.surround_inline_size = fragment_intrinsic_inline_sizes.surround_inline_size;
+ self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
+
+ match self.fragment.style().get_box().float {
+ float::none => {}
+ float::left => flags.set_has_left_floated_descendants(true),
+ float::right => flags.set_has_right_floated_descendants(true),
+ }
+ self.base.flags = flags
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ ///
+ /// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
+ /// contexts.
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("block::assign_inline_sizes {:s}", self.base.debug_id());
+
+ debug!("assign_inline_sizes({}): assigning inline_size for flow",
+ if self.is_float() {
+ "float"
+ } else {
+ "block"
+ });
+
+ if self.is_root() {
+ debug!("Setting root position");
+ self.base.position.start = LogicalPoint::zero(self.base.writing_mode);
+ self.base.position.size.inline = LogicalSize::from_physical(
+ self.base.writing_mode, layout_context.shared.screen_size).inline;
+ self.base.floats = Floats::new(self.base.writing_mode);
+
+ // The root element is never impacted by floats.
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+ }
+
+ // Our inline-size was set to the inline-size of the containing block by the flow's parent. Now compute
+ // the real value.
+ let containing_block_inline_size = self.base.position.size.inline;
+ self.compute_used_inline_size(layout_context, containing_block_inline_size);
+ if self.is_float() {
+ self.float.get_mut_ref().containing_inline_size = containing_block_inline_size;
+ }
+
+ // Formatting contexts are never impacted by floats.
+ match self.formatting_context_type() {
+ NonformattingContext => {}
+ BlockFormattingContext => {
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+
+ // We can't actually compute the inline-size of this block now, because floats might
+ // affect it. Speculate that its inline-size is equal to the inline-size computed above minus
+ // the inline-size of the previous float.
+ match self.previous_float_inline_size {
+ None => {}
+ Some(previous_float_inline_size) => {
+ self.fragment.border_box.size.inline =
+ self.fragment.border_box.size.inline - previous_float_inline_size
+ }
+ }
+ }
+ OtherFormattingContext => {
+ self.base.flags.set_impacted_by_left_floats(false);
+ self.base.flags.set_impacted_by_right_floats(false);
+ }
+ }
+
+ // Move in from the inline-start border edge
+ let inline_start_content_edge = self.fragment.border_box.start.i + self.fragment.border_padding.inline_start;
+ let padding_and_borders = self.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.fragment.border_box.size.inline - padding_and_borders;
+
+ if self.is_float() {
+ self.base.position.size.inline = content_inline_size;
+ }
+
+ self.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, None);
+ }
+
+ /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
+ /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if
+ /// this child was impacted by floats or false otherwise.
+ ///
+ /// This is called on child flows by the parent. Hence, we can assume that `assign_block-size` has
+ /// already been called on the child (because of the bottom-up traversal).
+ fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>)
+ -> bool {
+ if self.is_float() {
+ self.place_float();
+ return true
+ }
+
+ let impacted = self.base.flags.impacted_by_floats();
+ if impacted {
+ self.assign_block_size(layout_context);
+ }
+ impacted
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+
+ if self.is_replaced_content() {
+ // Assign block-size for fragment if it is an image fragment.
+ self.fragment.assign_replaced_block_size_if_necessary();
+ } else if self.is_float() {
+ debug!("assign_block_size_float: assigning block_size for float");
+ self.assign_block_size_float(ctx);
+ } else if self.is_root() {
+ // Root element margins should never be collapsed according to CSS § 8.3.1.
+ debug!("assign_block_size: assigning block_size for root flow");
+ self.assign_block_size_block_base(ctx, MarginsMayNotCollapse);
+ } else {
+ debug!("assign_block_size: assigning block_size for block");
+ self.assign_block_size_block_base(ctx, MarginsMayCollapse);
+ }
+ }
+
+ fn compute_absolute_position(&mut self) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+
+ if self.is_absolutely_positioned() {
+ let position_start = self.base.position.start.to_physical(
+ self.base.writing_mode, container_size);
+ self.base
+ .absolute_position_info
+ .absolute_containing_block_position = if self.is_fixed() {
+ // The viewport is initially at (0, 0).
+ position_start
+ } else {
+ // Absolute position of the containing block + position of absolute flow w/r/t the
+ // containing block.
+ self.base.absolute_position_info.absolute_containing_block_position
+ + position_start
+ };
+
+ // Set the absolute position, which will be passed down later as part
+ // of containing block details for absolute descendants.
+ self.base.abs_position =
+ self.base.absolute_position_info.absolute_containing_block_position;
+ }
+
+ // For relatively-positioned descendants, the containing block formed by a block is just
+ // the content box. The containing block for absolutely-positioned descendants, on the
+ // other hand, is only established if we are positioned.
+ let relative_offset =
+ self.fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+ if self.is_positioned() {
+ self.base.absolute_position_info.absolute_containing_block_position =
+ self.base.abs_position
+ + (self.generated_containing_block_rect().start
+ + relative_offset).to_physical(self.base.writing_mode, container_size)
+ }
+
+ let float_offset = if self.is_float() {
+ self.float.get_ref().rel_pos
+ } else {
+ LogicalPoint::zero(self.base.writing_mode)
+ };
+
+ // Compute absolute position info for children.
+ let mut absolute_position_info = self.base.absolute_position_info;
+ absolute_position_info.relative_containing_block_size = self.fragment.content_box().size;
+ absolute_position_info.layers_needed_for_positioned_flows =
+ self.base.flags.layers_needed_for_descendants();
+
+ // Process children.
+ let this_position = self.base.abs_position;
+ let writing_mode = self.base.writing_mode;
+ for kid in self.base.child_iter() {
+ if !kid.is_absolutely_positioned() {
+ let kid_base = flow::mut_base(kid);
+ kid_base.abs_position = this_position + (
+ kid_base.position.start
+ .add_point(&float_offset)
+ + relative_offset).to_physical(writing_mode, container_size);
+ kid_base.absolute_position_info = absolute_position_info
+ }
+ }
+
+ // Process absolute descendant links.
+ for absolute_descendant in self.base.abs_descendants.iter() {
+ flow::mut_base(absolute_descendant).absolute_position_info = absolute_position_info
+ }
+ }
+
+ fn mark_as_root(&mut self) {
+ self.is_root = true
+ }
+
+ /// Return true if store overflow is delayed for this flow.
+ ///
+ /// Currently happens only for absolutely positioned flows.
+ fn is_store_overflow_delayed(&mut self) -> bool {
+ self.is_absolutely_positioned()
+ }
+
+ fn is_root(&self) -> bool {
+ self.is_root
+ }
+
+ fn is_float(&self) -> bool {
+ self.float.is_some()
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> position::T {
+ self.fragment.style.get_box().position
+ }
+
+ /// Return true if this is the root of an Absolute flow tree.
+ ///
+ /// It has to be either relatively positioned or the Root flow.
+ fn is_root_of_absolute_flow_tree(&self) -> bool {
+ self.is_relatively_positioned() || self.is_root()
+ }
+
+ /// Return the dimensions of the containing block generated by this flow for absolutely-
+ /// positioned descendants. For block flows, this is the padding box.
+ fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
+ self.fragment.border_box - self.fragment.style().logical_border_width()
+ }
+
+ fn layer_id(&self, fragment_index: uint) -> LayerId {
+ // FIXME(#2010, pcwalton): This is a hack and is totally bogus in the presence of pseudo-
+ // elements. But until we have incremental reflow we can't do better--we recreate the flow
+ // for every DOM node so otherwise we nuke layers on every reflow.
+ LayerId(self.fragment.node.id(), fragment_index)
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.is_positioned()
+ }
+}
+
+impl fmt::Show for BlockFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_float() {
+ write!(f, "FloatFlow: {}", self.fragment)
+ } else if self.is_root() {
+ write!(f, "RootFlow: {}", self.fragment)
+ } else {
+ write!(f, "BlockFlow: {}", self.fragment)
+ }
+ }
+}
+
+/// The inputs for the inline-sizes-and-margins constraint equation.
+pub struct ISizeConstraintInput {
+ pub computed_inline_size: MaybeAuto,
+ pub inline_start_margin: MaybeAuto,
+ pub inline_end_margin: MaybeAuto,
+ pub inline_start: MaybeAuto,
+ pub inline_end: MaybeAuto,
+ pub available_inline_size: Au,
+ pub static_i_offset: Au,
+}
+
+impl ISizeConstraintInput {
+ pub fn new(computed_inline_size: MaybeAuto,
+ inline_start_margin: MaybeAuto,
+ inline_end_margin: MaybeAuto,
+ inline_start: MaybeAuto,
+ inline_end: MaybeAuto,
+ available_inline_size: Au,
+ static_i_offset: Au)
+ -> ISizeConstraintInput {
+ ISizeConstraintInput {
+ computed_inline_size: computed_inline_size,
+ inline_start_margin: inline_start_margin,
+ inline_end_margin: inline_end_margin,
+ inline_start: inline_start,
+ inline_end: inline_end,
+ available_inline_size: available_inline_size,
+ static_i_offset: static_i_offset,
+ }
+ }
+}
+
+/// The solutions for the inline-size-and-margins constraint equation.
+pub struct ISizeConstraintSolution {
+ pub inline_start: Au,
+ pub inline_end: Au,
+ pub inline_size: Au,
+ pub margin_inline_start: Au,
+ pub margin_inline_end: Au
+}
+
+impl ISizeConstraintSolution {
+ pub fn new(inline_size: Au, margin_inline_start: Au, margin_inline_end: Au) -> ISizeConstraintSolution {
+ ISizeConstraintSolution {
+ inline_start: Au(0),
+ inline_end: Au(0),
+ inline_size: inline_size,
+ margin_inline_start: margin_inline_start,
+ margin_inline_end: margin_inline_end,
+ }
+ }
+
+ fn for_absolute_flow(inline_start: Au,
+ inline_end: Au,
+ inline_size: Au,
+ margin_inline_start: Au,
+ margin_inline_end: Au)
+ -> ISizeConstraintSolution {
+ ISizeConstraintSolution {
+ inline_start: inline_start,
+ inline_end: inline_end,
+ inline_size: inline_size,
+ margin_inline_start: margin_inline_start,
+ margin_inline_end: margin_inline_end,
+ }
+ }
+}
+
+// Trait to encapsulate the ISize and Margin calculation.
+//
+// CSS Section 10.3
+pub trait ISizeAndMarginsComputer {
+ /// Compute the inputs for the ISize constraint equation.
+ ///
+ /// This is called only once to compute the initial inputs. For
+ /// calculation involving min-inline-size and max-inline-size, we don't need to
+ /// recompute these.
+ fn compute_inline_size_constraint_inputs(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> ISizeConstraintInput {
+ let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx);
+ let computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, ctx);
+
+ block.fragment.compute_border_padding_margins(containing_block_inline_size);
+
+ let style = block.fragment.style();
+
+ // The text alignment of a block flow is the text alignment of its box's style.
+ block.base.flags.set_text_align(style.get_inheritedtext().text_align);
+
+ let margin = style.logical_margin();
+ let position = style.logical_position();
+
+ let available_inline_size = containing_block_inline_size - block.fragment.border_padding.inline_start_end();
+ return ISizeConstraintInput::new(
+ computed_inline_size,
+ MaybeAuto::from_style(margin.inline_start, containing_block_inline_size),
+ MaybeAuto::from_style(margin.inline_end, containing_block_inline_size),
+ MaybeAuto::from_style(position.inline_start, containing_block_inline_size),
+ MaybeAuto::from_style(position.inline_end, containing_block_inline_size),
+ available_inline_size,
+ block.static_i_offset());
+ }
+
+ /// Set the used values for inline-size and margins got from the relevant constraint equation.
+ ///
+ /// This is called only once.
+ ///
+ /// Set:
+ /// + used values for content inline-size, inline-start margin, and inline-end margin for this flow's box.
+ /// + x-coordinate of this flow's box.
+ /// + x-coordinate of the flow wrt its Containing Block (if this is an absolute flow).
+ fn set_inline_size_constraint_solutions(&self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution) {
+ let inline_size;
+ {
+ let fragment = block.fragment();
+ fragment.margin.inline_start = solution.margin_inline_start;
+ fragment.margin.inline_end = solution.margin_inline_end;
+
+ // The associated fragment has the border box of this flow.
+ // Left border edge.
+ fragment.border_box.start.i = fragment.margin.inline_start;
+ // Border box inline-size.
+ inline_size = solution.inline_size + fragment.border_padding.inline_start_end();
+ fragment.border_box.size.inline = inline_size;
+ }
+
+ // We also resize the block itself, to ensure that overflow is not calculated
+ // as the inline-size of our parent. We might be smaller and we might be larger if we
+ // overflow.
+ let flow = flow::mut_base(block);
+ flow.position.size.inline = inline_size;
+ }
+
+ /// Set the x coordinate of the given flow if it is absolutely positioned.
+ fn set_flow_x_coord_if_necessary(&self, _: &mut BlockFlow, _: ISizeConstraintSolution) {}
+
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution;
+
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> MaybeAuto {
+ MaybeAuto::from_style(block.fragment().style().content_inline_size(),
+ self.containing_block_inline_size(block, parent_flow_inline_size, ctx))
+ }
+
+ fn containing_block_inline_size(&self,
+ _: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> Au {
+ parent_flow_inline_size
+ }
+
+ /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
+ ///
+ /// CSS Section 10.4: Minimum and Maximum inline-sizes
+ fn compute_used_inline_size(&self,
+ block: &mut BlockFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
+
+ let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, ctx);
+
+ let mut solution = self.solve_inline_size_constraints(block, &input);
+
+ // If the tentative used inline-size is greater than 'max-inline-size', inline-size should be recalculated,
+ // but this time using the computed value of 'max-inline-size' as the computed value for 'inline-size'.
+ match specified_or_none(block.fragment().style().max_inline_size(), containing_block_inline_size) {
+ Some(max_inline_size) if max_inline_size < solution.inline_size => {
+ input.computed_inline_size = Specified(max_inline_size);
+ solution = self.solve_inline_size_constraints(block, &input);
+ }
+ _ => {}
+ }
+
+ // If the resulting inline-size is smaller than 'min-inline-size', inline-size should be recalculated,
+ // but this time using the value of 'min-inline-size' as the computed value for 'inline-size'.
+ let computed_min_inline_size = specified(block.fragment().style().min_inline_size(),
+ containing_block_inline_size);
+ if computed_min_inline_size > solution.inline_size {
+ input.computed_inline_size = Specified(computed_min_inline_size);
+ solution = self.solve_inline_size_constraints(block, &input);
+ }
+
+ self.set_inline_size_constraint_solutions(block, solution);
+ self.set_flow_x_coord_if_necessary(block, solution);
+ }
+
+ /// Computes inline-start and inline-end margins and inline-size.
+ ///
+ /// This is used by both replaced and non-replaced Blocks.
+ ///
+ /// CSS 2.1 Section 10.3.3.
+ /// Constraint Equation: margin-inline-start + margin-inline-end + inline-size = available_inline-size
+ /// where available_inline-size = CB inline-size - (horizontal border + padding)
+ fn solve_block_inline_size_constraints(&self,
+ _: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin,
+ input.available_inline_size);
+
+ // If inline-size is not 'auto', and inline-size + margins > available_inline-size, all
+ // 'auto' margins are treated as 0.
+ let (inline_start_margin, inline_end_margin) = match computed_inline_size {
+ Auto => (inline_start_margin, inline_end_margin),
+ Specified(inline_size) => {
+ let inline_start = inline_start_margin.specified_or_zero();
+ let inline_end = inline_end_margin.specified_or_zero();
+
+ if (inline_start + inline_end + inline_size) > available_inline_size {
+ (Specified(inline_start), Specified(inline_end))
+ } else {
+ (inline_start_margin, inline_end_margin)
+ }
+ }
+ };
+
+ // Invariant: inline-start_margin + inline-size + inline-end_margin == available_inline-size
+ let (inline_start_margin, inline_size, inline_end_margin) = match (inline_start_margin, computed_inline_size, inline_end_margin) {
+ // If all have a computed value other than 'auto', the system is
+ // over-constrained so we discard the end margin.
+ (Specified(margin_start), Specified(inline_size), Specified(_margin_end)) =>
+ (margin_start, inline_size, available_inline_size - (margin_start + inline_size)),
+
+ // If exactly one value is 'auto', solve for it
+ (Auto, Specified(inline_size), Specified(margin_end)) =>
+ (available_inline_size - (inline_size + margin_end), inline_size, margin_end),
+ (Specified(margin_start), Auto, Specified(margin_end)) =>
+ (margin_start, available_inline_size - (margin_start + margin_end), margin_end),
+ (Specified(margin_start), Specified(inline_size), Auto) =>
+ (margin_start, inline_size, available_inline_size - (margin_start + inline_size)),
+
+ // If inline-size is set to 'auto', any other 'auto' value becomes '0',
+ // and inline-size is solved for
+ (Auto, Auto, Specified(margin_end)) =>
+ (Au::new(0), available_inline_size - margin_end, margin_end),
+ (Specified(margin_start), Auto, Auto) =>
+ (margin_start, available_inline_size - margin_start, Au::new(0)),
+ (Auto, Auto, Auto) =>
+ (Au::new(0), available_inline_size, Au::new(0)),
+
+ // If inline-start and inline-end margins are auto, they become equal
+ (Auto, Specified(inline_size), Auto) => {
+ let margin = (available_inline_size - inline_size).scale_by(0.5);
+ (margin, inline_size, margin)
+ }
+ };
+ ISizeConstraintSolution::new(inline_size, inline_start_margin, inline_end_margin)
+ }
+}
+
+/// The different types of Blocks.
+///
+/// They mainly differ in the way inline-size and block-sizes and margins are calculated
+/// for them.
+struct AbsoluteNonReplaced;
+struct AbsoluteReplaced;
+struct BlockNonReplaced;
+struct BlockReplaced;
+struct FloatNonReplaced;
+struct FloatReplaced;
+
+impl ISizeAndMarginsComputer for AbsoluteNonReplaced {
+ /// Solve the horizontal constraint equation for absolute non-replaced elements.
+ ///
+ /// CSS Section 10.3.7
+ /// Constraint equation:
+ /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
+ /// = absolute containing block inline-size - (horizontal padding and border)
+ /// [aka available_inline-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let &ISizeConstraintInput {
+ computed_inline_size,
+ inline_start_margin,
+ inline_end_margin,
+ inline_start,
+ inline_end,
+ available_inline_size,
+ static_i_offset,
+ ..
+ } = input;
+
+ // TODO: Check for direction of parent flow (NOT Containing Block)
+ // when right-to-left is implemented.
+ // Assume direction is 'ltr' for now
+
+ // Distance from the inline-start edge of the Absolute Containing Block to the
+ // inline-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_inline_start = static_i_offset;
+
+ let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end, computed_inline_size) {
+ (Auto, Auto, Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let inline_start = static_position_inline_start;
+ // Now it is the same situation as inline-start Specified and inline-end
+ // and inline-size Auto.
+
+ // Set inline-end to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start + margin_start + margin_end));
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end), Specified(inline_size)) => {
+ match (inline_start_margin, inline_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_inline_size - inline_start - inline_end - inline_size;
+ if total_margin_val < Au(0) {
+ // margin-inline-start becomes 0 because direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ (inline_start, inline_end, inline_size, Au(0), total_margin_val)
+ } else {
+ // Equal margins
+ (inline_start, inline_end, inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ }
+ (Specified(margin_start), Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum)
+ }
+ (Auto, Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end)
+ }
+ (Specified(margin_start), Specified(margin_end)) => {
+ // Values are over-constrained.
+ // Ignore value for 'inline-end' cos direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ }
+ }
+ // For the rest of the cases, auto values for margin are set to 0
+
+ // If only one is Auto, solve for it
+ (Auto, Specified(inline_end), Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Auto, Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_end + margin_start + margin_end;
+ (inline_start, inline_end, available_inline_size - sum, margin_start, margin_end)
+ }
+
+ // If inline-size is auto, then inline-size is shrink-to-fit. Solve for the
+ // non-auto value.
+ (Specified(inline_start), Auto, Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Set inline-end to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_start + margin_start + margin_end));
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Auto, Specified(inline_end), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Set inline-start to zero to calculate inline-size
+ let inline_size = block.get_shrink_to_fit_inline_size(
+ available_inline_size - (inline_end + margin_start + margin_end));
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+
+ (Auto, Auto, Specified(inline_size)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ // Setting 'inline-start' to static position because direction is 'ltr'.
+ // TODO: Handle 'rtl' when it is implemented.
+ let inline_start = static_position_inline_start;
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ };
+ ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au {
+ block.containing_block_size(ctx.shared.screen_size).inline
+ }
+
+ fn set_flow_x_coord_if_necessary(&self,
+ block: &mut BlockFlow,
+ solution: ISizeConstraintSolution) {
+ // Set the x-coordinate of the absolute flow wrt to its containing block.
+ block.base.position.start.i = solution.inline_start;
+ }
+}
+
+impl ISizeAndMarginsComputer for AbsoluteReplaced {
+ /// Solve the horizontal constraint equation for absolute replaced elements.
+ ///
+ /// `static_i_offset`: total offset of current flow's hypothetical
+ /// position (static position) from its actual Containing Block.
+ ///
+ /// CSS Section 10.3.8
+ /// Constraint equation:
+ /// inline-start + inline-end + inline-size + margin-inline-start + margin-inline-end
+ /// = absolute containing block inline-size - (horizontal padding and border)
+ /// [aka available_inline-size]
+ ///
+ /// Return the solution for the equation.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let &ISizeConstraintInput {
+ computed_inline_size,
+ inline_start_margin,
+ inline_end_margin,
+ inline_start,
+ inline_end,
+ available_inline_size,
+ static_i_offset,
+ ..
+ } = input;
+ // TODO: Check for direction of static-position Containing Block (aka
+ // parent flow, _not_ the actual Containing Block) when right-to-left
+ // is implemented
+ // Assume direction is 'ltr' for now
+ // TODO: Handle all the cases for 'rtl' direction.
+
+ let inline_size = match computed_inline_size {
+ Specified(w) => w,
+ _ => fail!("{} {}",
+ "The used value for inline_size for absolute replaced flow",
+ "should have already been calculated by now.")
+ };
+
+ // Distance from the inline-start edge of the Absolute Containing Block to the
+ // inline-start margin edge of a hypothetical box that would have been the
+ // first box of the element.
+ let static_position_inline_start = static_i_offset;
+
+ let (inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end) = match (inline_start, inline_end) {
+ (Auto, Auto) => {
+ let inline_start = static_position_inline_start;
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ // If only one is Auto, solve for it
+ (Auto, Specified(inline_end)) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_end + inline_size + margin_start + margin_end;
+ (available_inline_size - sum, inline_end, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Auto) => {
+ let margin_start = inline_start_margin.specified_or_zero();
+ let margin_end = inline_end_margin.specified_or_zero();
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ (Specified(inline_start), Specified(inline_end)) => {
+ match (inline_start_margin, inline_end_margin) {
+ (Auto, Auto) => {
+ let total_margin_val = available_inline_size - inline_start - inline_end - inline_size;
+ if total_margin_val < Au(0) {
+ // margin-inline-start becomes 0 because direction is 'ltr'.
+ (inline_start, inline_end, inline_size, Au(0), total_margin_val)
+ } else {
+ // Equal margins
+ (inline_start, inline_end, inline_size,
+ total_margin_val.scale_by(0.5),
+ total_margin_val.scale_by(0.5))
+ }
+ }
+ (Specified(margin_start), Auto) => {
+ let sum = inline_start + inline_end + inline_size + margin_start;
+ (inline_start, inline_end, inline_size, margin_start, available_inline_size - sum)
+ }
+ (Auto, Specified(margin_end)) => {
+ let sum = inline_start + inline_end + inline_size + margin_end;
+ (inline_start, inline_end, inline_size, available_inline_size - sum, margin_end)
+ }
+ (Specified(margin_start), Specified(margin_end)) => {
+ // Values are over-constrained.
+ // Ignore value for 'inline-end' cos direction is 'ltr'.
+ let sum = inline_start + inline_size + margin_start + margin_end;
+ (inline_start, available_inline_size - sum, inline_size, margin_start, margin_end)
+ }
+ }
+ }
+ };
+ ISizeConstraintSolution::for_absolute_flow(inline_start, inline_end, inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ _: Au,
+ ctx: &LayoutContext)
+ -> MaybeAuto {
+ let containing_block_inline_size = block.containing_block_size(ctx.shared.screen_size).inline;
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(containing_block_inline_size);
+ // For replaced absolute flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+
+ fn containing_block_inline_size(&self, block: &mut BlockFlow, _: Au, ctx: &LayoutContext) -> Au {
+ block.containing_block_size(ctx.shared.screen_size).inline
+ }
+
+ fn set_flow_x_coord_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution) {
+ // Set the x-coordinate of the absolute flow wrt to its containing block.
+ block.base.position.start.i = solution.inline_start;
+ }
+}
+
+impl ISizeAndMarginsComputer for BlockNonReplaced {
+ /// Compute inline-start and inline-end margins and inline-size.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ self.solve_block_inline_size_constraints(block, input)
+ }
+}
+
+impl ISizeAndMarginsComputer for BlockReplaced {
+ /// Compute inline-start and inline-end margins and inline-size.
+ ///
+ /// ISize has already been calculated. We now calculate the margins just
+ /// like for non-replaced blocks.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ match input.computed_inline_size {
+ Specified(_) => {},
+ Auto => fail!("BlockReplaced: inline_size should have been computed by now")
+ };
+ self.solve_block_inline_size_constraints(block, input)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> MaybeAuto {
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+
+}
+
+impl ISizeAndMarginsComputer for FloatNonReplaced {
+ /// CSS Section 10.3.5
+ ///
+ /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
+ fn solve_inline_size_constraints(&self,
+ block: &mut BlockFlow,
+ input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin, available_inline_size) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin,
+ input.available_inline_size);
+ let margin_inline_start = inline_start_margin.specified_or_zero();
+ let margin_inline_end = inline_end_margin.specified_or_zero();
+ let available_inline_size_float = available_inline_size - margin_inline_start - margin_inline_end;
+ let shrink_to_fit = block.get_shrink_to_fit_inline_size(available_inline_size_float);
+ let inline_size = computed_inline_size.specified_or_default(shrink_to_fit);
+ debug!("assign_inline_sizes_float -- inline_size: {}", inline_size);
+ ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
+ }
+}
+
+impl ISizeAndMarginsComputer for FloatReplaced {
+ /// CSS Section 10.3.5
+ ///
+ /// If inline-size is computed as 'auto', the used value is the 'shrink-to-fit' inline-size.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ let (computed_inline_size, inline_start_margin, inline_end_margin) = (input.computed_inline_size,
+ input.inline_start_margin,
+ input.inline_end_margin);
+ let margin_inline_start = inline_start_margin.specified_or_zero();
+ let margin_inline_end = inline_end_margin.specified_or_zero();
+ let inline_size = match computed_inline_size {
+ Specified(w) => w,
+ Auto => fail!("FloatReplaced: inline_size should have been computed by now")
+ };
+ debug!("assign_inline_sizes_float -- inline_size: {}", inline_size);
+ ISizeConstraintSolution::new(inline_size, margin_inline_start, margin_inline_end)
+ }
+
+ /// Calculate used value of inline-size just like we do for inline replaced elements.
+ fn initial_computed_inline_size(&self,
+ block: &mut BlockFlow,
+ parent_flow_inline_size: Au,
+ _: &LayoutContext)
+ -> MaybeAuto {
+ let fragment = block.fragment();
+ fragment.assign_replaced_inline_size_if_necessary(parent_flow_inline_size);
+ // For replaced block flow, the rest of the constraint solving will
+ // take inline-size to be specified as the value computed here.
+ Specified(fragment.content_inline_size())
+ }
+}
+
+fn propagate_column_inline_sizes_to_child(kid: &mut Flow,
+ child_index: uint,
+ content_inline_size: Au,
+ column_inline_sizes: &[Au],
+ inline_start_margin_edge: &mut Au) {
+ // If kid is table_rowgroup or table_row, the column inline-sizes info should be copied from its
+ // parent.
+ //
+ // FIXME(pcwalton): This seems inefficient. Reference count it instead?
+ let inline_size = if kid.is_table() || kid.is_table_rowgroup() || kid.is_table_row() {
+ *kid.col_inline_sizes() = column_inline_sizes.iter().map(|&x| x).collect();
+
+ // ISize of kid flow is our content inline-size.
+ content_inline_size
+ } else if kid.is_table_cell() {
+ // If kid is table_cell, the x offset and inline-size for each cell should be
+ // calculated from parent's column inline-sizes info.
+ *inline_start_margin_edge = if child_index == 0 {
+ Au(0)
+ } else {
+ *inline_start_margin_edge + column_inline_sizes[child_index - 1]
+ };
+
+ column_inline_sizes[child_index]
+ } else {
+ // ISize of kid flow is our content inline-size.
+ content_inline_size
+ };
+
+ let kid_base = flow::mut_base(kid);
+ kid_base.position.start.i = *inline_start_margin_edge;
+ kid_base.position.size.inline = inline_size;
+}
+
diff --git a/components/layout/construct.rs b/components/layout/construct.rs
new file mode 100644
index 00000000000..0f832bacfb8
--- /dev/null
+++ b/components/layout/construct.rs
@@ -0,0 +1,1049 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Creates flows and fragments from a DOM tree via a bottom-up, incremental traversal of the DOM.
+//!
+//! Each step of the traversal considers the node and existing flow, if there is one. If a node is
+//! not dirty and an existing flow exists, then the traversal reuses that flow. Otherwise, it
+//! proceeds to construct either a flow or a `ConstructionItem`. A construction item is a piece of
+//! intermediate data that goes with a DOM node and hasn't found its "home" yet-maybe it's a box,
+//! maybe it's an absolute or fixed position thing that hasn't found its containing block yet.
+//! Construction items bubble up the tree from children to parents until they find their homes.
+//!
+//! TODO(pcwalton): There is no incremental reflow yet. This scheme requires that nodes either have
+//! weak references to flows or that there be some mechanism to efficiently (O(1) time) "blow
+//! apart" a flow tree and have the flows migrate "home" to their respective DOM nodes while we
+//! perform flow tree construction. The precise mechanism for this will take some experimentation
+//! to get right.
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use block::BlockFlow;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{Flow, ImmutableFlowUtils, MutableOwnedFlowUtils};
+use flow::{Descendants, AbsDescendants};
+use flow;
+use flow_ref::FlowRef;
+use fragment::{Fragment, GenericFragment, IframeFragment, IframeFragmentInfo};
+use fragment::{ImageFragment, ImageFragmentInfo, SpecificFragmentInfo, TableFragment};
+use fragment::{TableCellFragment, TableColumnFragment, TableColumnFragmentInfo};
+use fragment::{TableRowFragment, TableWrapperFragment, UnscannedTextFragment};
+use fragment::{UnscannedTextFragmentInfo};
+use inline::{InlineFragments, InlineFlow};
+use parallel;
+use table_wrapper::TableWrapperFlow;
+use table::TableFlow;
+use table_caption::TableCaptionFlow;
+use table_colgroup::TableColGroupFlow;
+use table_rowgroup::TableRowGroupFlow;
+use table_row::TableRowFlow;
+use table_cell::TableCellFlow;
+use text::TextRunScanner;
+use util::{LayoutDataAccess, OpaqueNodeMethods};
+use wrapper::{PostorderNodeMutTraversal, TLayoutNode, ThreadSafeLayoutNode};
+use wrapper::{Before, BeforeBlock, After, AfterBlock, Normal};
+
+use gfx::display_list::OpaqueNode;
+use script::dom::element::{HTMLIFrameElementTypeId, HTMLImageElementTypeId};
+use script::dom::element::{HTMLObjectElementTypeId};
+use script::dom::element::{HTMLTableColElementTypeId, HTMLTableDataCellElementTypeId};
+use script::dom::element::{HTMLTableElementTypeId, HTMLTableHeaderCellElementTypeId};
+use script::dom::element::{HTMLTableRowElementTypeId, HTMLTableSectionElementTypeId};
+use script::dom::node::{CommentNodeTypeId, DoctypeNodeTypeId, DocumentFragmentNodeTypeId};
+use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId};
+use script::dom::node::{TextNodeTypeId};
+use script::dom::htmlobjectelement::is_image_data;
+use servo_util::namespace;
+use std::mem;
+use std::sync::atomics::Relaxed;
+use style::ComputedValues;
+use style::computed_values::{display, position, float};
+use sync::Arc;
+use url::Url;
+
+/// The results of flow construction for a DOM node.
+pub enum ConstructionResult {
+ /// This node contributes nothing at all (`display: none`). Alternately, this is what newly
+ /// created nodes have their `ConstructionResult` set to.
+ NoConstructionResult,
+
+ /// This node contributed a flow at the proper position in the tree.
+ /// Nothing more needs to be done for this node. It has bubbled up fixed
+ /// and absolute descendant flows that have a CB above it.
+ FlowConstructionResult(FlowRef, AbsDescendants),
+
+ /// This node contributed some object or objects that will be needed to construct a proper flow
+ /// later up the tree, but these objects have not yet found their home.
+ ConstructionItemConstructionResult(ConstructionItem),
+}
+
+/// Represents the output of flow construction for a DOM node that has not yet resulted in a
+/// complete flow. Construction items bubble up the tree until they find a `Flow` to be
+/// attached to.
+pub enum ConstructionItem {
+ /// Inline fragments and associated {ib} splits that have not yet found flows.
+ InlineFragmentsConstructionItem(InlineFragmentsConstructionResult),
+ /// Potentially ignorable whitespace.
+ WhitespaceConstructionItem(OpaqueNode, Arc<ComputedValues>),
+ /// TableColumn Fragment
+ TableColumnFragmentConstructionItem(Fragment),
+}
+
+/// Represents inline fragments and {ib} splits that are bubbling up from an inline.
+pub struct InlineFragmentsConstructionResult {
+ /// Any {ib} splits that we're bubbling up.
+ pub splits: Vec<InlineBlockSplit>,
+
+ /// Any fragments that succeed the {ib} splits.
+ pub fragments: InlineFragments,
+
+ /// Any absolute descendants that we're bubbling up.
+ pub abs_descendants: AbsDescendants,
+}
+
+/// Represents an {ib} split that has not yet found the containing block that it belongs to. This
+/// is somewhat tricky. An example may be helpful. For this DOM fragment:
+///
+/// <span>
+/// A
+/// <div>B</div>
+/// C
+/// </span>
+///
+/// The resulting `ConstructionItem` for the outer `span` will be:
+///
+/// InlineFragmentsConstructionItem(Some(~[
+/// InlineBlockSplit {
+/// predecessor_fragments: ~[
+/// A
+/// ],
+/// block: ~BlockFlow {
+/// B
+/// },
+/// }),~[
+/// C
+/// ])
+pub struct InlineBlockSplit {
+ /// The inline fragments that precede the flow.
+ pub predecessors: InlineFragments,
+
+ /// The flow that caused this {ib} split.
+ pub flow: FlowRef,
+}
+
+/// Holds inline fragments that we're gathering for children of an inline node.
+struct InlineFragmentsAccumulator {
+ /// The list of fragments.
+ fragments: InlineFragments,
+
+ /// Whether we've created a range to enclose all the fragments. This will be Some() if the outer node
+ /// is an inline and None otherwise.
+ enclosing_style: Option<Arc<ComputedValues>>,
+}
+
+impl InlineFragmentsAccumulator {
+ fn new() -> InlineFragmentsAccumulator {
+ InlineFragmentsAccumulator {
+ fragments: InlineFragments::new(),
+ enclosing_style: None,
+ }
+ }
+
+ fn from_inline_node(node: &ThreadSafeLayoutNode) -> InlineFragmentsAccumulator {
+ let fragments = InlineFragments::new();
+ InlineFragmentsAccumulator {
+ fragments: fragments,
+ enclosing_style: Some(node.style().clone()),
+ }
+ }
+
+ fn finish(self) -> InlineFragments {
+ let InlineFragmentsAccumulator {
+ fragments: mut fragments,
+ enclosing_style
+ } = self;
+
+ match enclosing_style {
+ Some(enclosing_style) => {
+ for frag in fragments.fragments.mut_iter() {
+ frag.add_inline_context_style(enclosing_style.clone());
+ }
+ }
+ None => {}
+ }
+ fragments
+ }
+}
+
+enum WhitespaceStrippingMode {
+ NoWhitespaceStripping,
+ StripWhitespaceFromStart,
+ StripWhitespaceFromEnd,
+}
+
+/// An object that knows how to create flows.
+pub struct FlowConstructor<'a, 'b> {
+ /// The layout context.
+ pub layout_context: &'b LayoutContext<'b>,
+}
+
+impl<'a, 'b> FlowConstructor<'a, 'b> {
+ /// Creates a new flow constructor.
+ pub fn new<'b>(layout_context: &'b LayoutContext)
+ -> FlowConstructor<'a, 'b> {
+ FlowConstructor {
+ layout_context: layout_context,
+ }
+ }
+
+ /// Builds the `ImageFragmentInfo` for the given image. This is out of line to guide inlining.
+ fn build_fragment_info_for_image(&mut self, node: &ThreadSafeLayoutNode, url: Option<Url>)
+ -> SpecificFragmentInfo {
+ match url {
+ None => GenericFragment,
+ Some(url) => {
+ // FIXME(pcwalton): The fact that image fragments store the cache within them makes
+ // little sense to me.
+ ImageFragment(ImageFragmentInfo::new(node, url, self.layout_context.shared.image_cache.clone()))
+ }
+ }
+ }
+
+ /// Builds specific `Fragment` info for the given node.
+ pub fn build_specific_fragment_info_for_node(&mut self, node: &ThreadSafeLayoutNode)
+ -> SpecificFragmentInfo {
+ match node.type_id() {
+ Some(ElementNodeTypeId(HTMLImageElementTypeId)) => {
+ self.build_fragment_info_for_image(node, node.image_url())
+ }
+ Some(ElementNodeTypeId(HTMLIFrameElementTypeId)) => {
+ IframeFragment(IframeFragmentInfo::new(node))
+ }
+ Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => {
+ let data = node.get_object_data();
+ self.build_fragment_info_for_image(node, data)
+ }
+ Some(ElementNodeTypeId(HTMLTableElementTypeId)) => TableWrapperFragment,
+ Some(ElementNodeTypeId(HTMLTableColElementTypeId)) => {
+ TableColumnFragment(TableColumnFragmentInfo::new(node))
+ }
+ Some(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) |
+ Some(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => TableCellFragment,
+ Some(ElementNodeTypeId(HTMLTableRowElementTypeId)) |
+ Some(ElementNodeTypeId(HTMLTableSectionElementTypeId)) => TableRowFragment,
+ None | Some(TextNodeTypeId) => UnscannedTextFragment(UnscannedTextFragmentInfo::new(node)),
+ _ => GenericFragment,
+ }
+ }
+
+ /// Creates an inline flow from a set of inline fragments, then adds it as a child of the given flow
+ /// or pushes it onto the given flow list.
+ ///
+ /// `#[inline(always)]` because this is performance critical and LLVM will not inline it
+ /// otherwise.
+ #[inline(always)]
+ fn flush_inline_fragments_to_flow_or_list(&mut self,
+ fragment_accumulator: InlineFragmentsAccumulator,
+ flow: &mut FlowRef,
+ flow_list: &mut Vec<FlowRef>,
+ whitespace_stripping: WhitespaceStrippingMode,
+ node: &ThreadSafeLayoutNode) {
+ let mut fragments = fragment_accumulator.finish();
+ if fragments.is_empty() { return };
+
+ match whitespace_stripping {
+ NoWhitespaceStripping => {}
+ StripWhitespaceFromStart => {
+ fragments.strip_ignorable_whitespace_from_start();
+ if fragments.is_empty() { return };
+ }
+ StripWhitespaceFromEnd => {
+ fragments.strip_ignorable_whitespace_from_end();
+ if fragments.is_empty() { return };
+ }
+ }
+
+ let mut inline_flow = box InlineFlow::from_fragments((*node).clone(), fragments);
+ let (ascent, descent) = inline_flow.compute_minimum_ascent_and_descent(self.layout_context.font_context(), &**node.style());
+ inline_flow.minimum_block_size_above_baseline = ascent;
+ inline_flow.minimum_depth_below_baseline = descent;
+ let mut inline_flow = inline_flow as Box<Flow>;
+ TextRunScanner::new().scan_for_runs(self.layout_context.font_context(), inline_flow);
+ let mut inline_flow = FlowRef::new(inline_flow);
+ inline_flow.finish(self.layout_context);
+
+ if flow.get().need_anonymous_flow(inline_flow.get()) {
+ flow_list.push(inline_flow)
+ } else {
+ flow.add_new_child(inline_flow)
+ }
+ }
+
+ fn build_block_flow_using_children_construction_result(&mut self,
+ flow: &mut FlowRef,
+ consecutive_siblings: &mut Vec<FlowRef>,
+ node: &ThreadSafeLayoutNode,
+ kid: ThreadSafeLayoutNode,
+ inline_fragment_accumulator:
+ &mut InlineFragmentsAccumulator,
+ abs_descendants: &mut Descendants,
+ first_fragment: &mut bool) {
+ match kid.swap_out_construction_result() {
+ NoConstructionResult => {}
+ FlowConstructionResult(kid_flow, kid_abs_descendants) => {
+ // If kid_flow is TableCaptionFlow, kid_flow should be added under
+ // TableWrapperFlow.
+ if flow.get().is_table() && kid_flow.get().is_table_caption() {
+ kid.set_flow_construction_result(FlowConstructionResult(
+ kid_flow,
+ Descendants::new()))
+ } else if flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow)
+ } else {
+ // Flush any inline fragments that we were gathering up. This allows us to handle
+ // {ib} splits.
+ debug!("flushing {} inline box(es) to flow A",
+ inline_fragment_accumulator.fragments.len());
+ self.flush_inline_fragments_to_flow_or_list(
+ mem::replace(inline_fragment_accumulator, InlineFragmentsAccumulator::new()),
+ flow,
+ consecutive_siblings,
+ StripWhitespaceFromStart,
+ node);
+ if !consecutive_siblings.is_empty() {
+ let consecutive_siblings = mem::replace(consecutive_siblings, vec!());
+ self.generate_anonymous_missing_child(consecutive_siblings,
+ flow,
+ node);
+ }
+ flow.add_new_child(kid_flow);
+ }
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(InlineFragmentsConstructionItem(
+ InlineFragmentsConstructionResult {
+ splits: splits,
+ fragments: successor_fragments,
+ abs_descendants: kid_abs_descendants,
+ })) => {
+ // Add any {ib} splits.
+ for split in splits.move_iter() {
+ // Pull apart the {ib} split object and push its predecessor fragments
+ // onto the list.
+ let InlineBlockSplit {
+ predecessors: predecessors,
+ flow: kid_flow
+ } = split;
+ inline_fragment_accumulator.fragments.push_all(predecessors);
+
+ // If this is the first fragment in flow, then strip ignorable
+ // whitespace per CSS 2.1 § 9.2.1.1.
+ let whitespace_stripping = if *first_fragment {
+ *first_fragment = false;
+ StripWhitespaceFromStart
+ } else {
+ NoWhitespaceStripping
+ };
+
+ // Flush any inline fragments that we were gathering up.
+ debug!("flushing {} inline box(es) to flow A",
+ inline_fragment_accumulator.fragments.len());
+ self.flush_inline_fragments_to_flow_or_list(
+ mem::replace(inline_fragment_accumulator,
+ InlineFragmentsAccumulator::new()),
+ flow,
+ consecutive_siblings,
+ whitespace_stripping,
+ node);
+
+ // Push the flow generated by the {ib} split onto our list of
+ // flows.
+ if flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow)
+ } else {
+ flow.add_new_child(kid_flow)
+ }
+ }
+
+ // Add the fragments to the list we're maintaining.
+ inline_fragment_accumulator.fragments.push_all(successor_fragments);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node, whitespace_style)) => {
+ // Add whitespace results. They will be stripped out later on when
+ // between block elements, and retained when between inline elements.
+ let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
+ let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node,
+ whitespace_style.clone(),
+ fragment_info);
+ inline_fragment_accumulator.fragments.push(&mut fragment, whitespace_style);
+ }
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ }
+ }
+ }
+
+ /// Build block flow for current node using information from children nodes.
+ ///
+ /// Consume results from children and combine them, handling {ib} splits.
+ /// Block flows and inline flows thus created will become the children of
+ /// this block flow.
+ /// Also, deal with the absolute and fixed descendants bubbled up by
+ /// children nodes.
+ fn build_flow_using_children(&mut self, mut flow: FlowRef, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ // Gather up fragments for the inline flows we might need to create.
+ let mut inline_fragment_accumulator = InlineFragmentsAccumulator::new();
+ let mut consecutive_siblings = vec!();
+ let mut first_fragment = true;
+
+ // List of absolute descendants, in tree order.
+ let mut abs_descendants = Descendants::new();
+ for kid in node.children() {
+ if kid.get_pseudo_element_type() != Normal {
+ self.process(&kid);
+ }
+
+ self.build_block_flow_using_children_construction_result(&mut flow,
+ &mut consecutive_siblings,
+ node,
+ kid,
+ &mut inline_fragment_accumulator,
+ &mut abs_descendants,
+ &mut first_fragment);
+ }
+
+ // Perform a final flush of any inline fragments that we were gathering up to handle {ib}
+ // splits, after stripping ignorable whitespace.
+ self.flush_inline_fragments_to_flow_or_list(inline_fragment_accumulator,
+ &mut flow,
+ &mut consecutive_siblings,
+ StripWhitespaceFromEnd,
+ node);
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings, &mut flow, node);
+ }
+
+ // The flow is done.
+ flow.finish(self.layout_context);
+ let is_positioned = flow.get_mut().as_block().is_positioned();
+ let is_fixed_positioned = flow.get_mut().as_block().is_fixed();
+ let is_absolutely_positioned = flow.get_mut().as_block().is_absolutely_positioned();
+ if is_positioned {
+ // This is the CB for all the absolute descendants.
+ flow.set_abs_descendants(abs_descendants);
+
+ abs_descendants = Descendants::new();
+
+ if is_fixed_positioned || is_absolutely_positioned {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its CB.
+ abs_descendants.push(flow.clone());
+ }
+ }
+ FlowConstructionResult(flow, abs_descendants)
+ }
+
+ /// Builds a flow for a node with `display: block`. This yields a `BlockFlow` with possibly
+ /// other `BlockFlow`s or `InlineFlow`s underneath it, depending on whether {ib} splits needed
+ /// to happen.
+ fn build_flow_for_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let flow = box BlockFlow::from_node(self, node) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds the flow for a node with `float: {left|right}`. This yields a float `BlockFlow` with
+ /// a `BlockFlow` underneath it.
+ fn build_flow_for_floated_block(&mut self, node: &ThreadSafeLayoutNode, float_kind: FloatKind)
+ -> ConstructionResult {
+ let flow = box BlockFlow::float_from_node(self, node, float_kind) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Concatenates the fragments of kids, adding in our own borders/padding/margins if necessary.
+ /// Returns the `InlineFragmentsConstructionResult`, if any. There will be no
+ /// `InlineFragmentsConstructionResult` if this node consisted entirely of ignorable whitespace.
+ fn build_fragments_for_nonreplaced_inline_content(&mut self, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ let mut opt_inline_block_splits: Vec<InlineBlockSplit> = Vec::new();
+ let mut fragment_accumulator = InlineFragmentsAccumulator::from_inline_node(node);
+ let mut abs_descendants = Descendants::new();
+
+ // Concatenate all the fragments of our kids, creating {ib} splits as necessary.
+ for kid in node.children() {
+ if kid.get_pseudo_element_type() != Normal {
+ self.process(&kid);
+ }
+ match kid.swap_out_construction_result() {
+ NoConstructionResult => {}
+ FlowConstructionResult(flow, kid_abs_descendants) => {
+ // {ib} split. Flush the accumulator to our new split and make a new
+ // accumulator to hold any subsequent fragments we come across.
+ let split = InlineBlockSplit {
+ predecessors:
+ mem::replace(&mut fragment_accumulator,
+ InlineFragmentsAccumulator::from_inline_node(node)).finish(),
+ flow: flow,
+ };
+ opt_inline_block_splits.push(split);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(InlineFragmentsConstructionItem(
+ InlineFragmentsConstructionResult {
+ splits: splits,
+ fragments: successors,
+ abs_descendants: kid_abs_descendants,
+ })) => {
+
+ // Bubble up {ib} splits.
+ for split in splits.move_iter() {
+ let InlineBlockSplit {
+ predecessors: predecessors,
+ flow: kid_flow
+ } = split;
+ fragment_accumulator.fragments.push_all(predecessors);
+
+ let split = InlineBlockSplit {
+ predecessors:
+ mem::replace(&mut fragment_accumulator,
+ InlineFragmentsAccumulator::from_inline_node(node))
+ .finish(),
+ flow: kid_flow,
+ };
+ opt_inline_block_splits.push(split)
+ }
+
+ // Push residual fragments.
+ fragment_accumulator.fragments.push_all(successors);
+ abs_descendants.push_descendants(kid_abs_descendants);
+ }
+ ConstructionItemConstructionResult(WhitespaceConstructionItem(whitespace_node,
+ whitespace_style))
+ => {
+ // Instantiate the whitespace fragment.
+ let fragment_info = UnscannedTextFragment(UnscannedTextFragmentInfo::from_text(" ".to_string()));
+ let mut fragment = Fragment::from_opaque_node_and_style(whitespace_node,
+ whitespace_style.clone(),
+ fragment_info);
+ fragment_accumulator.fragments.push(&mut fragment, whitespace_style)
+ }
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(_)) => {
+ // TODO: Implement anonymous table objects for missing parents
+ // CSS 2.1 § 17.2.1, step 3-2
+ }
+ }
+ }
+
+ // Finally, make a new construction result.
+ if opt_inline_block_splits.len() > 0 || fragment_accumulator.fragments.len() > 0
+ || abs_descendants.len() > 0 {
+ let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult {
+ splits: opt_inline_block_splits,
+ fragments: fragment_accumulator.finish(),
+ abs_descendants: abs_descendants,
+ });
+ ConstructionItemConstructionResult(construction_item)
+ } else {
+ NoConstructionResult
+ }
+ }
+
+ /// Creates an `InlineFragmentsConstructionResult` for replaced content. Replaced content doesn't
+ /// render its children, so this just nukes a child's fragments and creates a `Fragment`.
+ fn build_fragments_for_replaced_inline_content(&mut self, node: &ThreadSafeLayoutNode)
+ -> ConstructionResult {
+ for kid in node.children() {
+ kid.set_flow_construction_result(NoConstructionResult)
+ }
+
+ // If this node is ignorable whitespace, bail out now.
+ //
+ // FIXME(#2001, pcwalton): Don't do this if there's padding or borders.
+ if node.is_ignorable_whitespace() {
+ let opaque_node = OpaqueNodeMethods::from_thread_safe_layout_node(node);
+ return ConstructionItemConstructionResult(WhitespaceConstructionItem(
+ opaque_node,
+ node.style().clone()))
+ }
+
+ let mut fragments = InlineFragments::new();
+ fragments.push(&mut Fragment::new(self, node), node.style().clone());
+
+ let construction_item = InlineFragmentsConstructionItem(InlineFragmentsConstructionResult {
+ splits: Vec::new(),
+ fragments: fragments,
+ abs_descendants: Descendants::new(),
+ });
+ ConstructionItemConstructionResult(construction_item)
+ }
+
+ /// Builds one or more fragments for a node with `display: inline`. This yields an
+ /// `InlineFragmentsConstructionResult`.
+ fn build_fragments_for_inline(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ // Is this node replaced content?
+ if !node.is_replaced_content() {
+ // Go to a path that concatenates our kids' fragments.
+ self.build_fragments_for_nonreplaced_inline_content(node)
+ } else {
+ // Otherwise, just nuke our kids' fragments, create our fragment if any, and be done
+ // with it.
+ self.build_fragments_for_replaced_inline_content(node)
+ }
+ }
+
+ /// TableCaptionFlow is populated underneath TableWrapperFlow
+ fn place_table_caption_under_table_wrapper(&mut self,
+ table_wrapper_flow: &mut FlowRef,
+ node: &ThreadSafeLayoutNode) {
+ for kid in node.children() {
+ match kid.swap_out_construction_result() {
+ NoConstructionResult | ConstructionItemConstructionResult(_) => {}
+ FlowConstructionResult(kid_flow, _) => {
+ // Only kid flows with table-caption are matched here.
+ assert!(kid_flow.get().is_table_caption());
+ table_wrapper_flow.add_new_child(kid_flow);
+ }
+ }
+ }
+ }
+
+ /// Generates an anonymous table flow according to CSS 2.1 § 17.2.1, step 2.
+ /// If necessary, generate recursively another anonymous table flow.
+ fn generate_anonymous_missing_child(&mut self,
+ child_flows: Vec<FlowRef>,
+ flow: &mut FlowRef,
+ node: &ThreadSafeLayoutNode) {
+ let mut anonymous_flow = flow.get().generate_missing_child_flow(node);
+ let mut consecutive_siblings = vec!();
+ for kid_flow in child_flows.move_iter() {
+ if anonymous_flow.get().need_anonymous_flow(kid_flow.get()) {
+ consecutive_siblings.push(kid_flow);
+ continue;
+ }
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings,
+ &mut anonymous_flow,
+ node);
+ consecutive_siblings = vec!();
+ }
+ anonymous_flow.add_new_child(kid_flow);
+ }
+ if !consecutive_siblings.is_empty() {
+ self.generate_anonymous_missing_child(consecutive_siblings, &mut anonymous_flow, node);
+ }
+ // The flow is done.
+ anonymous_flow.finish(self.layout_context);
+ flow.add_new_child(anonymous_flow);
+ }
+
+ /// Builds a flow for a node with `display: table`. This yields a `TableWrapperFlow` with possibly
+ /// other `TableCaptionFlow`s or `TableFlow`s underneath it.
+ fn build_flow_for_table_wrapper(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableWrapperFragment);
+ let wrapper_flow = box TableWrapperFlow::from_node_and_fragment(node, fragment);
+ let mut wrapper_flow = FlowRef::new(wrapper_flow as Box<Flow>);
+
+ let table_fragment = Fragment::new_from_specific_info(node, TableFragment);
+ let table_flow = box TableFlow::from_node_and_fragment(node, table_fragment);
+ let table_flow = FlowRef::new(table_flow as Box<Flow>);
+
+ // We first populate the TableFlow with other flows than TableCaptionFlow.
+ // We then populate the TableWrapperFlow with TableCaptionFlow, and attach
+ // the TableFlow to the TableWrapperFlow
+ let construction_result = self.build_flow_using_children(table_flow, node);
+ self.place_table_caption_under_table_wrapper(&mut wrapper_flow, node);
+
+ let mut abs_descendants = Descendants::new();
+ let mut fixed_descendants = Descendants::new();
+
+ // NOTE: The order of captions and table are not the same order as in the DOM tree.
+ // All caption blocks are placed before the table flow
+ match construction_result {
+ FlowConstructionResult(table_flow, table_abs_descendants) => {
+ wrapper_flow.add_new_child(table_flow);
+ abs_descendants.push_descendants(table_abs_descendants);
+ }
+ _ => {}
+ }
+
+ // The flow is done.
+ wrapper_flow.finish(self.layout_context);
+ let is_positioned = wrapper_flow.get_mut().as_block().is_positioned();
+ let is_fixed_positioned = wrapper_flow.get_mut().as_block().is_fixed();
+ let is_absolutely_positioned = wrapper_flow.get_mut()
+ .as_block()
+ .is_absolutely_positioned();
+ if is_positioned {
+ // This is the CB for all the absolute descendants.
+ wrapper_flow.set_abs_descendants(abs_descendants);
+
+ abs_descendants = Descendants::new();
+
+ if is_fixed_positioned {
+ // Send itself along with the other fixed descendants.
+ fixed_descendants.push(wrapper_flow.clone());
+ } else if is_absolutely_positioned {
+ // This is now the only absolute flow in the subtree which hasn't yet
+ // reached its CB.
+ abs_descendants.push(wrapper_flow.clone());
+ }
+ }
+ FlowConstructionResult(wrapper_flow, abs_descendants)
+ }
+
+ /// Builds a flow for a node with `display: table-caption`. This yields a `TableCaptionFlow`
+ /// with possibly other `BlockFlow`s or `InlineFlow`s underneath it.
+ fn build_flow_for_table_caption(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let flow = box TableCaptionFlow::from_node(self, node) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-row-group`. This yields a `TableRowGroupFlow`
+ /// with possibly other `TableRowFlow`s underneath it.
+ fn build_flow_for_table_rowgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableRowFragment);
+ let flow = box TableRowGroupFlow::from_node_and_fragment(node, fragment);
+ let flow = flow as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-row`. This yields a `TableRowFlow` with
+ /// possibly other `TableCellFlow`s underneath it.
+ fn build_flow_for_table_row(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableRowFragment);
+ let flow = box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Builds a flow for a node with `display: table-cell`. This yields a `TableCellFlow` with
+ /// possibly other `BlockFlow`s or `InlineFlow`s underneath it.
+ fn build_flow_for_table_cell(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node, TableCellFragment);
+ let flow = box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow>;
+ self.build_flow_using_children(FlowRef::new(flow), node)
+ }
+
+ /// Creates a fragment for a node with `display: table-column`.
+ fn build_fragments_for_table_column(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ // CSS 2.1 § 17.2.1. Treat all child fragments of a `table-column` as `display: none`.
+ for kid in node.children() {
+ kid.set_flow_construction_result(NoConstructionResult)
+ }
+
+ let specific = TableColumnFragment(TableColumnFragmentInfo::new(node));
+ let construction_item = TableColumnFragmentConstructionItem(
+ Fragment::new_from_specific_info(node, specific)
+ );
+ ConstructionItemConstructionResult(construction_item)
+ }
+
+ /// Builds a flow for a node with `display: table-column-group`.
+ /// This yields a `TableColGroupFlow`.
+ fn build_flow_for_table_colgroup(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult {
+ let fragment = Fragment::new_from_specific_info(node,
+ TableColumnFragment(TableColumnFragmentInfo::new(node)));
+ let mut col_fragments = vec!();
+ for kid in node.children() {
+ // CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group`
+ // as `display: none`.
+ match kid.swap_out_construction_result() {
+ ConstructionItemConstructionResult(TableColumnFragmentConstructionItem(fragment)) => {
+ col_fragments.push(fragment);
+ }
+ _ => {}
+ }
+ }
+ if col_fragments.is_empty() {
+ debug!("add TableColumnFragment for empty colgroup");
+ let specific = TableColumnFragment(TableColumnFragmentInfo::new(node));
+ col_fragments.push(Fragment::new_from_specific_info(node, specific));
+ }
+ let flow = box TableColGroupFlow::from_node_and_fragments(node, fragment, col_fragments);
+ let mut flow = FlowRef::new(flow as Box<Flow>);
+ flow.finish(self.layout_context);
+
+ FlowConstructionResult(flow, Descendants::new())
+ }
+}
+
+impl<'a, 'b> PostorderNodeMutTraversal for FlowConstructor<'a, 'b> {
+ // Construct Flow based on 'display', 'position', and 'float' values.
+ //
+ // CSS 2.1 Section 9.7
+ //
+ // TODO: This should actually consult the table in that section to get the
+ // final computed value for 'display'.
+ //
+ // `#[inline(always)]` because this is always called from the traversal function and for some
+ // reason LLVM's inlining heuristics go awry here.
+ #[inline(always)]
+ fn process(&mut self, node: &ThreadSafeLayoutNode) -> bool {
+ // Get the `display` property for this node, and determine whether this node is floated.
+ let (display, float, positioning) = match node.type_id() {
+ None => {
+ // Pseudo-element.
+ let style = node.style();
+ (display::inline, style.get_box().float, style.get_box().position)
+ }
+ Some(ElementNodeTypeId(_)) => {
+ let style = node.style();
+ (style.get_box().display, style.get_box().float, style.get_box().position)
+ }
+ Some(TextNodeTypeId) => (display::inline, float::none, position::static_),
+ Some(CommentNodeTypeId) |
+ Some(DoctypeNodeTypeId) |
+ Some(DocumentFragmentNodeTypeId) |
+ Some(DocumentNodeTypeId) |
+ Some(ProcessingInstructionNodeTypeId) => {
+ (display::none, float::none, position::static_)
+ }
+ };
+
+ debug!("building flow for node: {:?} {:?}", display, float);
+
+ // Switch on display and floatedness.
+ match (display, float, positioning) {
+ // `display: none` contributes no flow construction result. Nuke the flow construction
+ // results of children.
+ (display::none, _, _) => {
+ for child in node.children() {
+ drop(child.swap_out_construction_result())
+ }
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table, _, _) => {
+ let construction_result = self.build_flow_for_table_wrapper(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Absolutely positioned elements will have computed value of
+ // `float` as 'none' and `display` as per the table.
+ // Only match here for block items. If an item is absolutely
+ // positioned, but inline we shouldn't try to construct a block
+ // flow here - instead, let it match the inline case
+ // below.
+ (display::block, _, position::absolute) | (_, _, position::fixed) => {
+ node.set_flow_construction_result(self.build_flow_for_block(node))
+ }
+
+ // Inline items contribute inline fragment construction results.
+ (display::inline, float::none, _) => {
+ let construction_result = self.build_fragments_for_inline(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_caption, _, _) => {
+ let construction_result = self.build_flow_for_table_caption(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_column_group, _, _) => {
+ let construction_result = self.build_flow_for_table_colgroup(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_column, _, _) => {
+ let construction_result = self.build_fragments_for_table_column(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_row_group, _, _) | (display::table_header_group, _, _) |
+ (display::table_footer_group, _, _) => {
+ let construction_result = self.build_flow_for_table_rowgroup(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_row, _, _) => {
+ let construction_result = self.build_flow_for_table_row(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Table items contribute table flow construction results.
+ (display::table_cell, _, _) => {
+ let construction_result = self.build_flow_for_table_cell(node);
+ node.set_flow_construction_result(construction_result)
+ }
+
+ // Block flows that are not floated contribute block flow construction results.
+ //
+ // TODO(pcwalton): Make this only trigger for blocks and handle the other `display`
+ // properties separately.
+
+ (_, float::none, _) => {
+ node.set_flow_construction_result(self.build_flow_for_block(node))
+ }
+
+ // Floated flows contribute float flow construction results.
+ (_, float_value, _) => {
+ let float_kind = FloatKind::from_property(float_value);
+ node.set_flow_construction_result(
+ self.build_flow_for_floated_block(node, float_kind))
+ }
+ }
+
+ true
+ }
+}
+
+/// A utility trait with some useful methods for node queries.
+trait NodeUtils {
+ /// Returns true if this node doesn't render its kids and false otherwise.
+ fn is_replaced_content(&self) -> bool;
+
+ /// Sets the construction result of a flow.
+ fn set_flow_construction_result(&self, result: ConstructionResult);
+
+ /// Replaces the flow construction result in a node with `NoConstructionResult` and returns the
+ /// old value.
+ fn swap_out_construction_result(&self) -> ConstructionResult;
+}
+
+impl<'ln> NodeUtils for ThreadSafeLayoutNode<'ln> {
+ fn is_replaced_content(&self) -> bool {
+ match self.type_id() {
+ Some(TextNodeTypeId) |
+ Some(ProcessingInstructionNodeTypeId) |
+ Some(CommentNodeTypeId) |
+ Some(DoctypeNodeTypeId) |
+ Some(DocumentFragmentNodeTypeId) |
+ Some(DocumentNodeTypeId) |
+ None |
+ Some(ElementNodeTypeId(HTMLImageElementTypeId)) => true,
+ Some(ElementNodeTypeId(HTMLObjectElementTypeId)) => self.has_object_data(),
+ Some(ElementNodeTypeId(_)) => false,
+ }
+ }
+
+ #[inline(always)]
+ fn set_flow_construction_result(&self, result: ConstructionResult) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) =>{
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ layout_data.data.before_flow_construction_result = result
+ },
+ After | AfterBlock => {
+ layout_data.data.after_flow_construction_result = result
+ },
+ Normal => layout_data.data.flow_construction_result = result,
+ }
+ },
+ &None => fail!("no layout data"),
+ }
+ }
+
+ #[inline(always)]
+ fn swap_out_construction_result(&self) -> ConstructionResult {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ mem::replace(&mut layout_data.data.before_flow_construction_result,
+ NoConstructionResult)
+ }
+ After | AfterBlock => {
+ mem::replace(&mut layout_data.data.after_flow_construction_result,
+ NoConstructionResult)
+ }
+ Normal => {
+ mem::replace(&mut layout_data.data.flow_construction_result,
+ NoConstructionResult)
+ }
+ }
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+}
+
+/// Methods for interacting with HTMLObjectElement nodes
+trait ObjectElement {
+ /// Returns None if this node is not matching attributes.
+ fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>);
+
+ /// Returns true if this node has object data that is correct uri.
+ fn has_object_data(&self) -> bool;
+
+ /// Returns the "data" attribute value parsed as a URL
+ fn get_object_data(&self) -> Option<Url>;
+}
+
+impl<'ln> ObjectElement for ThreadSafeLayoutNode<'ln> {
+ fn get_type_and_data(&self) -> (Option<&'static str>, Option<&'static str>) {
+ let elem = self.as_element();
+ (elem.get_attr(&namespace::Null, "type"), elem.get_attr(&namespace::Null, "data"))
+ }
+
+ fn has_object_data(&self) -> bool {
+ match self.get_type_and_data() {
+ (None, Some(uri)) => is_image_data(uri),
+ _ => false
+ }
+ }
+
+ fn get_object_data(&self) -> Option<Url> {
+ match self.get_type_and_data() {
+ (None, Some(uri)) if is_image_data(uri) => Url::parse(uri).ok(),
+ _ => None
+ }
+ }
+}
+
+pub trait FlowConstructionUtils {
+ /// Adds a new flow as a child of this flow. Removes the flow from the given leaf set if
+ /// it's present.
+ fn add_new_child(&mut self, new_child: FlowRef);
+
+ /// Finishes a flow. Once a flow is finished, no more child flows or boxes may be added to it.
+ /// This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic -- inline-size)
+ /// calculation, unless the global `bubble_inline-sizes_separately` flag is on.
+ ///
+ /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes
+ /// properly computed. (This is not, however, a memory safety problem.)
+ fn finish(&mut self, context: &LayoutContext);
+}
+
+impl FlowConstructionUtils for FlowRef {
+ /// Adds a new flow as a child of this flow. Fails if this flow is marked as a leaf.
+ ///
+ /// This must not be public because only the layout constructor can do this.
+ fn add_new_child(&mut self, mut new_child: FlowRef) {
+ {
+ let kid_base = flow::mut_base(new_child.get_mut());
+ kid_base.parallel.parent = parallel::mut_owned_flow_to_unsafe_flow(self);
+ }
+
+ let base = flow::mut_base(self.get_mut());
+ base.children.push_back(new_child);
+ let _ = base.parallel.children_count.fetch_add(1, Relaxed);
+ let _ = base.parallel.children_and_absolute_descendant_count.fetch_add(1, Relaxed);
+ }
+
+ /// Finishes a flow. Once a flow is finished, no more child flows or fragments may be added to
+ /// it. This will normally run the bubble-inline-sizes (minimum and preferred -- i.e. intrinsic --
+ /// inline-size) calculation, unless the global `bubble_inline-sizes_separately` flag is on.
+ ///
+ /// All flows must be finished at some point, or they will not have their intrinsic inline-sizes
+ /// properly computed. (This is not, however, a memory safety problem.)
+ ///
+ /// This must not be public because only the layout constructor can do this.
+ fn finish(&mut self, context: &LayoutContext) {
+ if !context.shared.opts.bubble_inline_sizes_separately {
+ self.get_mut().bubble_inline_sizes(context)
+ }
+ }
+}
+
diff --git a/components/layout/context.rs b/components/layout/context.rs
new file mode 100644
index 00000000000..936314dcb63
--- /dev/null
+++ b/components/layout/context.rs
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Data needed by the layout task.
+
+use css::matching::{ApplicableDeclarationsCache, StyleSharingCandidateCache};
+
+use geom::{Rect, Size2D};
+use gfx::display_list::OpaqueNode;
+use gfx::font_context::FontContext;
+use gfx::font_cache_task::FontCacheTask;
+use script::layout_interface::LayoutChan;
+use servo_msg::constellation_msg::ConstellationChan;
+use servo_net::local_image_cache::LocalImageCache;
+use servo_util::geometry::Au;
+use servo_util::opts::Opts;
+use sync::{Arc, Mutex};
+use std::mem;
+use style::Stylist;
+use url::Url;
+
+struct LocalLayoutContext {
+ font_context: FontContext,
+ applicable_declarations_cache: ApplicableDeclarationsCache,
+ style_sharing_candidate_cache: StyleSharingCandidateCache,
+}
+
+local_data_key!(local_context_key: *mut LocalLayoutContext)
+
+fn create_or_get_local_context(shared_layout_context: &SharedLayoutContext) -> *mut LocalLayoutContext {
+ let maybe_context = local_context_key.get();
+
+ let context = match maybe_context {
+ None => {
+ let context = box LocalLayoutContext {
+ font_context: FontContext::new(shared_layout_context.font_cache_task.clone()),
+ applicable_declarations_cache: ApplicableDeclarationsCache::new(),
+ style_sharing_candidate_cache: StyleSharingCandidateCache::new(),
+ };
+ local_context_key.replace(Some(unsafe { mem::transmute(context) }));
+ local_context_key.get().unwrap()
+ },
+ Some(context) => context
+ };
+
+ *context
+}
+
+pub struct SharedLayoutContext {
+ /// The local image cache.
+ pub image_cache: Arc<Mutex<LocalImageCache>>,
+
+ /// The current screen size.
+ pub screen_size: Size2D<Au>,
+
+ /// A channel up to the constellation.
+ pub constellation_chan: ConstellationChan,
+
+ /// A channel up to the layout task.
+ pub layout_chan: LayoutChan,
+
+ /// Interface to the font cache task.
+ pub font_cache_task: FontCacheTask,
+
+ /// The CSS selector stylist.
+ ///
+ /// FIXME(#2604): Make this no longer an unsafe pointer once we have fast `RWArc`s.
+ pub stylist: *const Stylist,
+
+ /// The root node at which we're starting the layout.
+ pub reflow_root: OpaqueNode,
+
+ /// The URL.
+ pub url: Url,
+
+ /// The command line options.
+ pub opts: Opts,
+
+ /// The dirty rectangle, used during display list building.
+ pub dirty: Rect<Au>,
+}
+
+pub struct LayoutContext<'a> {
+ pub shared: &'a SharedLayoutContext,
+ cached_local_layout_context: *mut LocalLayoutContext,
+}
+
+impl<'a> LayoutContext<'a> {
+ pub fn new(shared_layout_context: &'a SharedLayoutContext) -> LayoutContext<'a> {
+
+ let local_context = create_or_get_local_context(shared_layout_context);
+
+ LayoutContext {
+ shared: shared_layout_context,
+ cached_local_layout_context: local_context,
+ }
+ }
+
+ #[inline(always)]
+ pub fn font_context<'a>(&'a self) -> &'a mut FontContext {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.font_context)
+ }
+ }
+
+ #[inline(always)]
+ pub fn applicable_declarations_cache<'a>(&'a self) -> &'a mut ApplicableDeclarationsCache {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.applicable_declarations_cache)
+ }
+ }
+
+ #[inline(always)]
+ pub fn style_sharing_candidate_cache<'a>(&'a self) -> &'a mut StyleSharingCandidateCache {
+ unsafe {
+ let cached_context = &*self.cached_local_layout_context;
+ mem::transmute(&cached_context.style_sharing_candidate_cache)
+ }
+ }
+}
diff --git a/components/layout/css/matching.rs b/components/layout/css/matching.rs
new file mode 100644
index 00000000000..c1d766c32ca
--- /dev/null
+++ b/components/layout/css/matching.rs
@@ -0,0 +1,558 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// High-level interface to CSS selector matching.
+
+use css::node_style::StyledNode;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use extra::LayoutAuxMethods;
+use util::{LayoutDataAccess, LayoutDataWrapper};
+use wrapper::{LayoutElement, LayoutNode, PostorderNodeMutTraversal, ThreadSafeLayoutNode};
+
+use servo_util::atom::Atom;
+use servo_util::cache::{Cache, LRUCache, SimpleHashCache};
+use servo_util::namespace::Null;
+use servo_util::smallvec::{SmallVec, SmallVec16};
+use servo_util::str::DOMString;
+use std::mem;
+use std::hash::{Hash, sip};
+use std::slice::Items;
+use style::{After, Before, ComputedValues, DeclarationBlock, Stylist, TElement, TNode, cascade};
+use sync::Arc;
+
+pub struct ApplicableDeclarations {
+ pub normal: SmallVec16<DeclarationBlock>,
+ pub before: Vec<DeclarationBlock>,
+ pub after: Vec<DeclarationBlock>,
+
+ /// Whether the `normal` declarations are shareable with other nodes.
+ pub normal_shareable: bool,
+}
+
+impl ApplicableDeclarations {
+ pub fn new() -> ApplicableDeclarations {
+ ApplicableDeclarations {
+ normal: SmallVec16::new(),
+ before: Vec::new(),
+ after: Vec::new(),
+ normal_shareable: false,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.normal = SmallVec16::new();
+ self.before = Vec::new();
+ self.after = Vec::new();
+ self.normal_shareable = false;
+ }
+}
+
+#[deriving(Clone)]
+pub struct ApplicableDeclarationsCacheEntry {
+ pub declarations: Vec<DeclarationBlock>,
+}
+
+impl ApplicableDeclarationsCacheEntry {
+ fn new(slice: &[DeclarationBlock]) -> ApplicableDeclarationsCacheEntry {
+ let mut entry_declarations = Vec::new();
+ for declarations in slice.iter() {
+ entry_declarations.push(declarations.clone());
+ }
+ ApplicableDeclarationsCacheEntry {
+ declarations: entry_declarations,
+ }
+ }
+}
+
+impl PartialEq for ApplicableDeclarationsCacheEntry {
+ fn eq(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
+ let this_as_query = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
+ this_as_query.equiv(other)
+ }
+}
+
+impl Hash for ApplicableDeclarationsCacheEntry {
+ fn hash(&self, state: &mut sip::SipState) {
+ let tmp = ApplicableDeclarationsCacheQuery::new(self.declarations.as_slice());
+ tmp.hash(state);
+ }
+}
+
+struct ApplicableDeclarationsCacheQuery<'a> {
+ declarations: &'a [DeclarationBlock],
+}
+
+impl<'a> ApplicableDeclarationsCacheQuery<'a> {
+ fn new(declarations: &'a [DeclarationBlock]) -> ApplicableDeclarationsCacheQuery<'a> {
+ ApplicableDeclarationsCacheQuery {
+ declarations: declarations,
+ }
+ }
+}
+
+// Workaround for lack of `ptr_eq` on Arcs...
+#[inline]
+fn arc_ptr_eq<T>(a: &Arc<T>, b: &Arc<T>) -> bool {
+ unsafe {
+ let a: uint = mem::transmute_copy(a);
+ let b: uint = mem::transmute_copy(b);
+ a == b
+ }
+}
+
+impl<'a> Equiv<ApplicableDeclarationsCacheEntry> for ApplicableDeclarationsCacheQuery<'a> {
+ fn equiv(&self, other: &ApplicableDeclarationsCacheEntry) -> bool {
+ if self.declarations.len() != other.declarations.len() {
+ return false
+ }
+ for (this, other) in self.declarations.iter().zip(other.declarations.iter()) {
+ if !arc_ptr_eq(&this.declarations, &other.declarations) {
+ return false
+ }
+ }
+ return true
+ }
+}
+
+
+impl<'a> Hash for ApplicableDeclarationsCacheQuery<'a> {
+ fn hash(&self, state: &mut sip::SipState) {
+ for declaration in self.declarations.iter() {
+ let ptr: uint = unsafe {
+ mem::transmute_copy(declaration)
+ };
+ ptr.hash(state);
+ }
+ }
+}
+
+static APPLICABLE_DECLARATIONS_CACHE_SIZE: uint = 32;
+
+pub struct ApplicableDeclarationsCache {
+ cache: SimpleHashCache<ApplicableDeclarationsCacheEntry,Arc<ComputedValues>>,
+}
+
+impl ApplicableDeclarationsCache {
+ pub fn new() -> ApplicableDeclarationsCache {
+ ApplicableDeclarationsCache {
+ cache: SimpleHashCache::new(APPLICABLE_DECLARATIONS_CACHE_SIZE),
+ }
+ }
+
+ fn find(&self, declarations: &[DeclarationBlock]) -> Option<Arc<ComputedValues>> {
+ match self.cache.find_equiv(&ApplicableDeclarationsCacheQuery::new(declarations)) {
+ None => None,
+ Some(ref values) => Some((*values).clone()),
+ }
+ }
+
+ fn insert(&mut self, declarations: &[DeclarationBlock], style: Arc<ComputedValues>) {
+ self.cache.insert(ApplicableDeclarationsCacheEntry::new(declarations), style)
+ }
+}
+
+/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles.
+pub struct StyleSharingCandidateCache {
+ cache: LRUCache<StyleSharingCandidate,()>,
+}
+
+#[deriving(Clone)]
+pub struct StyleSharingCandidate {
+ pub style: Arc<ComputedValues>,
+ pub parent_style: Arc<ComputedValues>,
+ pub local_name: Atom,
+ pub class: Option<DOMString>,
+}
+
+impl PartialEq for StyleSharingCandidate {
+ fn eq(&self, other: &StyleSharingCandidate) -> bool {
+ arc_ptr_eq(&self.style, &other.style) &&
+ arc_ptr_eq(&self.parent_style, &other.parent_style) &&
+ self.local_name == other.local_name &&
+ self.class == other.class
+ }
+}
+
+impl StyleSharingCandidate {
+ /// Attempts to create a style sharing candidate from this node. Returns
+ /// the style sharing candidate or `None` if this node is ineligible for
+ /// style sharing.
+ fn new(node: &LayoutNode) -> Option<StyleSharingCandidate> {
+ let parent_node = match node.parent_node() {
+ None => return None,
+ Some(parent_node) => parent_node,
+ };
+ if !parent_node.is_element() {
+ return None
+ }
+
+ let style = unsafe {
+ match *node.borrow_layout_data_unchecked() {
+ None => return None,
+ Some(ref layout_data_ref) => {
+ match layout_data_ref.shared_data.style {
+ None => return None,
+ Some(ref data) => (*data).clone(),
+ }
+ }
+ }
+ };
+ let parent_style = unsafe {
+ match *parent_node.borrow_layout_data_unchecked() {
+ None => return None,
+ Some(ref parent_layout_data_ref) => {
+ match parent_layout_data_ref.shared_data.style {
+ None => return None,
+ Some(ref data) => (*data).clone(),
+ }
+ }
+ }
+ };
+
+ let mut style = Some(style);
+ let mut parent_style = Some(parent_style);
+ let element = node.as_element();
+ if element.style_attribute().is_some() {
+ return None
+ }
+
+ Some(StyleSharingCandidate {
+ style: style.take_unwrap(),
+ parent_style: parent_style.take_unwrap(),
+ local_name: element.get_local_name().clone(),
+ class: element.get_attr(&Null, "class")
+ .map(|string| string.to_string()),
+ })
+ }
+
+ fn can_share_style_with(&self, element: &LayoutElement) -> bool {
+ if *element.get_local_name() != self.local_name {
+ return false
+ }
+ match (&self.class, element.get_attr(&Null, "class")) {
+ (&None, Some(_)) | (&Some(_), None) => return false,
+ (&Some(ref this_class), Some(element_class)) if element_class != this_class.as_slice() => {
+ return false
+ }
+ (&Some(_), Some(_)) | (&None, None) => {}
+ }
+ true
+ }
+}
+
+static STYLE_SHARING_CANDIDATE_CACHE_SIZE: uint = 40;
+
+impl StyleSharingCandidateCache {
+ pub fn new() -> StyleSharingCandidateCache {
+ StyleSharingCandidateCache {
+ cache: LRUCache::new(STYLE_SHARING_CANDIDATE_CACHE_SIZE),
+ }
+ }
+
+ pub fn iter<'a>(&'a self) -> Items<'a,(StyleSharingCandidate,())> {
+ self.cache.iter()
+ }
+
+ pub fn insert_if_possible(&mut self, node: &LayoutNode) {
+ match StyleSharingCandidate::new(node) {
+ None => {}
+ Some(candidate) => self.cache.insert(candidate, ())
+ }
+ }
+
+ pub fn touch(&mut self, index: uint) {
+ self.cache.touch(index)
+ }
+}
+
+/// The results of attempting to share a style.
+pub enum StyleSharingResult<'ln> {
+ /// We didn't find anybody to share the style with. The boolean indicates whether the style
+ /// is shareable at all.
+ CannotShare(bool),
+ /// The node's style can be shared. The integer specifies the index in the LRU cache that was
+ /// hit.
+ StyleWasShared(uint),
+}
+
+pub trait MatchMethods {
+ /// Performs aux initialization, selector matching, cascading, and flow construction
+ /// sequentially.
+ fn recalc_style_for_subtree(&self,
+ stylist: &Stylist,
+ layout_context: &LayoutContext,
+ applicable_declarations: &mut ApplicableDeclarations,
+ parent: Option<LayoutNode>);
+
+ fn match_node(&self,
+ stylist: &Stylist,
+ applicable_declarations: &mut ApplicableDeclarations,
+ shareable: &mut bool);
+
+ /// Attempts to share a style with another node. This method is unsafe because it depends on
+ /// the `style_sharing_candidate_cache` having only live nodes in it, and we have no way to
+ /// guarantee that at the type system level yet.
+ unsafe fn share_style_if_possible(&self,
+ style_sharing_candidate_cache:
+ &mut StyleSharingCandidateCache,
+ parent: Option<LayoutNode>)
+ -> StyleSharingResult;
+
+ unsafe fn cascade_node(&self,
+ parent: Option<LayoutNode>,
+ applicable_declarations: &ApplicableDeclarations,
+ applicable_declarations_cache: &mut ApplicableDeclarationsCache);
+}
+
+trait PrivateMatchMethods {
+ fn cascade_node_pseudo_element(&self,
+ parent_style: Option<&Arc<ComputedValues>>,
+ applicable_declarations: &[DeclarationBlock],
+ style: &mut Option<Arc<ComputedValues>>,
+ applicable_declarations_cache: &mut
+ ApplicableDeclarationsCache,
+ shareable: bool);
+
+ fn share_style_with_candidate_if_possible(&self,
+ parent_node: Option<LayoutNode>,
+ candidate: &StyleSharingCandidate)
+ -> Option<Arc<ComputedValues>>;
+}
+
+impl<'ln> PrivateMatchMethods for LayoutNode<'ln> {
+ fn cascade_node_pseudo_element(&self,
+ parent_style: Option<&Arc<ComputedValues>>,
+ applicable_declarations: &[DeclarationBlock],
+ style: &mut Option<Arc<ComputedValues>>,
+ applicable_declarations_cache: &mut
+ ApplicableDeclarationsCache,
+ shareable: bool) {
+ let this_style;
+ let cacheable;
+ match parent_style {
+ Some(ref parent_style) => {
+ let cache_entry = applicable_declarations_cache.find(applicable_declarations);
+ let cached_computed_values = match cache_entry {
+ None => None,
+ Some(ref style) => Some(&**style),
+ };
+ let (the_style, is_cacheable) = cascade(applicable_declarations,
+ shareable,
+ Some(&***parent_style),
+ cached_computed_values);
+ cacheable = is_cacheable;
+ this_style = Arc::new(the_style);
+ }
+ None => {
+ let (the_style, is_cacheable) = cascade(applicable_declarations,
+ shareable,
+ None,
+ None);
+ cacheable = is_cacheable;
+ this_style = Arc::new(the_style);
+ }
+ };
+
+ // Cache the resolved style if it was cacheable.
+ if cacheable {
+ applicable_declarations_cache.insert(applicable_declarations, this_style.clone());
+ }
+
+ *style = Some(this_style);
+ }
+
+
+ fn share_style_with_candidate_if_possible(&self,
+ parent_node: Option<LayoutNode>,
+ candidate: &StyleSharingCandidate)
+ -> Option<Arc<ComputedValues>> {
+ assert!(self.is_element());
+
+ let parent_node = match parent_node {
+ Some(ref parent_node) if parent_node.is_element() => parent_node,
+ Some(_) | None => return None,
+ };
+
+ let parent_layout_data: &Option<LayoutDataWrapper> = unsafe {
+ mem::transmute(parent_node.borrow_layout_data_unchecked())
+ };
+ match parent_layout_data {
+ &Some(ref parent_layout_data_ref) => {
+ // Check parent style.
+ let parent_style = parent_layout_data_ref.shared_data.style.as_ref().unwrap();
+ if !arc_ptr_eq(parent_style, &candidate.parent_style) {
+ return None
+ }
+
+ // Check tag names, classes, etc.
+ if !candidate.can_share_style_with(&self.as_element()) {
+ return None
+ }
+
+ return Some(candidate.style.clone())
+ }
+ _ => {}
+ }
+
+ None
+ }
+}
+
+impl<'ln> MatchMethods for LayoutNode<'ln> {
+ fn match_node(&self,
+ stylist: &Stylist,
+ applicable_declarations: &mut ApplicableDeclarations,
+ shareable: &mut bool) {
+ let style_attribute = self.as_element().style_attribute().as_ref();
+
+ applicable_declarations.normal_shareable =
+ stylist.push_applicable_declarations(self,
+ style_attribute,
+ None,
+ &mut applicable_declarations.normal);
+ stylist.push_applicable_declarations(self,
+ None,
+ Some(Before),
+ &mut applicable_declarations.before);
+ stylist.push_applicable_declarations(self,
+ None,
+ Some(After),
+ &mut applicable_declarations.after);
+
+ *shareable = applicable_declarations.normal_shareable
+ }
+
+ unsafe fn share_style_if_possible(&self,
+ style_sharing_candidate_cache:
+ &mut StyleSharingCandidateCache,
+ parent: Option<LayoutNode>)
+ -> StyleSharingResult {
+ if !self.is_element() {
+ return CannotShare(false)
+ }
+ let ok = {
+ let element = self.as_element();
+ element.style_attribute().is_none() && element.get_attr(&Null, "id").is_none()
+ };
+ if !ok {
+ return CannotShare(false)
+ }
+
+ for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
+ match self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
+ Some(shared_style) => {
+ // Yay, cache hit. Share the style.
+ let mut layout_data_ref = self.mutate_layout_data();
+ layout_data_ref.get_mut_ref().shared_data.style = Some(shared_style);
+ return StyleWasShared(i)
+ }
+ None => {}
+ }
+ }
+
+ CannotShare(true)
+ }
+
+ fn recalc_style_for_subtree(&self,
+ stylist: &Stylist,
+ layout_context: &LayoutContext,
+ applicable_declarations: &mut ApplicableDeclarations,
+ parent: Option<LayoutNode>) {
+ self.initialize_layout_data(layout_context.shared.layout_chan.clone());
+
+ // First, check to see whether we can share a style with someone.
+ let sharing_result = unsafe {
+ self.share_style_if_possible(layout_context.style_sharing_candidate_cache(), parent.clone())
+ };
+
+ // Otherwise, match and cascade selectors.
+ match sharing_result {
+ CannotShare(mut shareable) => {
+ if self.is_element() {
+ self.match_node(stylist, applicable_declarations, &mut shareable)
+ }
+
+ unsafe {
+ self.cascade_node(parent,
+ applicable_declarations,
+ layout_context.applicable_declarations_cache())
+ }
+
+ applicable_declarations.clear();
+
+ // Add ourselves to the LRU cache.
+ if shareable {
+ layout_context.style_sharing_candidate_cache().insert_if_possible(self)
+ }
+ }
+ StyleWasShared(index) => layout_context.style_sharing_candidate_cache().touch(index),
+ }
+
+ for kid in self.children() {
+ kid.recalc_style_for_subtree(stylist,
+ layout_context,
+ applicable_declarations,
+ Some(self.clone()))
+ }
+
+ // Construct flows.
+ let layout_node = ThreadSafeLayoutNode::new(self);
+ let mut flow_constructor = FlowConstructor::new(layout_context);
+ flow_constructor.process(&layout_node);
+ }
+
+ unsafe fn cascade_node(&self,
+ parent: Option<LayoutNode>,
+ applicable_declarations: &ApplicableDeclarations,
+ applicable_declarations_cache: &mut ApplicableDeclarationsCache) {
+ // Get our parent's style. This must be unsafe so that we don't touch the parent's
+ // borrow flags.
+ //
+ // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow
+ // enforced safe, race-free access to the parent style.
+ let parent_style = match parent {
+ None => None,
+ Some(parent_node) => {
+ let parent_layout_data = parent_node.borrow_layout_data_unchecked();
+ match *parent_layout_data {
+ None => fail!("no parent data?!"),
+ Some(ref parent_layout_data) => {
+ match parent_layout_data.shared_data.style {
+ None => fail!("parent hasn't been styled yet?!"),
+ Some(ref style) => Some(style),
+ }
+ }
+ }
+ }
+ };
+
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &None => fail!("no layout data"),
+ &Some(ref mut layout_data) => {
+ self.cascade_node_pseudo_element(parent_style,
+ applicable_declarations.normal.as_slice(),
+ &mut layout_data.shared_data.style,
+ applicable_declarations_cache,
+ applicable_declarations.normal_shareable);
+ if applicable_declarations.before.len() > 0 {
+ self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
+ applicable_declarations.before.as_slice(),
+ &mut layout_data.data.before_style,
+ applicable_declarations_cache,
+ false);
+ }
+ if applicable_declarations.after.len() > 0 {
+ self.cascade_node_pseudo_element(Some(layout_data.shared_data.style.get_ref()),
+ applicable_declarations.after.as_slice(),
+ &mut layout_data.data.after_style,
+ applicable_declarations_cache,
+ false);
+ }
+ }
+ }
+ }
+}
+
diff --git a/components/layout/css/node_style.rs b/components/layout/css/node_style.rs
new file mode 100644
index 00000000000..e201b0050aa
--- /dev/null
+++ b/components/layout/css/node_style.rs
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Style retrieval from DOM elements.
+
+use css::node_util::NodeUtil;
+use incremental::RestyleDamage;
+use wrapper::ThreadSafeLayoutNode;
+
+use style::ComputedValues;
+use sync::Arc;
+
+/// Node mixin providing `style` method that returns a `NodeStyle`
+pub trait StyledNode {
+ fn style<'a>(&'a self) -> &'a Arc<ComputedValues>;
+ fn restyle_damage(&self) -> RestyleDamage;
+}
+
+impl<'ln> StyledNode for ThreadSafeLayoutNode<'ln> {
+ #[inline]
+ fn style<'a>(&'a self) -> &'a Arc<ComputedValues> {
+ self.get_css_select_results()
+ }
+
+ fn restyle_damage(&self) -> RestyleDamage {
+ self.get_restyle_damage()
+ }
+}
+
diff --git a/components/layout/css/node_util.rs b/components/layout/css/node_util.rs
new file mode 100644
index 00000000000..150995428ea
--- /dev/null
+++ b/components/layout/css/node_util.rs
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use incremental::RestyleDamage;
+use util::LayoutDataAccess;
+use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
+use wrapper::{After, AfterBlock, Before, BeforeBlock, Normal};
+use std::mem;
+use style::ComputedValues;
+use sync::Arc;
+
+pub trait NodeUtil {
+ fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues>;
+ fn have_css_select_results(&self) -> bool;
+
+ fn get_restyle_damage(&self) -> RestyleDamage;
+ fn set_restyle_damage(&self, damage: RestyleDamage);
+}
+
+impl<'ln> NodeUtil for ThreadSafeLayoutNode<'ln> {
+ /// Returns the style results for the given node. If CSS selector
+ /// matching has not yet been performed, fails.
+ #[inline]
+ fn get_css_select_results<'a>(&'a self) -> &'a Arc<ComputedValues> {
+ unsafe {
+ let layout_data_ref = self.borrow_layout_data();
+ match self.get_pseudo_element_type() {
+ Before | BeforeBlock => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .data
+ .before_style
+ .as_ref()
+ .unwrap())
+ }
+ After | AfterBlock => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .data
+ .after_style
+ .as_ref()
+ .unwrap())
+ }
+ Normal => {
+ mem::transmute(layout_data_ref.as_ref()
+ .unwrap()
+ .shared_data
+ .style
+ .as_ref()
+ .unwrap())
+ }
+ }
+ }
+ }
+
+ /// Does this node have a computed style yet?
+ fn have_css_select_results(&self) -> bool {
+ let layout_data_ref = self.borrow_layout_data();
+ layout_data_ref.get_ref().shared_data.style.is_some()
+ }
+
+ /// Get the description of how to account for recent style changes.
+ /// This is a simple bitfield and fine to copy by value.
+ fn get_restyle_damage(&self) -> RestyleDamage {
+ // For DOM elements, if we haven't computed damage yet, assume the worst.
+ // Other nodes don't have styles.
+ let default = if self.node_is_element() {
+ RestyleDamage::all()
+ } else {
+ RestyleDamage::empty()
+ };
+
+ let layout_data_ref = self.borrow_layout_data();
+ layout_data_ref
+ .get_ref()
+ .data
+ .restyle_damage
+ .unwrap_or(default)
+ }
+
+ /// Set the restyle damage field.
+ fn set_restyle_damage(&self, damage: RestyleDamage) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => layout_data.data.restyle_damage = Some(damage),
+ _ => fail!("no layout data for this node"),
+ }
+ }
+}
diff --git a/components/layout/extra.rs b/components/layout/extra.rs
new file mode 100644
index 00000000000..7b731185272
--- /dev/null
+++ b/components/layout/extra.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Code for managing the layout data in the DOM.
+
+use util::{PrivateLayoutData, LayoutDataAccess, LayoutDataWrapper};
+use wrapper::LayoutNode;
+use script::dom::node::SharedLayoutData;
+use script::layout_interface::LayoutChan;
+
+/// Functionality useful for querying the layout-specific data on DOM nodes.
+pub trait LayoutAuxMethods {
+ fn initialize_layout_data(&self, chan: LayoutChan);
+ fn initialize_style_for_subtree(&self, chan: LayoutChan);
+}
+
+impl<'ln> LayoutAuxMethods for LayoutNode<'ln> {
+ /// Resets layout data and styles for the node.
+ ///
+ /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
+ fn initialize_layout_data(&self, chan: LayoutChan) {
+ let mut layout_data_ref = self.mutate_layout_data();
+ match *layout_data_ref {
+ None => {
+ *layout_data_ref = Some(LayoutDataWrapper {
+ chan: Some(chan),
+ shared_data: SharedLayoutData { style: None },
+ data: box PrivateLayoutData::new(),
+ });
+ }
+ Some(_) => {}
+ }
+ }
+
+ /// Resets layout data and styles for a Node tree.
+ ///
+ /// FIXME(pcwalton): Do this as part of fragment building instead of in a traversal.
+ fn initialize_style_for_subtree(&self, chan: LayoutChan) {
+ for n in self.traverse_preorder() {
+ n.initialize_layout_data(chan.clone());
+ }
+ }
+}
diff --git a/components/layout/floats.rs b/components/layout/floats.rs
new file mode 100644
index 00000000000..94017e6b3f7
--- /dev/null
+++ b/components/layout/floats.rs
@@ -0,0 +1,439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use servo_util::geometry::{Au, max, min};
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize};
+use std::i32;
+use std::fmt;
+use style::computed_values::float;
+use sync::Arc;
+
+/// The kind of float: left or right.
+#[deriving(Clone, Encodable)]
+pub enum FloatKind {
+ FloatLeft,
+ FloatRight
+}
+
+impl FloatKind {
+ pub fn from_property(property: float::T) -> FloatKind {
+ match property {
+ float::none => fail!("can't create a float type from an unfloated property"),
+ float::left => FloatLeft,
+ float::right => FloatRight,
+ }
+ }
+}
+
+/// The kind of clearance: left, right, or both.
+pub enum ClearType {
+ ClearLeft,
+ ClearRight,
+ ClearBoth,
+}
+
+/// Information about a single float.
+#[deriving(Clone)]
+struct Float {
+ /// The boundaries of this float.
+ bounds: LogicalRect<Au>,
+ /// The kind of float: left or right.
+ kind: FloatKind,
+}
+
+impl fmt::Show for Float {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "bounds={} kind={:?}", self.bounds, self.kind)
+ }
+}
+
+/// Information about the floats next to a flow.
+///
+/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `#[deriving(Clone)]` and wrap in a
+/// mutex.
+#[deriving(Clone)]
+struct FloatList {
+ /// Information about each of the floats here.
+ floats: Vec<Float>,
+ /// Cached copy of the maximum block-start offset of the float.
+ max_block_start: Au,
+}
+
+impl FloatList {
+ fn new() -> FloatList {
+ FloatList {
+ floats: vec!(),
+ max_block_start: Au(0),
+ }
+ }
+}
+
+impl fmt::Show for FloatList {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "max_block_start={} floats={:?}", self.max_block_start, self.floats)
+ }
+}
+
+/// Wraps a `FloatList` to avoid allocation in the common case of no floats.
+///
+/// FIXME(pcwalton): When we have fast `MutexArc`s, try removing `CowArc` and use a mutex instead.
+#[deriving(Clone)]
+struct FloatListRef {
+ list: Option<Arc<FloatList>>,
+}
+
+impl FloatListRef {
+ fn new() -> FloatListRef {
+ FloatListRef {
+ list: None,
+ }
+ }
+
+ /// Returns true if the list is allocated and false otherwise. If false, there are guaranteed
+ /// not to be any floats.
+ fn is_present(&self) -> bool {
+ self.list.is_some()
+ }
+
+ #[inline]
+ fn get<'a>(&'a self) -> Option<&'a FloatList> {
+ match self.list {
+ None => None,
+ Some(ref list) => Some(&**list),
+ }
+ }
+
+ #[allow(experimental)]
+ #[inline]
+ fn get_mut<'a>(&'a mut self) -> &'a mut FloatList {
+ if self.list.is_none() {
+ self.list = Some(Arc::new(FloatList::new()))
+ }
+ self.list.as_mut().unwrap().make_unique()
+ }
+}
+
+/// All the information necessary to place a float.
+pub struct PlacementInfo {
+ /// The dimensions of the float.
+ pub size: LogicalSize<Au>,
+ /// The minimum block-start of the float, as determined by earlier elements.
+ pub ceiling: Au,
+ /// The maximum inline-end position of the float, generally determined by the containing block.
+ pub max_inline_size: Au,
+ /// The kind of float.
+ pub kind: FloatKind
+}
+
+impl fmt::Show for PlacementInfo {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "size={} ceiling={} max_inline_size={} kind={:?}", self.size, self.ceiling, self.max_inline_size, self.kind)
+ }
+}
+
+fn range_intersect(block_start_1: Au, block_end_1: Au, block_start_2: Au, block_end_2: Au) -> (Au, Au) {
+ (max(block_start_1, block_start_2), min(block_end_1, block_end_2))
+}
+
+/// Encapsulates information about floats. This is optimized to avoid allocation if there are
+/// no floats, and to avoid copying when translating the list of floats downward.
+#[deriving(Clone)]
+pub struct Floats {
+ /// The list of floats.
+ list: FloatListRef,
+ /// The offset of the flow relative to the first float.
+ offset: LogicalSize<Au>,
+ pub writing_mode: WritingMode,
+}
+
+impl fmt::Show for Floats {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.list.get() {
+ None => {
+ write!(f, "[empty]")
+ }
+ Some(list) => {
+ write!(f, "offset={} floats={}", self.offset, list)
+ }
+ }
+ }
+}
+
+impl Floats {
+ /// Creates a new `Floats` object.
+ pub fn new(writing_mode: WritingMode) -> Floats {
+ Floats {
+ list: FloatListRef::new(),
+ offset: LogicalSize::zero(writing_mode),
+ writing_mode: writing_mode,
+ }
+ }
+
+ /// Adjusts the recorded offset of the flow relative to the first float.
+ pub fn translate(&mut self, delta: LogicalSize<Au>) {
+ self.offset = self.offset + delta
+ }
+
+ /// Returns the position of the last float in flow coordinates.
+ pub fn last_float_pos(&self) -> Option<LogicalPoint<Au>> {
+ match self.list.get() {
+ None => None,
+ Some(list) => {
+ match list.floats.last() {
+ None => None,
+ Some(float) => Some(float.bounds.start + self.offset),
+ }
+ }
+ }
+ }
+
+ /// Returns a rectangle that encloses the region from block-start to block-start + block-size, with inline-size small
+ /// enough that it doesn't collide with any floats. max_x is the x-coordinate beyond which
+ /// floats have no effect. (Generally this is the containing block inline-size.)
+ pub fn available_rect(&self, block_start: Au, block_size: Au, max_x: Au) -> Option<LogicalRect<Au>> {
+ let list = match self.list.get() {
+ None => return None,
+ Some(list) => list,
+ };
+
+ let block_start = block_start - self.offset.block;
+
+ debug!("available_rect: trying to find space at {}", block_start);
+
+ // Relevant dimensions for the inline-end-most inline-start float
+ let mut max_inline_start = Au(0) - self.offset.inline;
+ let mut l_block_start = None;
+ let mut l_block_end = None;
+ // Relevant dimensions for the inline-start-most inline-end float
+ let mut min_inline_end = max_x - self.offset.inline;
+ let mut r_block_start = None;
+ let mut r_block_end = None;
+
+ // Find the float collisions for the given vertical range.
+ for float in list.floats.iter() {
+ debug!("available_rect: Checking for collision against float");
+ let float_pos = float.bounds.start;
+ let float_size = float.bounds.size;
+
+ debug!("float_pos: {}, float_size: {}", float_pos, float_size);
+ match float.kind {
+ FloatLeft if float_pos.i + float_size.inline > max_inline_start &&
+ float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
+ max_inline_start = float_pos.i + float_size.inline;
+
+ l_block_start = Some(float_pos.b);
+ l_block_end = Some(float_pos.b + float_size.block);
+
+ debug!("available_rect: collision with inline_start float: new max_inline_start is {}",
+ max_inline_start);
+ }
+ FloatRight if float_pos.i < min_inline_end &&
+ float_pos.b + float_size.block > block_start && float_pos.b < block_start + block_size => {
+ min_inline_end = float_pos.i;
+
+ r_block_start = Some(float_pos.b);
+ r_block_end = Some(float_pos.b + float_size.block);
+ debug!("available_rect: collision with inline_end float: new min_inline_end is {}",
+ min_inline_end);
+ }
+ FloatLeft | FloatRight => {}
+ }
+ }
+
+ // Extend the vertical range of the rectangle to the closest floats.
+ // If there are floats on both sides, take the intersection of the
+ // two areas. Also make sure we never return a block-start smaller than the
+ // given upper bound.
+ let (block_start, block_end) = match (r_block_start, r_block_end, l_block_start, l_block_end) {
+ (Some(r_block_start), Some(r_block_end), Some(l_block_start), Some(l_block_end)) =>
+ range_intersect(max(block_start, r_block_start), r_block_end, max(block_start, l_block_start), l_block_end),
+
+ (None, None, Some(l_block_start), Some(l_block_end)) => (max(block_start, l_block_start), l_block_end),
+ (Some(r_block_start), Some(r_block_end), None, None) => (max(block_start, r_block_start), r_block_end),
+ (None, None, None, None) => return None,
+ _ => fail!("Reached unreachable state when computing float area")
+ };
+
+ // FIXME(eatkinson): This assertion is too strong and fails in some cases. It is OK to
+ // return negative inline-sizes since we check against that inline-end away, but we should still
+ // undersrtand why they occur and add a stronger assertion here.
+ // assert!(max_inline-start < min_inline-end);
+
+ assert!(block_start <= block_end, "Float position error");
+
+ Some(LogicalRect::new(
+ self.writing_mode, max_inline_start + self.offset.inline, block_start + self.offset.block,
+ min_inline_end - max_inline_start, block_end - block_start
+ ))
+ }
+
+ /// Adds a new float to the list.
+ pub fn add_float(&mut self, info: &PlacementInfo) {
+ let new_info;
+ {
+ let list = self.list.get_mut();
+ new_info = PlacementInfo {
+ size: info.size,
+ ceiling: max(info.ceiling, list.max_block_start + self.offset.block),
+ max_inline_size: info.max_inline_size,
+ kind: info.kind
+ }
+ }
+
+ debug!("add_float: added float with info {:?}", new_info);
+
+ let new_float = Float {
+ bounds: LogicalRect::from_point_size(
+ self.writing_mode,
+ self.place_between_floats(&new_info).start - self.offset,
+ info.size,
+ ),
+ kind: info.kind
+ };
+
+ let list = self.list.get_mut();
+ list.floats.push(new_float);
+ list.max_block_start = max(list.max_block_start, new_float.bounds.start.b);
+ }
+
+ /// Given the block-start 3 sides of the rectangle, finds the largest block-size that will result in the
+ /// rectangle not colliding with any floats. Returns None if that block-size is infinite.
+ fn max_block_size_for_bounds(&self, inline_start: Au, block_start: Au, inline_size: Au) -> Option<Au> {
+ let list = match self.list.get() {
+ None => return None,
+ Some(list) => list,
+ };
+
+ let block_start = block_start - self.offset.block;
+ let inline_start = inline_start - self.offset.inline;
+ let mut max_block_size = None;
+
+ for float in list.floats.iter() {
+ if float.bounds.start.b + float.bounds.size.block > block_start &&
+ float.bounds.start.i + float.bounds.size.inline > inline_start &&
+ float.bounds.start.i < inline_start + inline_size {
+ let new_y = float.bounds.start.b;
+ max_block_size = Some(min(max_block_size.unwrap_or(new_y), new_y));
+ }
+ }
+
+ max_block_size.map(|h| h + self.offset.block)
+ }
+
+ /// Given placement information, finds the closest place a fragment can be positioned without
+ /// colliding with any floats.
+ pub fn place_between_floats(&self, info: &PlacementInfo) -> LogicalRect<Au> {
+ debug!("place_between_floats: Placing object with {}", info.size);
+
+ // If no floats, use this fast path.
+ if !self.list.is_present() {
+ match info.kind {
+ FloatLeft => {
+ return LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ info.ceiling,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ FloatRight => {
+ return LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ info.ceiling,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ }
+ }
+
+ // Can't go any higher than previous floats or previous elements in the document.
+ let mut float_b = info.ceiling;
+ loop {
+ let maybe_location = self.available_rect(float_b, info.size.block, info.max_inline_size);
+ debug!("place_float: Got available rect: {:?} for y-pos: {}", maybe_location, float_b);
+ match maybe_location {
+ // If there are no floats blocking us, return the current location
+ // TODO(eatkinson): integrate with overflow
+ None => {
+ return match info.kind {
+ FloatLeft => {
+ LogicalRect::new(
+ self.writing_mode,
+ Au(0),
+ float_b,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ FloatRight => {
+ LogicalRect::new(
+ self.writing_mode,
+ info.max_inline_size - info.size.inline,
+ float_b,
+ info.max_inline_size,
+ Au(i32::MAX))
+ }
+ }
+ }
+ Some(rect) => {
+ assert!(rect.start.b + rect.size.block != float_b,
+ "Non-terminating float placement");
+
+ // Place here if there is enough room
+ if rect.size.inline >= info.size.inline {
+ let block_size = self.max_block_size_for_bounds(rect.start.i,
+ rect.start.b,
+ rect.size.inline);
+ let block_size = block_size.unwrap_or(Au(i32::MAX));
+ return match info.kind {
+ FloatLeft => {
+ LogicalRect::new(
+ self.writing_mode,
+ rect.start.i,
+ float_b,
+ rect.size.inline,
+ block_size)
+ }
+ FloatRight => {
+ LogicalRect::new(
+ self.writing_mode,
+ rect.start.i + rect.size.inline - info.size.inline,
+ float_b,
+ rect.size.inline,
+ block_size)
+ }
+ }
+ }
+
+ // Try to place at the next-lowest location.
+ // Need to be careful of fencepost errors.
+ float_b = rect.start.b + rect.size.block;
+ }
+ }
+ }
+ }
+
+ pub fn clearance(&self, clear: ClearType) -> Au {
+ let list = match self.list.get() {
+ None => return Au(0),
+ Some(list) => list,
+ };
+
+ let mut clearance = Au(0);
+ for float in list.floats.iter() {
+ match (clear, float.kind) {
+ (ClearLeft, FloatLeft) |
+ (ClearRight, FloatRight) |
+ (ClearBoth, _) => {
+ let b = self.offset.block + float.bounds.start.b + float.bounds.size.block;
+ clearance = max(clearance, b);
+ }
+ _ => {}
+ }
+ }
+ clearance
+ }
+}
+
diff --git a/components/layout/flow.rs b/components/layout/flow.rs
new file mode 100644
index 00000000000..2759ebbe74b
--- /dev/null
+++ b/components/layout/flow.rs
@@ -0,0 +1,1138 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Servo's experimental layout system builds a tree of `Flow` and `Fragment` objects and solves
+//! layout constraints to obtain positions and display attributes of tree nodes. Positions are
+//! computed in several tree traversals driven by the fundamental data dependencies required by
+/// inline and block layout.
+///
+/// Flows are interior nodes in the layout tree and correspond closely to *flow contexts* in the
+/// CSS specification. Flows are responsible for positioning their child flow contexts and fragments.
+/// Flows have purpose-specific fields, such as auxiliary line structs, out-of-flow child
+/// lists, and so on.
+///
+/// Currently, the important types of flows are:
+///
+/// * `BlockFlow`: A flow that establishes a block context. It has several child flows, each of
+/// which are positioned according to block formatting context rules (CSS block boxes). Block
+/// flows also contain a single box to represent their rendered borders, padding, etc.
+/// The BlockFlow at the root of the tree has special behavior: it stretches to the boundaries of
+/// the viewport.
+///
+/// * `InlineFlow`: A flow that establishes an inline context. It has a flat list of child
+/// fragments/flows that are subject to inline layout and line breaking and structs to represent
+/// line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and
+/// similar methods.
+
+use css::node_style::StyledNode;
+use block::BlockFlow;
+use context::LayoutContext;
+use floats::Floats;
+use flow_list::{FlowList, Link, FlowListIterator, MutFlowListIterator};
+use flow_ref::FlowRef;
+use fragment::{Fragment, TableRowFragment, TableCellFragment};
+use incremental::RestyleDamage;
+use inline::InlineFlow;
+use model::{CollapsibleMargins, IntrinsicISizes, MarginCollapseInfo};
+use parallel::FlowParallelInfo;
+use table_wrapper::TableWrapperFlow;
+use table::TableFlow;
+use table_colgroup::TableColGroupFlow;
+use table_rowgroup::TableRowGroupFlow;
+use table_row::TableRowFlow;
+use table_caption::TableCaptionFlow;
+use table_cell::TableCellFlow;
+use wrapper::ThreadSafeLayoutNode;
+
+use collections::dlist::DList;
+use geom::Point2D;
+use gfx::display_list::DisplayList;
+use gfx::render_task::RenderLayer;
+use serialize::{Encoder, Encodable};
+use servo_msg::compositor_msg::LayerId;
+use servo_util::geometry::Au;
+use servo_util::logical_geometry::WritingMode;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize};
+use std::mem;
+use std::num::Zero;
+use std::fmt;
+use std::iter::Zip;
+use std::raw;
+use std::sync::atomics::{AtomicUint, Relaxed, SeqCst};
+use std::slice::MutItems;
+use style::computed_values::{clear, position, text_align};
+
+/// Virtual methods that make up a float context.
+///
+/// Note that virtual methods have a cost; we should not overuse them in Servo. Consider adding
+/// methods to `ImmutableFlowUtils` or `MutableFlowUtils` before adding more methods here.
+pub trait Flow: fmt::Show + ToString + Share {
+ // RTTI
+ //
+ // TODO(pcwalton): Use Rust's RTTI, once that works.
+
+ /// Returns the class of flow that this is.
+ fn class(&self) -> FlowClass;
+
+ /// If this is a block flow, returns the underlying object, borrowed immutably. Fails
+ /// otherwise.
+ fn as_immutable_block<'a>(&'a self) -> &'a BlockFlow {
+ fail!("called as_immutable_block() on a non-block flow")
+ }
+
+ /// If this is a block flow, returns the underlying object. Fails otherwise.
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ debug!("called as_block() on a flow of type {}", self.class());
+ fail!("called as_block() on a non-block flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object, borrowed immutably. Fails
+ /// otherwise.
+ fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
+ fail!("called as_immutable_inline() on a non-inline flow")
+ }
+
+ /// If this is an inline flow, returns the underlying object. Fails otherwise.
+ fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
+ fail!("called as_inline() on a non-inline flow")
+ }
+
+ /// If this is a table wrapper flow, returns the underlying object. Fails otherwise.
+ fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
+ fail!("called as_table_wrapper() on a non-tablewrapper flow")
+ }
+
+ /// If this is a table flow, returns the underlying object. Fails otherwise.
+ fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
+ fail!("called as_table() on a non-table flow")
+ }
+
+ /// If this is a table colgroup flow, returns the underlying object. Fails otherwise.
+ fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
+ fail!("called as_table_colgroup() on a non-tablecolgroup flow")
+ }
+
+ /// If this is a table rowgroup flow, returns the underlying object. Fails otherwise.
+ fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
+ fail!("called as_table_rowgroup() on a non-tablerowgroup flow")
+ }
+
+ /// If this is a table row flow, returns the underlying object. Fails otherwise.
+ fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
+ fail!("called as_table_row() on a non-tablerow flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object. Fails otherwise.
+ fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
+ fail!("called as_table_caption() on a non-tablecaption flow")
+ }
+
+ /// If this is a table cell flow, returns the underlying object. Fails otherwise.
+ fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
+ fail!("called as_table_cell() on a non-tablecell flow")
+ }
+
+ /// If this is a table row or table rowgroup or table flow, returns column inline-sizes.
+ /// Fails otherwise.
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ fail!("called col_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes.
+ /// Fails otherwise.
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ fail!("called col_min_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ /// If this is a table row flow or table rowgroup flow or table flow, returns column min inline-sizes.
+ /// Fails otherwise.
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ fail!("called col_pref_inline_sizes() on an other flow than table-row/table-rowgroup/table")
+ }
+
+ // Main methods
+
+ /// Pass 1 of reflow: computes minimum and preferred inline-sizes.
+ ///
+ /// Recursively (bottom-up) determine the flow's minimum and preferred inline-sizes. When called on
+ /// this flow, all child flows have had their minimum and preferred inline-sizes set. This function
+ /// must decide minimum/preferred inline-sizes based on its children's inline-sizes and the dimensions of
+ /// any boxes it is responsible for flowing.
+ fn bubble_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ fail!("bubble_inline_sizes not yet implemented")
+ }
+
+ /// Pass 2 of reflow: computes inline-size.
+ fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ fail!("assign_inline_sizes not yet implemented")
+ }
+
+ /// Pass 3a of reflow: computes block-size.
+ fn assign_block_size<'a>(&mut self, _ctx: &'a LayoutContext<'a>) {
+ fail!("assign_block_size not yet implemented")
+ }
+
+ /// Assigns block-sizes in-order; or, if this is a float, places the float. The default
+ /// implementation simply assigns block-sizes if this flow is impacted by floats. Returns true if
+ /// this child was impacted by floats or false otherwise.
+ fn assign_block_size_for_inorder_child_if_necessary<'a>(&mut self, layout_context: &'a LayoutContext<'a>)
+ -> bool {
+ let impacted = base(&*self).flags.impacted_by_floats();
+ if impacted {
+ self.assign_block_size(layout_context);
+ }
+ impacted
+ }
+
+ /// Phase 4 of reflow: computes absolute positions.
+ fn compute_absolute_position(&mut self) {
+ // The default implementation is a no-op.
+ }
+
+ /// Returns the direction that this flow clears floats in, if any.
+ fn float_clearance(&self) -> clear::T {
+ clear::none
+ }
+
+ /// Returns true if this float is a block formatting context and false otherwise. The default
+ /// implementation returns false.
+ fn is_block_formatting_context(&self, _only_impactable_by_floats: bool) -> bool {
+ false
+ }
+
+ fn compute_collapsible_block_start_margin(&mut self,
+ _layout_context: &mut LayoutContext,
+ _margin_collapse_info: &mut MarginCollapseInfo) {
+ // The default implementation is a no-op.
+ }
+
+ /// Marks this flow as the root flow. The default implementation is a no-op.
+ fn mark_as_root(&mut self) {}
+
+ // Note that the following functions are mostly called using static method
+ // dispatch, so it's ok to have them in this trait. Plus, they have
+ // different behaviour for different types of Flow, so they can't go into
+ // the Immutable / Mutable Flow Utils traits without additional casts.
+
+ /// Return true if store overflow is delayed for this flow.
+ ///
+ /// Currently happens only for absolutely positioned flows.
+ fn is_store_overflow_delayed(&mut self) -> bool {
+ false
+ }
+
+ fn is_root(&self) -> bool {
+ false
+ }
+
+ fn is_float(&self) -> bool {
+ false
+ }
+
+ /// The 'position' property of this flow.
+ fn positioning(&self) -> position::T {
+ position::static_
+ }
+
+ /// Return true if this flow has position 'fixed'.
+ fn is_fixed(&self) -> bool {
+ self.positioning() == position::fixed
+ }
+
+ fn is_positioned(&self) -> bool {
+ self.is_relatively_positioned() || self.is_absolutely_positioned()
+ }
+
+ fn is_relatively_positioned(&self) -> bool {
+ self.positioning() == position::relative
+ }
+
+ fn is_absolutely_positioned(&self) -> bool {
+ self.positioning() == position::absolute || self.is_fixed()
+ }
+
+ /// Return true if this is the root of an Absolute flow tree.
+ fn is_root_of_absolute_flow_tree(&self) -> bool {
+ false
+ }
+
+ /// Returns true if this is an absolute containing block.
+ fn is_absolute_containing_block(&self) -> bool {
+ false
+ }
+
+ /// Return the dimensions of the containing block generated by this flow for absolutely-
+ /// positioned descendants. For block flows, this is the padding box.
+ fn generated_containing_block_rect(&self) -> LogicalRect<Au> {
+ fail!("generated_containing_block_position not yet implemented for this flow")
+ }
+
+ /// Returns a layer ID for the given fragment.
+ fn layer_id(&self, fragment_id: uint) -> LayerId {
+ unsafe {
+ let pointer: uint = mem::transmute(self);
+ LayerId(pointer, fragment_id)
+ }
+ }
+}
+
+impl<'a, E, S: Encoder<E>> Encodable<S, E> for &'a Flow {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("flow", 0, |e| {
+ try!(e.emit_struct_field("class", 0, |e| self.class().encode(e)))
+ e.emit_struct_field("data", 1, |e| {
+ match self.class() {
+ BlockFlowClass => self.as_immutable_block().encode(e),
+ InlineFlowClass => self.as_immutable_inline().encode(e),
+ _ => { Ok(()) } // TODO: Support tables
+ }
+ })
+ })
+ }
+}
+
+// Base access
+
+#[inline(always)]
+pub fn base<'a>(this: &'a Flow) -> &'a BaseFlow {
+ unsafe {
+ let obj = mem::transmute::<&'a Flow, raw::TraitObject>(this);
+ mem::transmute::<*mut (), &'a BaseFlow>(obj.data)
+ }
+}
+
+/// Iterates over the children of this immutable flow.
+pub fn imm_child_iter<'a>(flow: &'a Flow) -> FlowListIterator<'a> {
+ base(flow).children.iter()
+}
+
+#[inline(always)]
+pub fn mut_base<'a>(this: &'a mut Flow) -> &'a mut BaseFlow {
+ unsafe {
+ let obj = mem::transmute::<&'a mut Flow, raw::TraitObject>(this);
+ mem::transmute::<*mut (), &'a mut BaseFlow>(obj.data)
+ }
+}
+
+/// Iterates over the children of this flow.
+pub fn child_iter<'a>(flow: &'a mut Flow) -> MutFlowListIterator<'a> {
+ mut_base(flow).children.mut_iter()
+}
+
+pub trait ImmutableFlowUtils {
+ // Convenience functions
+
+ /// Returns true if this flow is a block or a float flow.
+ fn is_block_like(self) -> bool;
+
+ /// Returns true if this flow is a table flow.
+ fn is_table(self) -> bool;
+
+ /// Returns true if this flow is a table caption flow.
+ fn is_table_caption(self) -> bool;
+
+ /// Returns true if this flow is a proper table child.
+ fn is_proper_table_child(self) -> bool;
+
+ /// Returns true if this flow is a table row flow.
+ fn is_table_row(self) -> bool;
+
+ /// Returns true if this flow is a table cell flow.
+ fn is_table_cell(self) -> bool;
+
+ /// Returns true if this flow is a table colgroup flow.
+ fn is_table_colgroup(self) -> bool;
+
+ /// Returns true if this flow is a table rowgroup flow.
+ fn is_table_rowgroup(self) -> bool;
+
+ /// Returns true if this flow is one of table-related flows.
+ fn is_table_kind(self) -> bool;
+
+ /// Returns true if anonymous flow is needed between this flow and child flow.
+ fn need_anonymous_flow(self, child: &Flow) -> bool;
+
+ /// Generates missing child flow of this flow.
+ fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef;
+
+ /// Returns true if this flow has no children.
+ fn is_leaf(self) -> bool;
+
+ /// Returns the number of children that this flow possesses.
+ fn child_count(self) -> uint;
+
+ /// Return true if this flow is a Block Container.
+ fn is_block_container(self) -> bool;
+
+ /// Returns true if this flow is a block flow.
+ fn is_block_flow(self) -> bool;
+
+ /// Returns true if this flow is an inline flow.
+ fn is_inline_flow(self) -> bool;
+
+ /// Dumps the flow tree for debugging.
+ fn dump(self);
+
+ /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level.
+ fn dump_with_level(self, level: uint);
+}
+
+pub trait MutableFlowUtils {
+ // Traversals
+
+ /// Traverses the tree in preorder.
+ fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool;
+
+ /// Traverses the tree in postorder.
+ fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool;
+
+ // Mutators
+
+ /// Computes the overflow region for this flow.
+ fn store_overflow(self, _: &LayoutContext);
+
+ /// Builds the display lists for this flow.
+ fn build_display_list(self, layout_context: &LayoutContext);
+}
+
+pub trait MutableOwnedFlowUtils {
+ /// Set absolute descendants for this flow.
+ ///
+ /// Set this flow as the Containing Block for all the absolute descendants.
+ fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants);
+}
+
+#[deriving(Encodable, PartialEq, Show)]
+pub enum FlowClass {
+ BlockFlowClass,
+ InlineFlowClass,
+ TableWrapperFlowClass,
+ TableFlowClass,
+ TableColGroupFlowClass,
+ TableRowGroupFlowClass,
+ TableRowFlowClass,
+ TableCaptionFlowClass,
+ TableCellFlowClass,
+}
+
+/// A top-down traversal.
+pub trait PreorderFlowTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&mut self, flow: &mut Flow) -> bool;
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune(&mut self, _flow: &mut Flow) -> bool {
+ false
+ }
+}
+
+/// A bottom-up traversal, with a optional in-order pass.
+pub trait PostorderFlowTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process(&mut self, flow: &mut Flow) -> bool;
+
+ /// Returns false if this node must be processed in-order. If this returns false, we skip the
+ /// operation for this node, but continue processing the ancestors. This is called *after*
+ /// child nodes are visited.
+ fn should_process(&mut self, _flow: &mut Flow) -> bool {
+ true
+ }
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune(&mut self, _flow: &mut Flow) -> bool {
+ false
+ }
+}
+
+/// Flags used in flows, tightly packed to save space.
+#[deriving(Clone, Encodable)]
+pub struct FlowFlags(pub u8);
+
+/// The bitmask of flags that represent the `has_left_floated_descendants` and
+/// `has_right_floated_descendants` fields.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static HAS_FLOATED_DESCENDANTS_BITMASK: u8 = 0b0000_0011;
+
+// Whether this flow has descendants that float left in the same block formatting context.
+bitfield!(FlowFlags, has_left_floated_descendants, set_has_left_floated_descendants, 0b0000_0001)
+
+// Whether this flow has descendants that float right in the same block formatting context.
+bitfield!(FlowFlags, has_right_floated_descendants, set_has_right_floated_descendants, 0b0000_0010)
+
+// Whether this flow is impacted by floats to the left in the same block formatting context (i.e.
+// its block-size depends on some prior flows with `float: left`).
+bitfield!(FlowFlags, impacted_by_left_floats, set_impacted_by_left_floats, 0b0000_0100)
+
+// Whether this flow is impacted by floats to the right in the same block formatting context (i.e.
+// its block-size depends on some prior flows with `float: right`).
+bitfield!(FlowFlags, impacted_by_right_floats, set_impacted_by_right_floats, 0b0000_1000)
+
+/// The bitmask of flags that represent the text alignment field.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static TEXT_ALIGN_BITMASK: u8 = 0b0011_0000;
+
+/// The number of bits we must shift off to handle the text alignment field.
+///
+/// NB: If you update this field, you must update the bitfields below.
+static TEXT_ALIGN_SHIFT: u8 = 4;
+
+// Whether this flow contains a flow that has its own layer within the same absolute containing
+// block.
+bitfield!(FlowFlags,
+ layers_needed_for_descendants,
+ set_layers_needed_for_descendants,
+ 0b0100_0000)
+
+// Whether this flow must have its own layer. Even if this flag is not set, it might get its own
+// layer if it's deemed to be likely to overlap flows with their own layer.
+bitfield!(FlowFlags, needs_layer, set_needs_layer, 0b1000_0000)
+
+impl FlowFlags {
+ /// Creates a new set of flow flags.
+ pub fn new() -> FlowFlags {
+ FlowFlags(0)
+ }
+
+ /// Propagates text alignment flags from an appropriate parent flow per CSS 2.1.
+ ///
+ /// FIXME(#2265, pcwalton): It would be cleaner and faster to make this a derived CSS property
+ /// `-servo-text-align-in-effect`.
+ pub fn propagate_text_alignment_from_parent(&mut self, parent_flags: FlowFlags) {
+ self.set_text_align_override(parent_flags);
+ }
+
+ #[inline]
+ pub fn text_align(self) -> text_align::T {
+ let FlowFlags(ff) = self;
+ FromPrimitive::from_u8((ff & TEXT_ALIGN_BITMASK) >> TEXT_ALIGN_SHIFT as uint).unwrap()
+ }
+
+ #[inline]
+ pub fn set_text_align(&mut self, value: text_align::T) {
+ let FlowFlags(ff) = *self;
+ *self = FlowFlags((ff & !TEXT_ALIGN_BITMASK) | ((value as u8) << TEXT_ALIGN_SHIFT as uint))
+ }
+
+ #[inline]
+ pub fn set_text_align_override(&mut self, parent: FlowFlags) {
+ let FlowFlags(ff) = *self;
+ let FlowFlags(pff) = parent;
+ *self = FlowFlags(ff | (pff & TEXT_ALIGN_BITMASK))
+ }
+
+ #[inline]
+ pub fn union_floated_descendants_flags(&mut self, other: FlowFlags) {
+ let FlowFlags(my_flags) = *self;
+ let FlowFlags(other_flags) = other;
+ *self = FlowFlags(my_flags | (other_flags & HAS_FLOATED_DESCENDANTS_BITMASK))
+ }
+
+ #[inline]
+ pub fn impacted_by_floats(&self) -> bool {
+ self.impacted_by_left_floats() || self.impacted_by_right_floats()
+ }
+}
+
+/// The Descendants of a flow.
+///
+/// Also, details about their position wrt this flow.
+pub struct Descendants {
+ /// Links to every descendant. This must be private because it is unsafe to leak `FlowRef`s to
+ /// layout.
+ descendant_links: Vec<FlowRef>,
+
+ /// Static y offsets of all descendants from the start of this flow box.
+ pub static_b_offsets: Vec<Au>,
+}
+
+impl Descendants {
+ pub fn new() -> Descendants {
+ Descendants {
+ descendant_links: Vec::new(),
+ static_b_offsets: Vec::new(),
+ }
+ }
+
+ pub fn len(&self) -> uint {
+ self.descendant_links.len()
+ }
+
+ pub fn push(&mut self, given_descendant: FlowRef) {
+ self.descendant_links.push(given_descendant);
+ }
+
+ /// Push the given descendants on to the existing descendants.
+ ///
+ /// Ignore any static y offsets, because they are None before layout.
+ pub fn push_descendants(&mut self, given_descendants: Descendants) {
+ for elem in given_descendants.descendant_links.move_iter() {
+ self.descendant_links.push(elem);
+ }
+ }
+
+ /// Return an iterator over the descendant flows.
+ pub fn iter<'a>(&'a mut self) -> DescendantIter<'a> {
+ DescendantIter {
+ iter: self.descendant_links.mut_slice_from(0).mut_iter(),
+ }
+ }
+
+ /// Return an iterator over (descendant, static y offset).
+ pub fn iter_with_offset<'a>(&'a mut self) -> DescendantOffsetIter<'a> {
+ let descendant_iter = DescendantIter {
+ iter: self.descendant_links.mut_slice_from(0).mut_iter(),
+ };
+ descendant_iter.zip(self.static_b_offsets.mut_slice_from(0).mut_iter())
+ }
+}
+
+pub type AbsDescendants = Descendants;
+
+pub struct DescendantIter<'a> {
+ iter: MutItems<'a, FlowRef>,
+}
+
+impl<'a> Iterator<&'a mut Flow> for DescendantIter<'a> {
+ fn next(&mut self) -> Option<&'a mut Flow> {
+ match self.iter.next() {
+ None => None,
+ Some(ref mut flow) => {
+ unsafe {
+ let result: &'a mut Flow = mem::transmute(flow.get_mut());
+ Some(result)
+ }
+ }
+ }
+ }
+}
+
+pub type DescendantOffsetIter<'a> = Zip<DescendantIter<'a>, MutItems<'a, Au>>;
+
+/// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+/// confused with absolutely-positioned flows).
+#[deriving(Encodable)]
+pub struct AbsolutePositionInfo {
+ /// The size of the containing block for relatively-positioned descendants.
+ pub relative_containing_block_size: LogicalSize<Au>,
+ /// The position of the absolute containing block.
+ pub absolute_containing_block_position: Point2D<Au>,
+ /// Whether the absolute containing block forces positioned descendants to be layerized.
+ ///
+ /// FIXME(pcwalton): Move into `FlowFlags`.
+ pub layers_needed_for_positioned_flows: bool,
+}
+
+impl AbsolutePositionInfo {
+ pub fn new(writing_mode: WritingMode) -> AbsolutePositionInfo {
+ // FIXME(pcwalton): The initial relative containing block-size should be equal to the size
+ // of the root layer.
+ AbsolutePositionInfo {
+ relative_containing_block_size: LogicalSize::zero(writing_mode),
+ absolute_containing_block_position: Zero::zero(),
+ layers_needed_for_positioned_flows: false,
+ }
+ }
+}
+
+/// Data common to all flows.
+pub struct BaseFlow {
+ /// NB: Must be the first element.
+ ///
+ /// The necessity of this will disappear once we have dynamically-sized types.
+ ref_count: AtomicUint,
+
+ pub restyle_damage: RestyleDamage,
+
+ /// The children of this flow.
+ pub children: FlowList,
+ pub next_sibling: Link,
+ pub prev_sibling: Link,
+
+ /* layout computations */
+ // TODO: min/pref and position are used during disjoint phases of
+ // layout; maybe combine into a single enum to save space.
+ pub intrinsic_inline_sizes: IntrinsicISizes,
+
+ /// The upper left corner of the box representing this flow, relative to the box representing
+ /// its parent flow.
+ ///
+ /// For absolute flows, this represents the position with respect to its *containing block*.
+ ///
+ /// This does not include margins in the block flow direction, because those can collapse. So
+ /// for the block direction (usually vertical), this represents the *border box*. For the
+ /// inline direction (usually horizontal), this represents the *margin box*.
+ pub position: LogicalRect<Au>,
+
+ /// The amount of overflow of this flow, relative to the containing block. Must include all the
+ /// pixels of all the display list items for correct invalidation.
+ pub overflow: LogicalRect<Au>,
+
+ /// Data used during parallel traversals.
+ ///
+ /// TODO(pcwalton): Group with other transient data to save space.
+ pub parallel: FlowParallelInfo,
+
+ /// The floats next to this flow.
+ pub floats: Floats,
+
+ /// The collapsible margins for this flow, if any.
+ pub collapsible_margins: CollapsibleMargins,
+
+ /// The position of this flow in page coordinates, computed during display list construction.
+ pub abs_position: Point2D<Au>,
+
+ /// Details about descendants with position 'absolute' or 'fixed' for which we are the
+ /// containing block. This is in tree order. This includes any direct children.
+ pub abs_descendants: AbsDescendants,
+
+ /// Offset wrt the nearest positioned ancestor - aka the Containing Block
+ /// for any absolutely positioned elements.
+ pub absolute_static_i_offset: Au,
+
+ /// Offset wrt the Initial Containing Block.
+ pub fixed_static_i_offset: Au,
+
+ /// Reference to the Containing Block, if this flow is absolutely positioned.
+ pub absolute_cb: ContainingBlockLink,
+
+ /// Information needed to compute absolute (i.e. viewport-relative) flow positions (not to be
+ /// confused with absolutely-positioned flows).
+ ///
+ /// FIXME(pcwalton): Merge with `absolute_static_i_offset` and `fixed_static_i_offset` above?
+ pub absolute_position_info: AbsolutePositionInfo,
+
+ /// The unflattened display items for this flow.
+ pub display_list: DisplayList,
+
+ /// Any layers that we're bubbling up, in a linked list.
+ pub layers: DList<RenderLayer>,
+
+ /// Various flags for flows, tightly packed to save space.
+ pub flags: FlowFlags,
+
+ pub writing_mode: WritingMode,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for BaseFlow {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("base", 0, |e| {
+ try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
+ try!(e.emit_struct_field("abs_position", 1, |e| self.abs_position.encode(e)))
+ try!(e.emit_struct_field("intrinsic_inline_sizes", 2, |e| self.intrinsic_inline_sizes.encode(e)))
+ try!(e.emit_struct_field("position", 3, |e| self.position.encode(e)))
+ e.emit_struct_field("children", 4, |e| {
+ e.emit_seq(self.children.len(), |e| {
+ for (i, c) in self.children.iter().enumerate() {
+ try!(e.emit_seq_elt(i, |e| c.encode(e)))
+ }
+ Ok(())
+ })
+
+ })
+ })
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for BaseFlow {
+ fn drop(&mut self) {
+ if self.ref_count.load(SeqCst) != 0 {
+ fail!("Flow destroyed before its ref count hit zero—this is unsafe!")
+ }
+ }
+}
+
+impl BaseFlow {
+ #[inline]
+ pub fn new(node: ThreadSafeLayoutNode) -> BaseFlow {
+ let writing_mode = node.style().writing_mode;
+ BaseFlow {
+ ref_count: AtomicUint::new(1),
+
+ restyle_damage: node.restyle_damage(),
+
+ children: FlowList::new(),
+ next_sibling: None,
+ prev_sibling: None,
+
+ intrinsic_inline_sizes: IntrinsicISizes::new(),
+ position: LogicalRect::zero(writing_mode),
+ overflow: LogicalRect::zero(writing_mode),
+
+ parallel: FlowParallelInfo::new(),
+
+ floats: Floats::new(writing_mode),
+ collapsible_margins: CollapsibleMargins::new(),
+ abs_position: Zero::zero(),
+ abs_descendants: Descendants::new(),
+ absolute_static_i_offset: Au::new(0),
+ fixed_static_i_offset: Au::new(0),
+ absolute_cb: ContainingBlockLink::new(),
+ display_list: DisplayList::new(),
+ layers: DList::new(),
+ absolute_position_info: AbsolutePositionInfo::new(writing_mode),
+
+ flags: FlowFlags::new(),
+ writing_mode: writing_mode,
+ }
+ }
+
+ pub fn child_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
+ self.children.mut_iter()
+ }
+
+ pub unsafe fn ref_count<'a>(&'a self) -> &'a AtomicUint {
+ &self.ref_count
+ }
+
+ pub fn debug_id(&self) -> String {
+ format!("{:p}", self as *const _)
+ }
+}
+
+impl<'a> ImmutableFlowUtils for &'a Flow {
+ /// Returns true if this flow is a block or a float flow.
+ fn is_block_like(self) -> bool {
+ match self.class() {
+ BlockFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a proper table child.
+ /// 'Proper table child' is defined as table-row flow, table-rowgroup flow,
+ /// table-column-group flow, or table-caption flow.
+ fn is_proper_table_child(self) -> bool {
+ match self.class() {
+ TableRowFlowClass | TableRowGroupFlowClass |
+ TableColGroupFlowClass | TableCaptionFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table row flow.
+ fn is_table_row(self) -> bool {
+ match self.class() {
+ TableRowFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table cell flow.
+ fn is_table_cell(self) -> bool {
+ match self.class() {
+ TableCellFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table colgroup flow.
+ fn is_table_colgroup(self) -> bool {
+ match self.class() {
+ TableColGroupFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table flow.
+ fn is_table(self) -> bool {
+ match self.class() {
+ TableFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table caption flow.
+ fn is_table_caption(self) -> bool {
+ match self.class() {
+ TableCaptionFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a table rowgroup flow.
+ fn is_table_rowgroup(self) -> bool {
+ match self.class() {
+ TableRowGroupFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is one of table-related flows.
+ fn is_table_kind(self) -> bool {
+ match self.class() {
+ TableWrapperFlowClass | TableFlowClass |
+ TableColGroupFlowClass | TableRowGroupFlowClass |
+ TableRowFlowClass | TableCaptionFlowClass | TableCellFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if anonymous flow is needed between this flow and child flow.
+ /// Spec: http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
+ fn need_anonymous_flow(self, child: &Flow) -> bool {
+ match self.class() {
+ TableFlowClass => !child.is_proper_table_child(),
+ TableRowGroupFlowClass => !child.is_table_row(),
+ TableRowFlowClass => !child.is_table_cell(),
+ _ => false
+ }
+ }
+
+ /// Generates missing child flow of this flow.
+ fn generate_missing_child_flow(self, node: &ThreadSafeLayoutNode) -> FlowRef {
+ let flow = match self.class() {
+ TableFlowClass | TableRowGroupFlowClass => {
+ let fragment = Fragment::new_anonymous_table_fragment(node, TableRowFragment);
+ box TableRowFlow::from_node_and_fragment(node, fragment) as Box<Flow>
+ },
+ TableRowFlowClass => {
+ let fragment = Fragment::new_anonymous_table_fragment(node, TableCellFragment);
+ box TableCellFlow::from_node_and_fragment(node, fragment) as Box<Flow>
+ },
+ _ => {
+ fail!("no need to generate a missing child")
+ }
+ };
+ FlowRef::new(flow)
+ }
+
+ /// Returns true if this flow has no children.
+ fn is_leaf(self) -> bool {
+ base(self).children.len() == 0
+ }
+
+ /// Returns the number of children that this flow possesses.
+ fn child_count(self) -> uint {
+ base(self).children.len()
+ }
+
+ /// Return true if this flow is a Block Container.
+ ///
+ /// Except for table fragments and replaced elements, block-level fragments (`BlockFlow`) are
+ /// also block container fragments.
+ /// Non-replaced inline blocks and non-replaced table cells are also block
+ /// containers.
+ fn is_block_container(self) -> bool {
+ match self.class() {
+ // TODO: Change this when inline-blocks are supported.
+ BlockFlowClass | TableCaptionFlowClass | TableCellFlowClass => {
+ // FIXME: Actually check the type of the node
+ self.child_count() != 0
+ }
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is a block flow.
+ fn is_block_flow(self) -> bool {
+ match self.class() {
+ BlockFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Returns true if this flow is an inline flow.
+ fn is_inline_flow(self) -> bool {
+ match self.class() {
+ InlineFlowClass => true,
+ _ => false,
+ }
+ }
+
+ /// Dumps the flow tree for debugging.
+ fn dump(self) {
+ self.dump_with_level(0)
+ }
+
+ /// Dumps the flow tree for debugging, with a prefix to indicate that we're at the given level.
+ fn dump_with_level(self, level: uint) {
+ let mut indent = String::new();
+ for _ in range(0, level) {
+ indent.push_str("| ")
+ }
+ debug!("{}+ {}", indent, self.to_string());
+ for kid in imm_child_iter(self) {
+ kid.dump_with_level(level + 1)
+ }
+ }
+}
+
+impl<'a> MutableFlowUtils for &'a mut Flow {
+ /// Traverses the tree in preorder.
+ fn traverse_preorder<T:PreorderFlowTraversal>(self, traversal: &mut T) -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ if !traversal.process(self) {
+ return false
+ }
+
+ for kid in child_iter(self) {
+ if !kid.traverse_preorder(traversal) {
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Traverses the tree in postorder.
+ fn traverse_postorder<T:PostorderFlowTraversal>(self, traversal: &mut T) -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ for kid in child_iter(self) {
+ if !kid.traverse_postorder(traversal) {
+ return false
+ }
+ }
+
+ if !traversal.should_process(self) {
+ return true
+ }
+
+ traversal.process(self)
+ }
+
+ /// Calculate and set overflow for current flow.
+ ///
+ /// CSS Section 11.1
+ /// This is the union of rectangles of the flows for which we define the
+ /// Containing Block.
+ ///
+ /// Assumption: This is called in a bottom-up traversal, so kids' overflows have
+ /// already been set.
+ /// Assumption: Absolute descendants have had their overflow calculated.
+ fn store_overflow(self, _: &LayoutContext) {
+ let my_position = mut_base(self).position;
+ let mut overflow = my_position;
+
+ if self.is_block_container() {
+ for kid in child_iter(self) {
+ if kid.is_store_overflow_delayed() {
+ // Absolute flows will be handled by their CB. If we are
+ // their CB, they will show up in `abs_descendants`.
+ continue;
+ }
+ let mut kid_overflow = base(kid).overflow;
+ kid_overflow = kid_overflow.translate(&my_position.start);
+ overflow = overflow.union(&kid_overflow)
+ }
+
+ // FIXME(#2004, pcwalton): This is wrong for `position: fixed`.
+ for descendant_link in mut_base(self).abs_descendants.iter() {
+ let mut kid_overflow = base(descendant_link).overflow;
+ kid_overflow = kid_overflow.translate(&my_position.start);
+ overflow = overflow.union(&kid_overflow)
+ }
+ }
+ mut_base(self).overflow = overflow;
+ }
+
+ /// Push display items for current flow and its descendants onto the appropriate display lists
+ /// of the given stacking context.
+ ///
+ /// Arguments:
+ ///
+ /// * `builder`: The display list builder, which contains information used during the entire
+ /// display list building pass.
+ ///
+ /// * `info`: Per-flow display list building information.
+ fn build_display_list(self, layout_context: &LayoutContext) {
+ debug!("Flow: building display list");
+ match self.class() {
+ BlockFlowClass => self.as_block().build_display_list_block(layout_context),
+ InlineFlowClass => self.as_inline().build_display_list_inline(layout_context),
+ TableWrapperFlowClass => {
+ self.as_table_wrapper().build_display_list_table_wrapper(layout_context)
+ }
+ TableFlowClass => self.as_table().build_display_list_table(layout_context),
+ TableRowGroupFlowClass => {
+ self.as_table_rowgroup().build_display_list_table_rowgroup(layout_context)
+ }
+ TableRowFlowClass => self.as_table_row().build_display_list_table_row(layout_context),
+ TableCaptionFlowClass => {
+ self.as_table_caption().build_display_list_table_caption(layout_context)
+ }
+ TableCellFlowClass => {
+ self.as_table_cell().build_display_list_table_cell(layout_context)
+ }
+ TableColGroupFlowClass => {
+ // Nothing to do here, as column groups don't render.
+ }
+ }
+ }
+}
+
+impl MutableOwnedFlowUtils for FlowRef {
+ /// Set absolute descendants for this flow.
+ ///
+ /// Set yourself as the Containing Block for all the absolute descendants.
+ ///
+ /// This is called during flow construction, so nothing else can be accessing the descendant
+ /// flows. This is enforced by the fact that we have a mutable `FlowRef`, which only flow
+ /// construction is allowed to possess.
+ fn set_abs_descendants(&mut self, abs_descendants: AbsDescendants) {
+ let this = self.clone();
+
+ let block = self.get_mut().as_block();
+ block.base.abs_descendants = abs_descendants;
+ block.base
+ .parallel
+ .children_and_absolute_descendant_count
+ .fetch_add(block.base.abs_descendants.len() as int, Relaxed);
+
+ for descendant_link in block.base.abs_descendants.iter() {
+ let base = mut_base(descendant_link);
+ base.absolute_cb.set(this.clone());
+ }
+ }
+}
+
+/// A link to a flow's containing block.
+///
+/// This cannot safely be a `Flow` pointer because this is a pointer *up* the tree, not *down* the
+/// tree. A pointer up the tree is unsafe during layout because it can be used to access a node
+/// with an immutable reference while that same node is being laid out, causing possible iterator
+/// invalidation and use-after-free.
+///
+/// FIXME(pcwalton): I think this would be better with a borrow flag instead of `unsafe`.
+pub struct ContainingBlockLink {
+ /// The pointer up to the containing block.
+ link: Option<FlowRef>,
+}
+
+impl ContainingBlockLink {
+ fn new() -> ContainingBlockLink {
+ ContainingBlockLink {
+ link: None,
+ }
+ }
+
+ fn set(&mut self, link: FlowRef) {
+ self.link = Some(link)
+ }
+
+ pub unsafe fn get<'a>(&'a mut self) -> &'a mut Option<FlowRef> {
+ &mut self.link
+ }
+
+ #[inline]
+ pub fn generated_containing_block_rect(&mut self) -> LogicalRect<Au> {
+ match self.link {
+ None => fail!("haven't done it"),
+ Some(ref mut link) => link.get_mut().generated_containing_block_rect(),
+ }
+ }
+}
+
diff --git a/components/layout/flow_list.rs b/components/layout/flow_list.rs
new file mode 100644
index 00000000000..4277326a624
--- /dev/null
+++ b/components/layout/flow_list.rs
@@ -0,0 +1,296 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A variant of `DList` specialized to store `Flow`s without an extra
+//! indirection.
+
+use flow::{Flow, base, mut_base};
+use flow_ref::FlowRef;
+
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+use std::ptr;
+use std::raw;
+
+pub type Link = Option<FlowRef>;
+
+
+#[allow(raw_pointer_deriving)]
+pub struct Rawlink<'a> {
+ object: raw::TraitObject,
+ marker: ContravariantLifetime<'a>,
+}
+
+/// Doubly-linked list of Flows.
+///
+/// The forward links are strong references.
+/// The backward links are weak references.
+pub struct FlowList {
+ length: uint,
+ list_head: Link,
+ list_tail: Link,
+}
+
+/// Double-ended FlowList iterator
+pub struct FlowListIterator<'a> {
+ head: &'a Link,
+ nelem: uint,
+}
+
+/// Double-ended mutable FlowList iterator
+pub struct MutFlowListIterator<'a> {
+ head: Rawlink<'a>,
+ nelem: uint,
+}
+
+impl<'a> Rawlink<'a> {
+ /// Like Option::None for Rawlink
+ pub fn none() -> Rawlink<'static> {
+ Rawlink {
+ object: raw::TraitObject {
+ vtable: ptr::mut_null(),
+ data: ptr::mut_null(),
+ },
+ marker: ContravariantLifetime,
+ }
+ }
+
+ /// Like Option::Some for Rawlink
+ pub fn some(n: &Flow) -> Rawlink {
+ unsafe {
+ Rawlink {
+ object: mem::transmute::<&Flow, raw::TraitObject>(n),
+ marker: ContravariantLifetime,
+ }
+ }
+ }
+
+ pub unsafe fn resolve_mut(&self) -> Option<&'a mut Flow> {
+ if self.object.data.is_null() {
+ None
+ } else {
+ Some(mem::transmute_copy::<raw::TraitObject, &mut Flow>(&self.object))
+ }
+ }
+}
+
+/// Set the .prev field on `next`, then return `Some(next)`
+unsafe fn link_with_prev(mut next: FlowRef, prev: Option<FlowRef>) -> Link {
+ mut_base(next.get_mut()).prev_sibling = prev;
+ Some(next)
+}
+
+impl Collection for FlowList {
+ /// O(1)
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.list_head.is_none()
+ }
+ /// O(1)
+ #[inline]
+ fn len(&self) -> uint {
+ self.length
+ }
+}
+
+// This doesn't quite fit the Deque trait because of the need to switch between
+// &Flow and ~Flow.
+impl FlowList {
+ /// Provide a reference to the front element, or None if the list is empty
+ #[inline]
+ pub fn front<'a>(&'a self) -> Option<&'a Flow> {
+ self.list_head.as_ref().map(|head| head.get())
+ }
+
+ /// Provide a mutable reference to the front element, or None if the list is empty
+ #[inline]
+ pub unsafe fn front_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
+ self.list_head.as_mut().map(|head| head.get_mut())
+ }
+
+ /// Provide a reference to the back element, or None if the list is empty
+ #[inline]
+ pub fn back<'a>(&'a self) -> Option<&'a Flow> {
+ match self.list_tail {
+ None => None,
+ Some(ref list_tail) => Some(list_tail.get())
+ }
+ }
+
+ /// Provide a mutable reference to the back element, or None if the list is empty
+ #[inline]
+ pub unsafe fn back_mut<'a>(&'a mut self) -> Option<&'a mut Flow> {
+ // Can't use map() due to error:
+ // lifetime of `tail` is too short to guarantee its contents can be safely reborrowed
+ match self.list_tail {
+ None => None,
+ Some(ref mut tail) => {
+ let x: &mut Flow = tail.get_mut();
+ Some(mem::transmute_copy(&x))
+ }
+ }
+ }
+
+ /// Add an element first in the list
+ ///
+ /// O(1)
+ pub fn push_front(&mut self, mut new_head: FlowRef) {
+ unsafe {
+ match self.list_head {
+ None => {
+ self.list_tail = Some(new_head.clone());
+ self.list_head = link_with_prev(new_head, None);
+ }
+ Some(ref mut head) => {
+ mut_base(new_head.get_mut()).prev_sibling = None;
+ mut_base(head.get_mut()).prev_sibling = Some(new_head.clone());
+ mem::swap(head, &mut new_head);
+ mut_base(head.get_mut()).next_sibling = Some(new_head);
+ }
+ }
+ self.length += 1;
+ }
+ }
+
+ /// Remove the first element and return it, or None if the list is empty
+ ///
+ /// O(1)
+ pub fn pop_front(&mut self) -> Option<FlowRef> {
+ self.list_head.take().map(|mut front_node| {
+ self.length -= 1;
+ unsafe {
+ match mut_base(front_node.get_mut()).next_sibling.take() {
+ Some(node) => self.list_head = link_with_prev(node, None),
+ None => self.list_tail = None,
+ }
+ }
+ front_node
+ })
+ }
+
+ /// Add an element last in the list
+ ///
+ /// O(1)
+ pub fn push_back(&mut self, new_tail: FlowRef) {
+ if self.list_tail.is_none() {
+ return self.push_front(new_tail);
+ }
+
+ let old_tail = self.list_tail.clone();
+ self.list_tail = Some(new_tail.clone());
+ let mut tail = (*old_tail.as_ref().unwrap()).clone();
+ let tail_clone = Some(tail.clone());
+ unsafe {
+ mut_base(tail.get_mut()).next_sibling = link_with_prev(new_tail, tail_clone);
+ }
+ self.length += 1;
+ }
+
+ /// Create an empty list
+ #[inline]
+ pub fn new() -> FlowList {
+ FlowList {
+ list_head: None,
+ list_tail: None,
+ length: 0,
+ }
+ }
+
+ /// Provide a forward iterator
+ #[inline]
+ pub fn iter<'a>(&'a self) -> FlowListIterator<'a> {
+ FlowListIterator {
+ nelem: self.len(),
+ head: &self.list_head,
+ }
+ }
+
+ /// Provide a forward iterator with mutable references
+ #[inline]
+ pub fn mut_iter<'a>(&'a mut self) -> MutFlowListIterator<'a> {
+ let len = self.len();
+ let head_raw = match self.list_head {
+ Some(ref mut h) => Rawlink::some(h.get()),
+ None => Rawlink::none(),
+ };
+ MutFlowListIterator {
+ nelem: len,
+ head: head_raw,
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for FlowList {
+ fn drop(&mut self) {
+ // Dissolve the list in backwards direction
+ // Just dropping the list_head can lead to stack exhaustion
+ // when length is >> 1_000_000
+ let mut tail = mem::replace(&mut self.list_tail, None);
+ loop {
+ let new_tail = match tail {
+ None => break,
+ Some(ref mut prev) => {
+ let prev_base = mut_base(prev.get_mut());
+ prev_base.next_sibling.take();
+ prev_base.prev_sibling.clone()
+ }
+ };
+ tail = new_tail
+ }
+ self.length = 0;
+ self.list_head = None;
+ }
+}
+
+impl<'a> Iterator<&'a Flow> for FlowListIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a Flow> {
+ if self.nelem == 0 {
+ return None;
+ }
+ self.head.as_ref().map(|head| {
+ let head_base = base(head.get());
+ self.nelem -= 1;
+ self.head = &head_base.next_sibling;
+ let ret: &Flow = head.get();
+ ret
+ })
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (uint, Option<uint>) {
+ (self.nelem, Some(self.nelem))
+ }
+}
+
+impl<'a> Iterator<&'a mut Flow> for MutFlowListIterator<'a> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a mut Flow> {
+ if self.nelem == 0 {
+ return None;
+ }
+ unsafe {
+ self.head.resolve_mut().map(|next| {
+ self.nelem -= 1;
+ self.head = match mut_base(next).next_sibling {
+ Some(ref mut node) => {
+ let x: &mut Flow = node.get_mut();
+ // NOTE: transmute needed here to break the link
+ // between x and next so that it is no longer
+ // borrowed.
+ mem::transmute(Rawlink::some(x))
+ }
+ None => Rawlink::none(),
+ };
+ next
+ })
+ }
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (uint, Option<uint>) {
+ (self.nelem, Some(self.nelem))
+ }
+}
diff --git a/components/layout/flow_ref.rs b/components/layout/flow_ref.rs
new file mode 100644
index 00000000000..d90d9ac4cc0
--- /dev/null
+++ b/components/layout/flow_ref.rs
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Reference-counted pointers to flows.
+///
+/// Eventually, with dynamically sized types in Rust, much of this code will be superfluous.
+
+use flow::Flow;
+use flow;
+
+use std::mem;
+use std::ptr;
+use std::raw;
+use std::sync::atomics::SeqCst;
+
+#[unsafe_no_drop_flag]
+pub struct FlowRef {
+ object: raw::TraitObject,
+}
+
+impl FlowRef {
+ pub fn new(mut flow: Box<Flow>) -> FlowRef {
+ unsafe {
+ let result = {
+ let flow_ref: &mut Flow = flow;
+ let object = mem::transmute::<&mut Flow, raw::TraitObject>(flow_ref);
+ FlowRef { object: object }
+ };
+ mem::forget(flow);
+ result
+ }
+ }
+
+ pub fn get<'a>(&'a self) -> &'a Flow {
+ unsafe {
+ mem::transmute_copy::<raw::TraitObject, &'a Flow>(&self.object)
+ }
+ }
+
+ pub fn get_mut<'a>(&'a mut self) -> &'a mut Flow {
+ unsafe {
+ mem::transmute_copy::<raw::TraitObject, &'a mut Flow>(&self.object)
+ }
+ }
+}
+
+impl Drop for FlowRef {
+ fn drop(&mut self) {
+ unsafe {
+ if self.object.vtable.is_null() {
+ return
+ }
+ if flow::base(self.get()).ref_count().fetch_sub(1, SeqCst) > 1 {
+ return
+ }
+ let flow_ref: FlowRef = mem::replace(self, FlowRef {
+ object: raw::TraitObject {
+ vtable: ptr::mut_null(),
+ data: ptr::mut_null(),
+ }
+ });
+ drop(mem::transmute::<raw::TraitObject, Box<Flow>>(flow_ref.object));
+ mem::forget(flow_ref);
+ self.object.vtable = ptr::mut_null();
+ self.object.data = ptr::mut_null();
+ }
+ }
+}
+
+impl Clone for FlowRef {
+ fn clone(&self) -> FlowRef {
+ unsafe {
+ drop(flow::base(self.get()).ref_count().fetch_add(1, SeqCst));
+ FlowRef {
+ object: raw::TraitObject {
+ vtable: self.object.vtable,
+ data: self.object.data,
+ }
+ }
+ }
+ }
+}
+
diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs
new file mode 100644
index 00000000000..191283603b9
--- /dev/null
+++ b/components/layout/fragment.rs
@@ -0,0 +1,1597 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The `Fragment` type, which represents the leaves of the layout tree.
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::{ClearBoth, ClearLeft, ClearRight, ClearType};
+use flow::Flow;
+use flow;
+use inline::{InlineFragmentContext, InlineMetrics};
+use layout_debug;
+use model::{Auto, IntrinsicISizes, MaybeAuto, Specified, specified};
+use model;
+use text;
+use util::{OpaqueNodeMethods, ToGfxColor};
+use wrapper::{TLayoutNode, ThreadSafeLayoutNode};
+
+use geom::{Point2D, Rect, Size2D, SideOffsets2D};
+use geom::approxeq::ApproxEq;
+use gfx::color::rgb;
+use gfx::display_list::{BackgroundAndBorderLevel, BaseDisplayItem, BorderDisplayItem};
+use gfx::display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass};
+use gfx::display_list::{ContentStackingLevel, DisplayItem, DisplayList, ImageDisplayItem};
+use gfx::display_list::{ImageDisplayItemClass, LineDisplayItem};
+use gfx::display_list::{LineDisplayItemClass, OpaqueNode, PseudoDisplayItemClass};
+use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingLevel};
+use gfx::display_list::{TextDisplayItem, TextDisplayItemClass};
+use gfx::display_list::{Upright, SidewaysLeft, SidewaysRight};
+use gfx::font::FontStyle;
+use gfx::text::glyph::CharIndex;
+use gfx::text::text_run::TextRun;
+use serialize::{Encodable, Encoder};
+use servo_msg::constellation_msg::{ConstellationChan, FrameRectMsg, PipelineId, SubpageId};
+use servo_net::image::holder::ImageHolder;
+use servo_net::local_image_cache::LocalImageCache;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize, LogicalMargin};
+use servo_util::range::*;
+use servo_util::namespace;
+use servo_util::smallvec::SmallVec;
+use servo_util::str::is_whitespace;
+use std::fmt;
+use std::from_str::FromStr;
+use std::mem;
+use std::num::Zero;
+use style::{ComputedValues, TElement, TNode, cascade_anonymous, RGBA};
+use style::computed_values::{LengthOrPercentageOrAuto, overflow, LPA_Auto, background_attachment};
+use style::computed_values::{background_repeat, border_style, clear, position, text_align};
+use style::computed_values::{text_decoration, vertical_align, visibility, white_space};
+use sync::{Arc, Mutex};
+use url::Url;
+
+/// Fragments (`struct Fragment`) are the leaves of the layout tree. They cannot position themselves. In
+/// general, fragments do not have a simple correspondence with CSS fragments in the specification:
+///
+/// * Several fragments may correspond to the same CSS box or DOM node. For example, a CSS text box
+/// broken across two lines is represented by two fragments.
+///
+/// * Some CSS fragments are not created at all, such as some anonymous block fragments induced by inline
+/// fragments with block-level sibling fragments. In that case, Servo uses an `InlineFlow` with
+/// `BlockFlow` siblings; the `InlineFlow` is block-level, but not a block container. It is
+/// positioned as if it were a block fragment, but its children are positioned according to inline
+/// flow.
+///
+/// A `GenericFragment` is an empty fragment that contributes only borders, margins, padding, and
+/// backgrounds. It is analogous to a CSS nonreplaced content box.
+///
+/// A fragment's type influences how its styles are interpreted during layout. For example, replaced
+/// content such as images are resized differently from tables, text, or other content. Different
+/// types of fragments may also contain custom data; for example, text fragments contain text.
+///
+/// FIXME(#2260, pcwalton): This can be slimmed down some.
+#[deriving(Clone)]
+pub struct Fragment {
+ /// An opaque reference to the DOM node that this `Fragment` originates from.
+ pub node: OpaqueNode,
+
+ /// The CSS style of this fragment.
+ pub style: Arc<ComputedValues>,
+
+ /// The position of this fragment relative to its owning flow.
+ /// The size includes padding and border, but not margin.
+ pub border_box: LogicalRect<Au>,
+
+ /// The sum of border and padding; i.e. the distance from the edge of the border box to the
+ /// content edge of the fragment.
+ pub border_padding: LogicalMargin<Au>,
+
+ /// The margin of the content box.
+ pub margin: LogicalMargin<Au>,
+
+ /// Info specific to the kind of fragment. Keep this enum small.
+ pub specific: SpecificFragmentInfo,
+
+ /// New-line chracter(\n)'s positions(relative, not absolute)
+ ///
+ /// FIXME(#2260, pcwalton): This is very inefficient; remove.
+ pub new_line_pos: Vec<CharIndex>,
+
+ /// Holds the style context information for fragments
+ /// that are part of an inline formatting context.
+ pub inline_context: Option<InlineFragmentContext>,
+
+ /// A debug ID that is consistent for the life of
+ /// this fragment (via transform etc).
+ pub debug_id: uint,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Fragment {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_struct("fragment", 0, |e| {
+ try!(e.emit_struct_field("id", 0, |e| self.debug_id().encode(e)))
+ try!(e.emit_struct_field("border_box", 1, |e| self.border_box.encode(e)))
+ e.emit_struct_field("margin", 2, |e| self.margin.encode(e))
+ })
+ }
+}
+
+/// Info specific to the kind of fragment. Keep this enum small.
+#[deriving(Clone)]
+pub enum SpecificFragmentInfo {
+ GenericFragment,
+ ImageFragment(ImageFragmentInfo),
+ IframeFragment(IframeFragmentInfo),
+ ScannedTextFragment(ScannedTextFragmentInfo),
+ TableFragment,
+ TableCellFragment,
+ TableColumnFragment(TableColumnFragmentInfo),
+ TableRowFragment,
+ TableWrapperFragment,
+ UnscannedTextFragment(UnscannedTextFragmentInfo),
+}
+
+/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
+#[deriving(Clone)]
+pub struct ImageFragmentInfo {
+ /// The image held within this fragment.
+ pub image: ImageHolder,
+ pub computed_inline_size: Option<Au>,
+ pub computed_block_size: Option<Au>,
+ pub dom_inline_size: Option<Au>,
+ pub dom_block_size: Option<Au>,
+ pub writing_mode_is_vertical: bool,
+}
+
+impl ImageFragmentInfo {
+ /// Creates a new image fragment from the given URL and local image cache.
+ ///
+ /// FIXME(pcwalton): The fact that image fragments store the cache in the fragment makes little sense to
+ /// me.
+ pub fn new(node: &ThreadSafeLayoutNode,
+ image_url: Url,
+ local_image_cache: Arc<Mutex<LocalImageCache>>)
+ -> ImageFragmentInfo {
+ fn convert_length(node: &ThreadSafeLayoutNode, name: &str) -> Option<Au> {
+ let element = node.as_element();
+ element.get_attr(&namespace::Null, name).and_then(|string| {
+ let n: Option<int> = FromStr::from_str(string);
+ n
+ }).and_then(|pixels| Some(Au::from_px(pixels)))
+ }
+
+ let is_vertical = node.style().writing_mode.is_vertical();
+ let dom_width = convert_length(node, "width");
+ let dom_height = convert_length(node, "height");
+ ImageFragmentInfo {
+ image: ImageHolder::new(image_url, local_image_cache),
+ computed_inline_size: None,
+ computed_block_size: None,
+ dom_inline_size: if is_vertical { dom_height } else { dom_width },
+ dom_block_size: if is_vertical { dom_width } else { dom_height },
+ writing_mode_is_vertical: is_vertical,
+ }
+ }
+
+ /// Returns the calculated inline-size of the image, accounting for the inline-size attribute.
+ pub fn computed_inline_size(&self) -> Au {
+ self.computed_inline_size.expect("image inline_size is not computed yet!")
+ }
+
+ /// Returns the calculated block-size of the image, accounting for the block-size attribute.
+ pub fn computed_block_size(&self) -> Au {
+ self.computed_block_size.expect("image block_size is not computed yet!")
+ }
+
+ /// Returns the original inline-size of the image.
+ pub fn image_inline_size(&mut self) -> Au {
+ let size = self.image.get_size().unwrap_or(Size2D::zero());
+ Au::from_px(if self.writing_mode_is_vertical { size.height } else { size.width })
+ }
+
+ /// Returns the original block-size of the image.
+ pub fn image_block_size(&mut self) -> Au {
+ let size = self.image.get_size().unwrap_or(Size2D::zero());
+ Au::from_px(if self.writing_mode_is_vertical { size.width } else { size.height })
+ }
+
+ // Return used value for inline-size or block-size.
+ //
+ // `dom_length`: inline-size or block-size as specified in the `img` tag.
+ // `style_length`: inline-size as given in the CSS
+ pub fn style_length(style_length: LengthOrPercentageOrAuto,
+ dom_length: Option<Au>,
+ container_inline_size: Au) -> MaybeAuto {
+ match (MaybeAuto::from_style(style_length,container_inline_size),dom_length) {
+ (Specified(length),_) => {
+ Specified(length)
+ },
+ (Auto,Some(length)) => {
+ Specified(length)
+ },
+ (Auto,None) => {
+ Auto
+ }
+ }
+ }
+}
+
+/// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the size
+/// of this iframe can be communicated via the constellation to the iframe's own layout task.
+#[deriving(Clone)]
+pub struct IframeFragmentInfo {
+ /// The pipeline ID of this iframe.
+ pub pipeline_id: PipelineId,
+ /// The subpage ID of this iframe.
+ pub subpage_id: SubpageId,
+}
+
+impl IframeFragmentInfo {
+ /// Creates the information specific to an iframe fragment.
+ pub fn new(node: &ThreadSafeLayoutNode) -> IframeFragmentInfo {
+ let (pipeline_id, subpage_id) = node.iframe_pipeline_and_subpage_ids();
+ IframeFragmentInfo {
+ pipeline_id: pipeline_id,
+ subpage_id: subpage_id,
+ }
+ }
+}
+
+/// A scanned text fragment represents a single run of text with a distinct style. A `TextFragment`
+/// may be split into two or more fragments across line breaks. Several `TextFragment`s may
+/// correspond to a single DOM text node. Split text fragments are implemented by referring to
+/// subsets of a single `TextRun` object.
+#[deriving(Clone)]
+pub struct ScannedTextFragmentInfo {
+ /// The text run that this represents.
+ pub run: Arc<Box<TextRun>>,
+
+ /// The range within the above text run that this represents.
+ pub range: Range<CharIndex>,
+}
+
+impl ScannedTextFragmentInfo {
+ /// Creates the information specific to a scanned text fragment from a range and a text run.
+ pub fn new(run: Arc<Box<TextRun>>, range: Range<CharIndex>) -> ScannedTextFragmentInfo {
+ ScannedTextFragmentInfo {
+ run: run,
+ range: range,
+ }
+ }
+}
+
+#[deriving(Show)]
+pub struct SplitInfo {
+ // TODO(bjz): this should only need to be a single character index, but both values are
+ // currently needed for splitting in the `inline::try_append_*` functions.
+ pub range: Range<CharIndex>,
+ pub inline_size: Au,
+}
+
+impl SplitInfo {
+ fn new(range: Range<CharIndex>, info: &ScannedTextFragmentInfo) -> SplitInfo {
+ SplitInfo {
+ range: range,
+ inline_size: info.run.advance_for_range(&range),
+ }
+ }
+}
+
+/// Data for an unscanned text fragment. Unscanned text fragments are the results of flow construction that
+/// have not yet had their inline-size determined.
+#[deriving(Clone)]
+pub struct UnscannedTextFragmentInfo {
+ /// The text inside the fragment.
+ pub text: String,
+}
+
+impl UnscannedTextFragmentInfo {
+ /// Creates a new instance of `UnscannedTextFragmentInfo` from the given DOM node.
+ pub fn new(node: &ThreadSafeLayoutNode) -> UnscannedTextFragmentInfo {
+ // FIXME(pcwalton): Don't copy text; atomically reference count it instead.
+ UnscannedTextFragmentInfo {
+ text: node.text(),
+ }
+ }
+
+ /// Creates a new instance of `UnscannedTextFragmentInfo` from the given text.
+ #[inline]
+ pub fn from_text(text: String) -> UnscannedTextFragmentInfo {
+ UnscannedTextFragmentInfo {
+ text: text,
+ }
+ }
+}
+
+/// A fragment that represents a table column.
+#[deriving(Clone)]
+pub struct TableColumnFragmentInfo {
+ /// the number of columns a <col> element should span
+ pub span: Option<int>,
+}
+
+impl TableColumnFragmentInfo {
+ /// Create the information specific to an table column fragment.
+ pub fn new(node: &ThreadSafeLayoutNode) -> TableColumnFragmentInfo {
+ let span = {
+ let element = node.as_element();
+ element.get_attr(&namespace::Null, "span").and_then(|string| {
+ let n: Option<int> = FromStr::from_str(string);
+ n
+ })
+ };
+ TableColumnFragmentInfo {
+ span: span,
+ }
+ }
+}
+
+impl Fragment {
+ /// Constructs a new `Fragment` instance for the given node.
+ ///
+ /// Arguments:
+ ///
+ /// * `constructor`: The flow constructor.
+ ///
+ /// * `node`: The node to create a fragment for.
+ pub fn new(constructor: &mut FlowConstructor, node: &ThreadSafeLayoutNode) -> Fragment {
+ let style = node.style().clone();
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: constructor.build_specific_fragment_info_for_node(node),
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance from a specific info.
+ pub fn new_from_specific_info(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment {
+ let style = node.style().clone();
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance for an anonymous table object.
+ pub fn new_anonymous_table_fragment(node: &ThreadSafeLayoutNode, specific: SpecificFragmentInfo) -> Fragment {
+ // CSS 2.1 § 17.2.1 This is for non-inherited properties on anonymous table fragments
+ // example:
+ //
+ // <div style="display: table">
+ // Foo
+ // </div>
+ //
+ // Anonymous table fragments, TableRowFragment and TableCellFragment, are generated around `Foo`, but it shouldn't inherit the border.
+
+ let node_style = cascade_anonymous(&**node.style());
+ let writing_mode = node_style.writing_mode;
+ Fragment {
+ node: OpaqueNodeMethods::from_thread_safe_layout_node(node),
+ style: Arc::new(node_style),
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Constructs a new `Fragment` instance from an opaque node.
+ pub fn from_opaque_node_and_style(node: OpaqueNode,
+ style: Arc<ComputedValues>,
+ specific: SpecificFragmentInfo)
+ -> Fragment {
+ let writing_mode = style.writing_mode;
+ Fragment {
+ node: node,
+ style: style,
+ border_box: LogicalRect::zero(writing_mode),
+ border_padding: LogicalMargin::zero(writing_mode),
+ margin: LogicalMargin::zero(writing_mode),
+ specific: specific,
+ new_line_pos: vec!(),
+ inline_context: None,
+ debug_id: layout_debug::generate_unique_debug_id(),
+ }
+ }
+
+ /// Returns a debug ID of this fragment. This ID should not be considered stable across multiple
+ /// layouts or fragment manipulations.
+ pub fn debug_id(&self) -> uint {
+ self.debug_id
+ }
+
+ /// Transforms this fragment into another fragment of the given type, with the given size, preserving all
+ /// the other data.
+ pub fn transform(&self, size: LogicalSize<Au>, specific: SpecificFragmentInfo) -> Fragment {
+ Fragment {
+ node: self.node,
+ style: self.style.clone(),
+ border_box: LogicalRect::from_point_size(
+ self.style.writing_mode, self.border_box.start, size),
+ border_padding: self.border_padding,
+ margin: self.margin,
+ specific: specific,
+ new_line_pos: self.new_line_pos.clone(),
+ inline_context: self.inline_context.clone(),
+ debug_id: self.debug_id,
+ }
+ }
+
+ /// Adds a style to the inline context for this fragment. If the inline
+ /// context doesn't exist yet, it will be created.
+ pub fn add_inline_context_style(&mut self, style: Arc<ComputedValues>) {
+ if self.inline_context.is_none() {
+ self.inline_context = Some(InlineFragmentContext::new());
+ }
+ self.inline_context.get_mut_ref().styles.push(style.clone());
+ }
+
+ /// Uses the style only to estimate the intrinsic inline-sizes. These may be modified for text or
+ /// replaced elements.
+ fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizes {
+ let (use_margins, use_padding) = match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) => (true, true),
+ TableFragment | TableCellFragment => (false, true),
+ TableWrapperFragment => (true, false),
+ TableRowFragment => (false, false),
+ ScannedTextFragment(_) | TableColumnFragment(_) | UnscannedTextFragment(_) => {
+ // Styles are irrelevant for these kinds of fragments.
+ return IntrinsicISizes::new()
+ }
+ };
+
+ let style = self.style();
+ let inline_size = MaybeAuto::from_style(style.content_inline_size(), Au::new(0)).specified_or_zero();
+
+ let margin = style.logical_margin();
+ let (margin_inline_start, margin_inline_end) = if use_margins {
+ (MaybeAuto::from_style(margin.inline_start, Au(0)).specified_or_zero(),
+ MaybeAuto::from_style(margin.inline_end, Au(0)).specified_or_zero())
+ } else {
+ (Au(0), Au(0))
+ };
+
+ let padding = style.logical_padding();
+ let (padding_inline_start, padding_inline_end) = if use_padding {
+ (model::specified(padding.inline_start, Au(0)),
+ model::specified(padding.inline_end, Au(0)))
+ } else {
+ (Au(0), Au(0))
+ };
+
+ // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK?
+ let border = self.border_width();
+ let surround_inline_size = margin_inline_start + margin_inline_end + padding_inline_start + padding_inline_end +
+ border.inline_start_end();
+
+ IntrinsicISizes {
+ minimum_inline_size: inline_size,
+ preferred_inline_size: inline_size,
+ surround_inline_size: surround_inline_size,
+ }
+ }
+
+ pub fn calculate_line_height(&self, layout_context: &LayoutContext) -> Au {
+ let font_style = text::computed_style_to_font_style(&*self.style);
+ let font_metrics = text::font_metrics_for_style(layout_context.font_context(), &font_style);
+ text::line_height_from_style(&*self.style, &font_metrics)
+ }
+
+ /// Returns the sum of the inline-sizes of all the borders of this fragment. This is private because
+ /// it should only be called during intrinsic inline-size computation or computation of
+ /// `border_padding`. Other consumers of this information should simply consult that field.
+ #[inline]
+ fn border_width(&self) -> LogicalMargin<Au> {
+ match self.inline_context {
+ None => self.style().logical_border_width(),
+ Some(ref inline_fragment_context) => {
+ let zero = LogicalMargin::zero(self.style.writing_mode);
+ inline_fragment_context.styles.iter().fold(zero, |acc, style| acc + style.logical_border_width())
+ }
+ }
+ }
+
+ /// Computes the border, padding, and vertical margins from the containing block inline-size and the
+ /// style. After this call, the `border_padding` and the vertical direction of the `margin`
+ /// field will be correct.
+ pub fn compute_border_padding_margins(&mut self,
+ containing_block_inline_size: Au) {
+ // Compute vertical margins. Note that this value will be ignored by layout if the style
+ // specifies `auto`.
+ match self.specific {
+ TableFragment | TableCellFragment | TableRowFragment | TableColumnFragment(_) => {
+ self.margin.block_start = Au(0);
+ self.margin.block_end = Au(0)
+ }
+ _ => {
+ // NB: Percentages are relative to containing block inline-size (not block-size) per CSS 2.1.
+ let margin = self.style().logical_margin();
+ self.margin.block_start = MaybeAuto::from_style(margin.block_start, containing_block_inline_size)
+ .specified_or_zero();
+ self.margin.block_end = MaybeAuto::from_style(margin.block_end, containing_block_inline_size)
+ .specified_or_zero()
+ }
+ }
+
+ // Compute border.
+ let border = self.border_width();
+
+ // Compute padding.
+ let padding = match self.specific {
+ TableColumnFragment(_) | TableRowFragment |
+ TableWrapperFragment => LogicalMargin::zero(self.style.writing_mode),
+ _ => {
+ match self.inline_context {
+ None => model::padding_from_style(self.style(), containing_block_inline_size),
+ Some(ref inline_fragment_context) => {
+ let zero = LogicalMargin::zero(self.style.writing_mode);
+ inline_fragment_context.styles.iter()
+ .fold(zero, |acc, style| acc + model::padding_from_style(&**style, Au(0)))
+ }
+ }
+ }
+ };
+
+ self.border_padding = border + padding
+ }
+
+ // Return offset from original position because of `position: relative`.
+ pub fn relative_position(&self,
+ containing_block_size: &LogicalSize<Au>)
+ -> LogicalSize<Au> {
+ fn from_style(style: &ComputedValues, container_size: &LogicalSize<Au>)
+ -> LogicalSize<Au> {
+ let offsets = style.logical_position();
+ let offset_i = if offsets.inline_start != LPA_Auto {
+ MaybeAuto::from_style(offsets.inline_start, container_size.inline).specified_or_zero()
+ } else {
+ -MaybeAuto::from_style(offsets.inline_end, container_size.inline).specified_or_zero()
+ };
+ let offset_b = if offsets.block_start != LPA_Auto {
+ MaybeAuto::from_style(offsets.block_start, container_size.inline).specified_or_zero()
+ } else {
+ -MaybeAuto::from_style(offsets.block_end, container_size.inline).specified_or_zero()
+ };
+ LogicalSize::new(style.writing_mode, offset_i, offset_b)
+ }
+
+ // Go over the ancestor fragments and add all relative offsets (if any).
+ let mut rel_pos = LogicalSize::zero(self.style.writing_mode);
+ match self.inline_context {
+ None => {
+ if self.style().get_box().position == position::relative {
+ rel_pos = rel_pos + from_style(self.style(), containing_block_size);
+ }
+ }
+ Some(ref inline_fragment_context) => {
+ for style in inline_fragment_context.styles.iter() {
+ if style.get_box().position == position::relative {
+ rel_pos = rel_pos + from_style(&**style, containing_block_size);
+ }
+ }
+ },
+ }
+ rel_pos
+ }
+
+ /// Always inline for SCCP.
+ ///
+ /// FIXME(pcwalton): Just replace with the clear type from the style module for speed?
+ #[inline(always)]
+ pub fn clear(&self) -> Option<ClearType> {
+ let style = self.style();
+ match style.get_box().clear {
+ clear::none => None,
+ clear::left => Some(ClearLeft),
+ clear::right => Some(ClearRight),
+ clear::both => Some(ClearBoth),
+ }
+ }
+
+ /// Converts this fragment's computed style to a font style used for rendering.
+ pub fn font_style(&self) -> FontStyle {
+ text::computed_style_to_font_style(self.style())
+ }
+
+ #[inline(always)]
+ pub fn style<'a>(&'a self) -> &'a ComputedValues {
+ &*self.style
+ }
+
+ /// Returns the text alignment of the computed style of the nearest ancestor-or-self `Element`
+ /// node.
+ pub fn text_align(&self) -> text_align::T {
+ self.style().get_inheritedtext().text_align
+ }
+
+ pub fn vertical_align(&self) -> vertical_align::T {
+ self.style().get_box().vertical_align
+ }
+
+ pub fn white_space(&self) -> white_space::T {
+ self.style().get_inheritedtext().white_space
+ }
+
+ /// Returns the text decoration of this fragment, according to the style of the nearest ancestor
+ /// element.
+ ///
+ /// NB: This may not be the actual text decoration, because of the override rules specified in
+ /// CSS 2.1 § 16.3.1. Unfortunately, computing this properly doesn't really fit into Servo's
+ /// model. Therefore, this is a best lower bound approximation, but the end result may actually
+ /// have the various decoration flags turned on afterward.
+ pub fn text_decoration(&self) -> text_decoration::T {
+ self.style().get_text().text_decoration
+ }
+
+ /// Returns the inline-start offset from margin edge to content edge.
+ ///
+ /// FIXME(#2262, pcwalton): I think this method is pretty bogus, because it won't work for
+ /// inlines.
+ pub fn inline_start_offset(&self) -> Au {
+ match self.specific {
+ TableWrapperFragment => self.margin.inline_start,
+ TableFragment | TableCellFragment | TableRowFragment => self.border_padding.inline_start,
+ TableColumnFragment(_) => Au(0),
+ _ => self.margin.inline_start + self.border_padding.inline_start,
+ }
+ }
+
+ /// Returns true if this element can be split. This is true for text fragments.
+ pub fn can_split(&self) -> bool {
+ match self.specific {
+ ScannedTextFragment(..) => true,
+ _ => false,
+ }
+ }
+
+ /// Adds the display items necessary to paint the background of this fragment to the display
+ /// list if necessary.
+ pub fn build_display_list_for_background_if_applicable(&self,
+ style: &ComputedValues,
+ list: &mut DisplayList,
+ layout_context: &LayoutContext,
+ level: StackingLevel,
+ absolute_bounds: &Rect<Au>) {
+ // FIXME: This causes a lot of background colors to be displayed when they are clearly not
+ // needed. We could use display list optimization to clean this up, but it still seems
+ // inefficient. What we really want is something like "nearest ancestor element that
+ // doesn't have a fragment".
+ let background_color = style.resolve_color(style.get_background().background_color);
+ if !background_color.alpha.approx_eq(&0.0) {
+ let display_item = box SolidColorDisplayItem {
+ base: BaseDisplayItem::new(*absolute_bounds, self.node, level),
+ color: background_color.to_gfx_color(),
+ };
+
+ list.push(SolidColorDisplayItemClass(display_item))
+ }
+
+ // The background image is painted on top of the background color.
+ // Implements background image, per spec:
+ // http://www.w3.org/TR/CSS21/colors.html#background
+ let background = style.get_background();
+ let image_url = match background.background_image {
+ None => return,
+ Some(ref image_url) => image_url,
+ };
+
+ let mut holder = ImageHolder::new(image_url.clone(), layout_context.shared.image_cache.clone());
+ let image = match holder.get_image() {
+ None => {
+ // No image data at all? Do nothing.
+ //
+ // TODO: Add some kind of placeholder background image.
+ debug!("(building display list) no background image :(");
+ return
+ }
+ Some(image) => image,
+ };
+ debug!("(building display list) building background image");
+
+ // Adjust bounds for `background-position` and `background-attachment`.
+ let mut bounds = *absolute_bounds;
+ let horizontal_position = model::specified(background.background_position.horizontal,
+ bounds.size.width);
+ let vertical_position = model::specified(background.background_position.vertical,
+ bounds.size.height);
+
+ let clip_display_item;
+ match background.background_attachment {
+ background_attachment::scroll => {
+ clip_display_item = None;
+ bounds.origin.x = bounds.origin.x + horizontal_position;
+ bounds.origin.y = bounds.origin.y + vertical_position;
+ bounds.size.width = bounds.size.width - horizontal_position;
+ bounds.size.height = bounds.size.height - vertical_position;
+ }
+ background_attachment::fixed => {
+ clip_display_item = Some(box ClipDisplayItem {
+ base: BaseDisplayItem::new(bounds, self.node, level),
+ children: DisplayList::new(),
+ });
+
+ bounds = Rect {
+ origin: Point2D(horizontal_position, vertical_position),
+ size: Size2D(bounds.origin.x + bounds.size.width,
+ bounds.origin.y + bounds.size.height),
+ }
+ }
+ }
+
+ // Adjust sizes for `background-repeat`.
+ match background.background_repeat {
+ background_repeat::no_repeat => {
+ bounds.size.width = Au::from_px(image.width as int);
+ bounds.size.height = Au::from_px(image.height as int)
+ }
+ background_repeat::repeat_x => {
+ bounds.size.height = Au::from_px(image.height as int)
+ }
+ background_repeat::repeat_y => {
+ bounds.size.width = Au::from_px(image.width as int)
+ }
+ background_repeat::repeat => {}
+ };
+
+ // Create the image display item.
+ let image_display_item = ImageDisplayItemClass(box ImageDisplayItem {
+ base: BaseDisplayItem::new(bounds, self.node, level),
+ image: image.clone(),
+ stretch_size: Size2D(Au::from_px(image.width as int),
+ Au::from_px(image.height as int)),
+ });
+
+ match clip_display_item {
+ None => list.push(image_display_item),
+ Some(mut clip_display_item) => {
+ clip_display_item.children.push(image_display_item);
+ list.push(ClipDisplayItemClass(clip_display_item))
+ }
+ }
+ }
+
+ /// Adds the display items necessary to paint the borders of this fragment to a display list if
+ /// necessary.
+ pub fn build_display_list_for_borders_if_applicable(&self,
+ style: &ComputedValues,
+ list: &mut DisplayList,
+ abs_bounds: &Rect<Au>,
+ level: StackingLevel) {
+ let border = style.logical_border_width();
+ if border.is_zero() {
+ return
+ }
+
+ let top_color = style.resolve_color(style.get_border().border_top_color);
+ let right_color = style.resolve_color(style.get_border().border_right_color);
+ let bottom_color = style.resolve_color(style.get_border().border_bottom_color);
+ let left_color = style.resolve_color(style.get_border().border_left_color);
+
+ // Append the border to the display list.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(*abs_bounds, self.node, level),
+ border: border.to_physical(style.writing_mode),
+ color: SideOffsets2D::new(top_color.to_gfx_color(),
+ right_color.to_gfx_color(),
+ bottom_color.to_gfx_color(),
+ left_color.to_gfx_color()),
+ style: SideOffsets2D::new(style.get_border().border_top_style,
+ style.get_border().border_right_style,
+ style.get_border().border_bottom_style,
+ style.get_border().border_left_style)
+ };
+
+ list.push(BorderDisplayItemClass(border_display_item))
+ }
+
+ fn build_debug_borders_around_text_fragments(&self,
+ display_list: &mut DisplayList,
+ flow_origin: Point2D<Au>,
+ text_fragment: &ScannedTextFragmentInfo) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
+
+ // Compute the text fragment bounds and draw a border surrounding them.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel),
+ border: SideOffsets2D::new_all_same(Au::from_px(1)),
+ color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
+ style: SideOffsets2D::new_all_same(border_style::solid)
+ };
+ display_list.push(BorderDisplayItemClass(border_display_item));
+
+ // Draw a rectangle representing the baselines.
+ let ascent = text_fragment.run.ascent();
+ let mut baseline = self.border_box.clone();
+ baseline.start.b = baseline.start.b + ascent;
+ baseline.size.block = Au(0);
+ let mut baseline = baseline.to_physical(self.style.writing_mode, container_size);
+ baseline.origin = baseline.origin + flow_origin;
+
+ let line_display_item = box LineDisplayItem {
+ base: BaseDisplayItem::new(baseline, self.node, ContentStackingLevel),
+ color: rgb(0, 200, 0),
+ style: border_style::dashed,
+ };
+ display_list.push(LineDisplayItemClass(line_display_item));
+ }
+
+ fn build_debug_borders_around_fragment(&self,
+ display_list: &mut DisplayList,
+ flow_origin: Point2D<Au>) {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ // Fragment position wrt to the owning flow.
+ let fragment_bounds = self.border_box.to_physical(self.style.writing_mode, container_size);
+ let absolute_fragment_bounds = Rect(
+ fragment_bounds.origin + flow_origin,
+ fragment_bounds.size);
+
+ // This prints a debug border around the border of this fragment.
+ let border_display_item = box BorderDisplayItem {
+ base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, ContentStackingLevel),
+ border: SideOffsets2D::new_all_same(Au::from_px(1)),
+ color: SideOffsets2D::new_all_same(rgb(0, 0, 200)),
+ style: SideOffsets2D::new_all_same(border_style::solid)
+ };
+ display_list.push(BorderDisplayItemClass(border_display_item))
+ }
+
+ /// Adds the display items for this fragment to the given stacking context.
+ ///
+ /// Arguments:
+ ///
+ /// * `display_list`: The unflattened display list to add display items to.
+ /// * `layout_context`: The layout context.
+ /// * `dirty`: The dirty rectangle in the coordinate system of the owning flow.
+ /// * `flow_origin`: Position of the origin of the owning flow wrt the display list root flow.
+ pub fn build_display_list(&self,
+ display_list: &mut DisplayList,
+ layout_context: &LayoutContext,
+ flow_origin: Point2D<Au>,
+ background_and_border_level: BackgroundAndBorderLevel)
+ -> ChildDisplayListAccumulator {
+ // FIXME(#2795): Get the real container size
+ let container_size = Size2D::zero();
+ let rect_to_absolute = |logical_rect: LogicalRect<Au>| {
+ let physical_rect = logical_rect.to_physical(self.style.writing_mode, container_size);
+ Rect(physical_rect.origin + flow_origin, physical_rect.size)
+ };
+ // Fragment position wrt to the owning flow.
+ let absolute_fragment_bounds = rect_to_absolute(self.border_box);
+ debug!("Fragment::build_display_list at rel={}, abs={}: {}",
+ self.border_box,
+ absolute_fragment_bounds,
+ self);
+ debug!("Fragment::build_display_list: dirty={}, flow_origin={}",
+ layout_context.shared.dirty,
+ flow_origin);
+
+ let mut accumulator = ChildDisplayListAccumulator::new(self.style(),
+ absolute_fragment_bounds,
+ self.node,
+ ContentStackingLevel);
+ if self.style().get_inheritedbox().visibility != visibility::visible {
+ return accumulator
+ }
+
+ if !absolute_fragment_bounds.intersects(&layout_context.shared.dirty) {
+ debug!("Fragment::build_display_list: Did not intersect...");
+ return accumulator
+ }
+
+ debug!("Fragment::build_display_list: intersected. Adding display item...");
+
+ {
+ let level =
+ StackingLevel::from_background_and_border_level(background_and_border_level);
+
+ // Add a pseudo-display item for content box queries. This is a very bogus thing to do.
+ let base_display_item = box BaseDisplayItem::new(absolute_fragment_bounds, self.node, level);
+ display_list.push(PseudoDisplayItemClass(base_display_item));
+
+ // Add the background to the list, if applicable.
+ match self.inline_context {
+ Some(ref inline_context) => {
+ for style in inline_context.styles.iter().rev() {
+ self.build_display_list_for_background_if_applicable(&**style,
+ display_list,
+ layout_context,
+ level,
+ &absolute_fragment_bounds);
+ }
+ }
+ None => {
+ self.build_display_list_for_background_if_applicable(&*self.style,
+ display_list,
+ layout_context,
+ level,
+ &absolute_fragment_bounds);
+ }
+ }
+
+ // Add a border, if applicable.
+ //
+ // TODO: Outlines.
+ match self.inline_context {
+ Some(ref inline_context) => {
+ for style in inline_context.styles.iter().rev() {
+ self.build_display_list_for_borders_if_applicable(&**style,
+ display_list,
+ &absolute_fragment_bounds,
+ level);
+ }
+ }
+ None => {
+ self.build_display_list_for_borders_if_applicable(&*self.style,
+ display_list,
+ &absolute_fragment_bounds,
+ level);
+ }
+ }
+ }
+
+ let content_box = self.content_box();
+ let absolute_content_box = rect_to_absolute(content_box);
+
+ // Add a clip, if applicable.
+ match self.specific {
+ UnscannedTextFragment(_) => fail!("Shouldn't see unscanned fragments here."),
+ TableColumnFragment(_) => fail!("Shouldn't see table column fragments here."),
+ ScannedTextFragment(ref text_fragment) => {
+ // Create the text display item.
+ let orientation = if self.style.writing_mode.is_vertical() {
+ if self.style.writing_mode.is_sideways_left() {
+ SidewaysLeft
+ } else {
+ SidewaysRight
+ }
+ } else {
+ Upright
+ };
+
+ let metrics = &text_fragment.run.font_metrics;
+ let baseline_origin ={
+ let mut tmp = content_box.start;
+ tmp.b = tmp.b + metrics.ascent;
+ tmp.to_physical(self.style.writing_mode, container_size) + flow_origin
+ };
+
+ let text_display_item = box TextDisplayItem {
+ base: BaseDisplayItem::new(
+ absolute_content_box, self.node, ContentStackingLevel),
+ text_run: text_fragment.run.clone(),
+ range: text_fragment.range,
+ text_color: self.style().get_color().color.to_gfx_color(),
+ orientation: orientation,
+ baseline_origin: baseline_origin,
+ };
+ accumulator.push(display_list, TextDisplayItemClass(text_display_item));
+
+
+ // Create display items for text decoration
+ {
+ let line = |maybe_color: Option<RGBA>, rect: || -> LogicalRect<Au>| {
+ match maybe_color {
+ None => {},
+ Some(color) => {
+ accumulator.push(display_list, SolidColorDisplayItemClass(
+ box SolidColorDisplayItem {
+ base: BaseDisplayItem::new(
+ rect_to_absolute(rect()),
+ self.node, ContentStackingLevel),
+ color: color.to_gfx_color(),
+ }
+ ));
+ }
+ }
+ };
+
+ let text_decorations =
+ self.style().get_inheritedtext()._servo_text_decorations_in_effect;
+ line(text_decorations.underline, || {
+ let mut rect = content_box.clone();
+ rect.start.b = rect.start.b + metrics.ascent - metrics.underline_offset;
+ rect.size.block = metrics.underline_size;
+ rect
+ });
+
+ line(text_decorations.overline, || {
+ let mut rect = content_box.clone();
+ rect.size.block = metrics.underline_size;
+ rect
+ });
+
+ line(text_decorations.line_through, || {
+ let mut rect = content_box.clone();
+ rect.start.b = rect.start.b + metrics.ascent - metrics.strikeout_offset;
+ rect.size.block = metrics.strikeout_size;
+ rect
+ });
+ }
+
+ // Draw debug frames for text bounds.
+ //
+ // FIXME(#2263, pcwalton): This is a bit of an abuse of the logging infrastructure.
+ // We should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_text_fragments(display_list,
+ flow_origin,
+ text_fragment))
+ },
+ GenericFragment | IframeFragment(..) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => {
+ // FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
+ // should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
+ },
+ ImageFragment(_) => {
+ match self.specific {
+ ImageFragment(ref image_fragment) => {
+ let image_ref = &image_fragment.image;
+ match image_ref.get_image_if_present() {
+ Some(image) => {
+ debug!("(building display list) building image fragment");
+
+ // Place the image into the display list.
+ let image_display_item = box ImageDisplayItem {
+ base: BaseDisplayItem::new(absolute_content_box,
+ self.node,
+ ContentStackingLevel),
+ image: image.clone(),
+ stretch_size: absolute_content_box.size,
+ };
+ accumulator.push(display_list,
+ ImageDisplayItemClass(image_display_item))
+ }
+ None => {
+ // No image data at all? Do nothing.
+ //
+ // TODO: Add some kind of placeholder image.
+ debug!("(building display list) no image :(");
+ }
+ }
+ }
+ _ => fail!("shouldn't get here"),
+ }
+
+ // FIXME(pcwalton): This is a bit of an abuse of the logging
+ // infrastructure. We should have a real `SERVO_DEBUG` system.
+ debug!("{:?}", self.build_debug_borders_around_fragment(display_list, flow_origin))
+ }
+ }
+
+ // If this is an iframe, then send its position and size up to the constellation.
+ //
+ // FIXME(pcwalton): Doing this during display list construction seems potentially
+ // problematic if iframes are outside the area we're computing the display list for, since
+ // they won't be able to reflow at all until the user scrolls to them. Perhaps we should
+ // separate this into two parts: first we should send the size only to the constellation
+ // once that's computed during assign-block-sizes, and second we should should send the origin
+ // to the constellation here during display list construction. This should work because
+ // layout for the iframe only needs to know size, and origin is only relevant if the
+ // iframe is actually going to be displayed.
+ match self.specific {
+ IframeFragment(ref iframe_fragment) => {
+ self.finalize_position_and_size_of_iframe(iframe_fragment, flow_origin, layout_context)
+ }
+ _ => {}
+ }
+
+ accumulator
+ }
+
+ /// Returns the intrinsic inline-sizes of this fragment.
+ pub fn intrinsic_inline_sizes(&mut self)
+ -> IntrinsicISizes {
+ let mut result = self.style_specified_intrinsic_inline_size();
+
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableColumnFragment(_) | TableRowFragment |
+ TableWrapperFragment => {}
+ ImageFragment(ref mut image_fragment_info) => {
+ let image_inline_size = image_fragment_info.image_inline_size();
+ result.minimum_inline_size = geometry::max(result.minimum_inline_size, image_inline_size);
+ result.preferred_inline_size = geometry::max(result.preferred_inline_size, image_inline_size);
+ }
+ ScannedTextFragment(ref text_fragment_info) => {
+ let range = &text_fragment_info.range;
+ let min_line_inline_size = text_fragment_info.run.min_width_for_range(range);
+
+ // See http://dev.w3.org/csswg/css-sizing/#max-content-inline-size.
+ // TODO: Account for soft wrap opportunities.
+ let max_line_inline_size = text_fragment_info.run.metrics_for_range(range).advance_width;
+
+ result.minimum_inline_size = geometry::max(result.minimum_inline_size, min_line_inline_size);
+ result.preferred_inline_size = geometry::max(result.preferred_inline_size, max_line_inline_size);
+ }
+ UnscannedTextFragment(..) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+
+ // Take borders and padding for parent inline fragments into account, if necessary.
+ match self.inline_context {
+ None => {}
+ Some(ref context) => {
+ for style in context.styles.iter() {
+ let border_width = style.logical_border_width().inline_start_end();
+ let padding_inline_size = model::padding_from_style(&**style, Au(0)).inline_start_end();
+ result.minimum_inline_size = result.minimum_inline_size + border_width + padding_inline_size;
+ result.preferred_inline_size = result.preferred_inline_size + border_width + padding_inline_size;
+ }
+ }
+ }
+
+ result
+ }
+
+
+ /// TODO: What exactly does this function return? Why is it Au(0) for GenericFragment?
+ pub fn content_inline_size(&self) -> Au {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => Au(0),
+ ImageFragment(ref image_fragment_info) => {
+ image_fragment_info.computed_inline_size()
+ }
+ ScannedTextFragment(ref text_fragment_info) => {
+ let (range, run) = (&text_fragment_info.range, &text_fragment_info.run);
+ let text_bounds = run.metrics_for_range(range).bounding_box;
+ text_bounds.size.width
+ }
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+ }
+
+ /// Returns, and computes, the block-size of this fragment.
+ pub fn content_block_size(&self, layout_context: &LayoutContext) -> Au {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => Au(0),
+ ImageFragment(ref image_fragment_info) => {
+ image_fragment_info.computed_block_size()
+ }
+ ScannedTextFragment(_) => {
+ // Compute the block-size based on the line-block-size and font size.
+ self.calculate_line_height(layout_context)
+ }
+ TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ }
+ }
+
+ /// Returns the dimensions of the content box.
+ ///
+ /// This is marked `#[inline]` because it is frequently called when only one or two of the
+ /// values are needed and that will save computation.
+ #[inline]
+ pub fn content_box(&self) -> LogicalRect<Au> {
+ self.border_box - self.border_padding
+ }
+
+ /// Find the split of a fragment that includes a new-line character.
+ ///
+ /// A return value of `None` indicates that the fragment is not splittable.
+ /// Otherwise the split information is returned. The right information is
+ /// optional due to the possibility of it being whitespace.
+ //
+ // TODO(bjz): The text run should be removed in the future, but it is currently needed for
+ // the current method of fragment splitting in the `inline::try_append_*` functions.
+ pub fn find_split_info_by_new_line(&self)
+ -> Option<(SplitInfo, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
+ TableRowFragment | TableWrapperFragment => None,
+ TableColumnFragment(_) => fail!("Table column fragments do not need to split"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ScannedTextFragment(ref text_fragment_info) => {
+ let mut new_line_pos = self.new_line_pos.clone();
+ let cur_new_line_pos = new_line_pos.remove(0).unwrap();
+
+ let inline_start_range = Range::new(text_fragment_info.range.begin(), cur_new_line_pos);
+ let inline_end_range = Range::new(text_fragment_info.range.begin() + cur_new_line_pos + CharIndex(1),
+ text_fragment_info.range.length() - (cur_new_line_pos + CharIndex(1)));
+
+ // Left fragment is for inline-start text of first founded new-line character.
+ let inline_start_fragment = SplitInfo::new(inline_start_range, text_fragment_info);
+
+ // Right fragment is for inline-end text of first founded new-line character.
+ let inline_end_fragment = if inline_end_range.length() > CharIndex(0) {
+ Some(SplitInfo::new(inline_end_range, text_fragment_info))
+ } else {
+ None
+ };
+
+ Some((inline_start_fragment, inline_end_fragment, text_fragment_info.run.clone()))
+ }
+ }
+ }
+
+ /// Attempts to find the split positions of a text fragment so that its inline-size is
+ /// no more than `max_inline-size`.
+ ///
+ /// A return value of `None` indicates that the fragment could not be split.
+ /// Otherwise the information pertaining to the split is returned. The inline-start
+ /// and inline-end split information are both optional due to the possibility of
+ /// them being whitespace.
+ //
+ // TODO(bjz): The text run should be removed in the future, but it is currently needed for
+ // the current method of fragment splitting in the `inline::try_append_*` functions.
+ pub fn find_split_info_for_inline_size(&self, start: CharIndex, max_inline_size: Au, starts_line: bool)
+ -> Option<(Option<SplitInfo>, Option<SplitInfo>, Arc<Box<TextRun>> /* TODO(bjz): remove */)> {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | ImageFragment(_) | TableFragment | TableCellFragment |
+ TableRowFragment | TableWrapperFragment => None,
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ScannedTextFragment(ref text_fragment_info) => {
+ let mut pieces_processed_count: uint = 0;
+ let mut remaining_inline_size: Au = max_inline_size;
+ let mut inline_start_range = Range::new(text_fragment_info.range.begin() + start, CharIndex(0));
+ let mut inline_end_range: Option<Range<CharIndex>> = None;
+
+ debug!("split_to_inline_size: splitting text fragment (strlen={}, range={}, avail_inline_size={})",
+ text_fragment_info.run.text.len(),
+ text_fragment_info.range,
+ max_inline_size);
+
+ for (glyphs, offset, slice_range) in text_fragment_info.run.iter_slices_for_range(
+ &text_fragment_info.range) {
+ debug!("split_to_inline_size: considering slice (offset={}, range={}, \
+ remain_inline_size={})",
+ offset,
+ slice_range,
+ remaining_inline_size);
+
+ let metrics = text_fragment_info.run.metrics_for_slice(glyphs, &slice_range);
+ let advance = metrics.advance_width;
+
+ let should_continue;
+ if advance <= remaining_inline_size {
+ should_continue = true;
+
+ if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() {
+ debug!("split_to_inline_size: case=skipping leading trimmable whitespace");
+ inline_start_range.shift_by(slice_range.length());
+ } else {
+ debug!("split_to_inline_size: case=enlarging span");
+ remaining_inline_size = remaining_inline_size - advance;
+ inline_start_range.extend_by(slice_range.length());
+ }
+ } else {
+ // The advance is more than the remaining inline-size.
+ should_continue = false;
+ let slice_begin = offset + slice_range.begin();
+ let slice_end = offset + slice_range.end();
+
+ if glyphs.is_whitespace() {
+ // If there are still things after the trimmable whitespace, create the
+ // inline-end chunk.
+ if slice_end < text_fragment_info.range.end() {
+ debug!("split_to_inline_size: case=skipping trimmable trailing \
+ whitespace, then split remainder");
+ let inline_end_range_end = text_fragment_info.range.end() - slice_end;
+ inline_end_range = Some(Range::new(slice_end, inline_end_range_end));
+ } else {
+ debug!("split_to_inline_size: case=skipping trimmable trailing \
+ whitespace");
+ }
+ } else if slice_begin < text_fragment_info.range.end() {
+ // There are still some things inline-start over at the end of the line. Create
+ // the inline-end chunk.
+ let inline_end_range_end = text_fragment_info.range.end() - slice_begin;
+ inline_end_range = Some(Range::new(slice_begin, inline_end_range_end));
+ debug!("split_to_inline_size: case=splitting remainder with inline_end range={:?}",
+ inline_end_range);
+ }
+ }
+
+ pieces_processed_count += 1;
+
+ if !should_continue {
+ break
+ }
+ }
+
+ let inline_start_is_some = inline_start_range.length() > CharIndex(0);
+
+ if (pieces_processed_count == 1 || !inline_start_is_some) && !starts_line {
+ None
+ } else {
+ let inline_start = if inline_start_is_some {
+ Some(SplitInfo::new(inline_start_range, text_fragment_info))
+ } else {
+ None
+ };
+ let inline_end = inline_end_range.map(|inline_end_range| SplitInfo::new(inline_end_range, text_fragment_info));
+
+ Some((inline_start, inline_end, text_fragment_info.run.clone()))
+ }
+ }
+ }
+ }
+
+ /// Returns true if this fragment is an unscanned text fragment that consists entirely of whitespace.
+ pub fn is_whitespace_only(&self) -> bool {
+ match self.specific {
+ UnscannedTextFragment(ref text_fragment_info) => is_whitespace(text_fragment_info.text.as_slice()),
+ _ => false,
+ }
+ }
+
+ /// Assigns replaced inline-size, padding, and margins for this fragment only if it is replaced
+ /// content per CSS 2.1 § 10.3.2.
+ pub fn assign_replaced_inline_size_if_necessary(&mut self,
+ container_inline_size: Au) {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => return,
+ TableColumnFragment(_) => fail!("Table column fragments do not have inline_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ImageFragment(_) | ScannedTextFragment(_) => {}
+ };
+
+ self.compute_border_padding_margins(container_inline_size);
+
+ let style_inline_size = self.style().content_inline_size();
+ let style_block_size = self.style().content_block_size();
+ let noncontent_inline_size = self.border_padding.inline_start_end();
+
+ match self.specific {
+ ScannedTextFragment(_) => {
+ // Scanned text fragments will have already had their content inline-sizes assigned by this
+ // point.
+ self.border_box.size.inline = self.border_box.size.inline + noncontent_inline_size
+ }
+ ImageFragment(ref mut image_fragment_info) => {
+ // TODO(ksh8281): compute border,margin
+ let inline_size = ImageFragmentInfo::style_length(style_inline_size,
+ image_fragment_info.dom_inline_size,
+ container_inline_size);
+ let block_size = ImageFragmentInfo::style_length(style_block_size,
+ image_fragment_info.dom_block_size,
+ Au(0));
+
+ let inline_size = match (inline_size,block_size) {
+ (Auto, Auto) => image_fragment_info.image_inline_size(),
+ (Auto,Specified(h)) => {
+ let scale = image_fragment_info.
+ image_block_size().to_f32().unwrap() / h.to_f32().unwrap();
+ Au::new((image_fragment_info.image_inline_size().to_f32().unwrap() / scale) as i32)
+ },
+ (Specified(w), _) => w,
+ };
+
+ self.border_box.size.inline = inline_size + noncontent_inline_size;
+ image_fragment_info.computed_inline_size = Some(inline_size);
+ }
+ _ => fail!("this case should have been handled above"),
+ }
+ }
+
+ /// Assign block-size for this fragment if it is replaced content. The inline-size must have been assigned
+ /// first.
+ ///
+ /// Ideally, this should follow CSS 2.1 § 10.6.2.
+ pub fn assign_replaced_block_size_if_necessary(&mut self) {
+ match self.specific {
+ GenericFragment | IframeFragment(_) | TableFragment | TableCellFragment | TableRowFragment |
+ TableWrapperFragment => return,
+ TableColumnFragment(_) => fail!("Table column fragments do not have block_size"),
+ UnscannedTextFragment(_) => fail!("Unscanned text fragments should have been scanned by now!"),
+ ImageFragment(_) | ScannedTextFragment(_) => {}
+ }
+
+ let style_inline_size = self.style().content_inline_size();
+ let style_block_size = self.style().content_block_size();
+ let noncontent_block_size = self.border_padding.block_start_end();
+
+ match self.specific {
+ ImageFragment(ref mut image_fragment_info) => {
+ // TODO(ksh8281): compute border,margin,padding
+ let inline_size = image_fragment_info.computed_inline_size();
+ // FIXME(ksh8281): we shouldn't assign block-size this way
+ // we don't know about size of parent's block-size
+ let block_size = ImageFragmentInfo::style_length(style_block_size,
+ image_fragment_info.dom_block_size,
+ Au(0));
+
+ let block_size = match (style_inline_size, image_fragment_info.dom_inline_size, block_size) {
+ (LPA_Auto, None, Auto) => {
+ image_fragment_info.image_block_size()
+ },
+ (_,_,Auto) => {
+ let scale = image_fragment_info.image_inline_size().to_f32().unwrap()
+ / inline_size.to_f32().unwrap();
+ Au::new((image_fragment_info.image_block_size().to_f32().unwrap() / scale) as i32)
+ },
+ (_,_,Specified(h)) => {
+ h
+ }
+ };
+
+ image_fragment_info.computed_block_size = Some(block_size);
+ self.border_box.size.block = block_size + noncontent_block_size
+ }
+ ScannedTextFragment(_) => {
+ // Scanned text fragments' content block-sizes are calculated by the text run scanner
+ // during flow construction.
+ self.border_box.size.block = self.border_box.size.block + noncontent_block_size
+ }
+ _ => fail!("should have been handled above"),
+ }
+ }
+
+ /// Calculates block-size above baseline, depth below baseline, and ascent for this fragment when
+ /// used in an inline formatting context. See CSS 2.1 § 10.8.1.
+ pub fn inline_metrics(&self, layout_context: &LayoutContext) -> InlineMetrics {
+ match self.specific {
+ ImageFragment(ref image_fragment_info) => {
+ let computed_block_size = image_fragment_info.computed_block_size();
+ InlineMetrics {
+ block_size_above_baseline: computed_block_size + self.border_padding.block_start_end(),
+ depth_below_baseline: Au(0),
+ ascent: computed_block_size + self.border_padding.block_end,
+ }
+ }
+ ScannedTextFragment(ref text_fragment) => {
+ // See CSS 2.1 § 10.8.1.
+ let line_height = self.calculate_line_height(layout_context);
+ InlineMetrics::from_font_metrics(&text_fragment.run.font_metrics, line_height)
+ }
+ _ => {
+ InlineMetrics {
+ block_size_above_baseline: self.border_box.size.block,
+ depth_below_baseline: Au(0),
+ ascent: self.border_box.size.block,
+ }
+ }
+ }
+ }
+
+ /// Returns true if this fragment can merge with another adjacent fragment or false otherwise.
+ pub fn can_merge_with_fragment(&self, other: &Fragment) -> bool {
+ match (&self.specific, &other.specific) {
+ (&UnscannedTextFragment(_), &UnscannedTextFragment(_)) => {
+ // FIXME: Should probably use a whitelist of styles that can safely differ (#3165)
+ self.font_style() == other.font_style() &&
+ self.text_decoration() == other.text_decoration() &&
+ self.white_space() == other.white_space()
+ }
+ _ => false,
+ }
+ }
+
+ /// Sends the size and position of this iframe fragment to the constellation. This is out of
+ /// line to guide inlining.
+ #[inline(never)]
+ fn finalize_position_and_size_of_iframe(&self,
+ iframe_fragment: &IframeFragmentInfo,
+ offset: Point2D<Au>,
+ layout_context: &LayoutContext) {
+ let mbp = (self.margin + self.border_padding).to_physical(self.style.writing_mode);
+ let content_size = self.content_box().size.to_physical(self.style.writing_mode);
+
+ let left = offset.x + mbp.left;
+ let top = offset.y + mbp.top;
+ let width = content_size.width;
+ let height = content_size.height;
+ let origin = Point2D(geometry::to_frac_px(left) as f32, geometry::to_frac_px(top) as f32);
+ let size = Size2D(geometry::to_frac_px(width) as f32, geometry::to_frac_px(height) as f32);
+ let rect = Rect(origin, size);
+
+ debug!("finalizing position and size of iframe for {:?},{:?}",
+ iframe_fragment.pipeline_id,
+ iframe_fragment.subpage_id);
+ let msg = FrameRectMsg(iframe_fragment.pipeline_id, iframe_fragment.subpage_id, rect);
+ let ConstellationChan(ref chan) = layout_context.shared.constellation_chan;
+ chan.send(msg)
+ }
+}
+
+impl fmt::Show for Fragment {
+ /// Outputs a debugging string describing this fragment.
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ try!(write!(f, "({} ",
+ match self.specific {
+ GenericFragment => "GenericFragment",
+ IframeFragment(_) => "IframeFragment",
+ ImageFragment(_) => "ImageFragment",
+ ScannedTextFragment(_) => "ScannedTextFragment",
+ TableFragment => "TableFragment",
+ TableCellFragment => "TableCellFragment",
+ TableColumnFragment(_) => "TableColumnFragment",
+ TableRowFragment => "TableRowFragment",
+ TableWrapperFragment => "TableWrapperFragment",
+ UnscannedTextFragment(_) => "UnscannedTextFragment",
+ }));
+ try!(write!(f, "bp {}", self.border_padding));
+ try!(write!(f, " "));
+ try!(write!(f, "m {}", self.margin));
+ write!(f, ")")
+ }
+}
+
+/// An object that accumulates display lists of child flows, applying a clipping rect if necessary.
+pub struct ChildDisplayListAccumulator {
+ clip_display_item: Option<Box<ClipDisplayItem>>,
+}
+
+impl ChildDisplayListAccumulator {
+ /// Creates a `ChildDisplayListAccumulator` from the `overflow` property in the given style.
+ fn new(style: &ComputedValues, bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel)
+ -> ChildDisplayListAccumulator {
+ ChildDisplayListAccumulator {
+ clip_display_item: match style.get_box().overflow {
+ overflow::hidden | overflow::auto | overflow::scroll => {
+ Some(box ClipDisplayItem {
+ base: BaseDisplayItem::new(bounds, node, level),
+ children: DisplayList::new(),
+ })
+ },
+ overflow::visible => None,
+ }
+ }
+ }
+
+ /// Pushes the given display item onto this display list.
+ pub fn push(&mut self, parent_display_list: &mut DisplayList, item: DisplayItem) {
+ match self.clip_display_item {
+ None => parent_display_list.push(item),
+ Some(ref mut clip_display_item) => clip_display_item.children.push(item),
+ }
+ }
+
+ /// Pushes the display items from the given child onto this display list.
+ pub fn push_child(&mut self, parent_display_list: &mut DisplayList, child: &mut Flow) {
+ let kid_display_list = mem::replace(&mut flow::mut_base(child).display_list,
+ DisplayList::new());
+ match self.clip_display_item {
+ None => parent_display_list.push_all_move(kid_display_list),
+ Some(ref mut clip_display_item) => {
+ clip_display_item.children.push_all_move(kid_display_list)
+ }
+ }
+ }
+
+ /// Consumes this accumulator and pushes the clipping item, if any, onto the display list
+ /// associated with the given flow, along with the items in the given display list.
+ pub fn finish(self, parent: &mut Flow, mut display_list: DisplayList) {
+ let ChildDisplayListAccumulator {
+ clip_display_item
+ } = self;
+ match clip_display_item {
+ None => {}
+ Some(clip_display_item) => display_list.push(ClipDisplayItemClass(clip_display_item)),
+ }
+ flow::mut_base(parent).display_list = display_list
+ }
+}
diff --git a/components/layout/incremental.rs b/components/layout/incremental.rs
new file mode 100644
index 00000000000..d04c068b6aa
--- /dev/null
+++ b/components/layout/incremental.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use style::ComputedValues;
+
+bitflags! {
+ #[doc = "Individual layout actions that may be necessary after restyling."]
+ flags RestyleDamage: int {
+ #[doc = "Repaint the node itself."]
+ #[doc = "Currently unused; need to decide how this propagates."]
+ static Repaint = 0x01,
+
+ #[doc = "Recompute intrinsic inline_sizes (minimum and preferred)."]
+ #[doc = "Propagates down the flow tree because the computation is"]
+ #[doc = "bottom-up."]
+ static BubbleISizes = 0x02,
+
+ #[doc = "Recompute actual inline_sizes and block_sizes."]
+ #[doc = "Propagates up the flow tree because the computation is"]
+ #[doc = "top-down."]
+ static Reflow = 0x04
+ }
+}
+
+impl RestyleDamage {
+ /// Elements of self which should also get set on any ancestor flow.
+ pub fn propagate_up(self) -> RestyleDamage {
+ self & Reflow
+ }
+
+ /// Elements of self which should also get set on any child flows.
+ pub fn propagate_down(self) -> RestyleDamage {
+ self & BubbleISizes
+ }
+}
+
+// NB: We need the braces inside the RHS due to Rust #8012. This particular
+// version of this macro might be safe anyway, but we want to avoid silent
+// breakage on modifications.
+macro_rules! add_if_not_equal(
+ ($old:ident, $new:ident, $damage:ident,
+ [ $($effect:ident),* ], [ $($style_struct_getter:ident.$name:ident),* ]) => ({
+ if $( ($old.$style_struct_getter().$name != $new.$style_struct_getter().$name) )||* {
+ $damage.insert($($effect)|*);
+ }
+ })
+)
+
+pub fn compute_damage(old: &ComputedValues, new: &ComputedValues) -> RestyleDamage {
+ let mut damage = RestyleDamage::empty();
+
+ // This checks every CSS property, as enumerated in
+ // impl<'self> CssComputedStyle<'self>
+ // in src/support/netsurfcss/rust-netsurfcss/netsurfcss.rc.
+
+ // FIXME: We can short-circuit more of this.
+
+ add_if_not_equal!(old, new, damage, [ Repaint ],
+ [ get_color.color, get_background.background_color,
+ get_border.border_top_color, get_border.border_right_color,
+ get_border.border_bottom_color, get_border.border_left_color ]);
+
+ add_if_not_equal!(old, new, damage, [ Repaint, BubbleISizes, Reflow ],
+ [ get_border.border_top_width, get_border.border_right_width,
+ get_border.border_bottom_width, get_border.border_left_width,
+ get_margin.margin_top, get_margin.margin_right,
+ get_margin.margin_bottom, get_margin.margin_left,
+ get_padding.padding_top, get_padding.padding_right,
+ get_padding.padding_bottom, get_padding.padding_left,
+ get_box.position, get_box.width, get_box.height, get_box.float, get_box.display,
+ get_font.font_family, get_font.font_size, get_font.font_style, get_font.font_weight,
+ get_inheritedtext.text_align, get_text.text_decoration, get_inheritedbox.line_height ]);
+
+ // FIXME: test somehow that we checked every CSS property
+
+ damage
+}
diff --git a/components/layout/inline.rs b/components/layout/inline.rs
new file mode 100644
index 00000000000..6c22b25746e
--- /dev/null
+++ b/components/layout/inline.rs
@@ -0,0 +1,1170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![deny(unsafe_block)]
+
+use css::node_style::StyledNode;
+use context::LayoutContext;
+use floats::{FloatLeft, Floats, PlacementInfo};
+use flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
+use flow;
+use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, SplitInfo};
+use layout_debug;
+use model::IntrinsicISizes;
+use text;
+use wrapper::ThreadSafeLayoutNode;
+
+use collections::{Deque, RingBuf};
+use geom::Rect;
+use gfx::display_list::ContentLevel;
+use gfx::font::FontMetrics;
+use gfx::font_context::FontContext;
+use gfx::text::glyph::CharIndex;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::{LogicalRect, LogicalSize};
+use servo_util::range;
+use servo_util::range::{EachIndex, Range, RangeIndex, IntRangeIndex};
+use std::fmt;
+use std::mem;
+use std::num;
+use std::u16;
+use style::computed_values::{text_align, vertical_align, white_space};
+use style::ComputedValues;
+use sync::Arc;
+
+/// `Line`s are represented as offsets into the child list, rather than
+/// as an object that "owns" fragments. Choosing a different set of line
+/// breaks requires a new list of offsets, and possibly some splitting and
+/// merging of TextFragments.
+///
+/// A similar list will keep track of the mapping between CSS fragments and
+/// the corresponding fragments in the inline flow.
+///
+/// After line breaks are determined, render fragments in the inline flow may
+/// overlap visually. For example, in the case of nested inline CSS fragments,
+/// outer inlines must be at least as large as the inner inlines, for
+/// purposes of drawing noninherited things like backgrounds, borders,
+/// outlines.
+///
+/// N.B. roc has an alternative design where the list instead consists of
+/// things like "start outer fragment, text, start inner fragment, text, end inner
+/// fragment, text, end outer fragment, text". This seems a little complicated to
+/// serve as the starting point, but the current design doesn't make it
+/// hard to try out that alternative.
+///
+/// Line fragments also contain some metadata used during line breaking. The
+/// green zone is the area that the line can expand to before it collides
+/// with a float or a horizontal wall of the containing block. The block-start
+/// inline-start corner of the green zone is the same as that of the line, but
+/// the green zone can be taller and wider than the line itself.
+#[deriving(Encodable)]
+pub struct Line {
+ /// A range of line indices that describe line breaks.
+ ///
+ /// For example, consider the following HTML and rendered element with
+ /// linebreaks:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// ~~~text
+ /// +------------+
+ /// | I like |
+ /// | truffles, |
+ /// | +----+ |
+ /// | | | |
+ /// | +----+ yes |
+ /// | I do. |
+ /// +------------+
+ /// ~~~
+ ///
+ /// The ranges that describe these lines would be:
+ ///
+ /// | [0.0, 1.4) | [1.5, 2.0) | [2.0, 3.4) | [3.4, 4.0) |
+ /// |------------|-------------|-------------|------------|
+ /// | 'I like' | 'truffles,' | '<img> yes' | 'I do.' |
+ pub range: Range<LineIndices>,
+ /// The bounds are the exact position and extents of the line with respect
+ /// to the parent box.
+ ///
+ /// For example, for the HTML below...
+ ///
+ /// ~~~html
+ /// <div><span>I <span>like truffles, <img></span></div>
+ /// ~~~
+ ///
+ /// ...the bounds would be:
+ ///
+ /// ~~~text
+ /// +-----------------------------------------------------------+
+ /// | ^ |
+ /// | | |
+ /// | origin.y |
+ /// | | |
+ /// | v |
+ /// |< - origin.x ->+ - - - - - - - - +---------+---- |
+ /// | | | | ^ |
+ /// | | | <img> | size.block-size |
+ /// | I like truffles, | | v |
+ /// | + - - - - - - - - +---------+---- |
+ /// | | | |
+ /// | |<------ size.inline-size ------->| |
+ /// | |
+ /// | |
+ /// +-----------------------------------------------------------+
+ /// ~~~
+ pub bounds: LogicalRect<Au>,
+ /// The green zone is the greatest extent from wich a line can extend to
+ /// before it collides with a float.
+ ///
+ /// ~~~text
+ /// +-----------------------+
+ /// |::::::::::::::::: |
+ /// |:::::::::::::::::FFFFFF|
+ /// |============:::::FFFFFF|
+ /// |:::::::::::::::::FFFFFF|
+ /// |:::::::::::::::::FFFFFF|
+ /// |::::::::::::::::: |
+ /// | FFFFFFFFF |
+ /// | FFFFFFFFF |
+ /// | FFFFFFFFF |
+ /// | |
+ /// +-----------------------+
+ ///
+ /// === line
+ /// ::: green zone
+ /// FFF float
+ /// ~~~
+ pub green_zone: LogicalSize<Au>
+}
+
+int_range_index! {
+ #[deriving(Encodable)]
+ #[doc = "The index of a fragment in a flattened vector of DOM elements."]
+ struct FragmentIndex(int)
+}
+
+/// A line index consists of two indices: a fragment index that refers to the
+/// index of a DOM fragment within a flattened inline element; and a glyph index
+/// where the 0th glyph refers to the first glyph of that fragment.
+#[deriving(Clone, Encodable, PartialEq, PartialOrd, Eq, Ord, Zero)]
+pub struct LineIndices {
+ /// The index of a fragment into the flattened vector of DOM elements.
+ ///
+ /// For example, given the HTML below:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// The fragments would be indexed as follows:
+ ///
+ /// | 0 | 1 | 2 | 3 |
+ /// |------|------------------|---------|--------------|
+ /// | 'I ' | 'like truffles,' | `<img>` | ' yes I do.' |
+ pub fragment_index: FragmentIndex,
+ /// The index of a character in a DOM fragment. Continuous runs of whitespace
+ /// are treated as single characters. Non-breakable DOM fragments such as
+ /// images are treated as having a range length of `1`.
+ ///
+ /// For example, given the HTML below:
+ ///
+ /// ~~~html
+ /// <span>I <span>like truffles, <img></span> yes I do.</span>
+ /// ~~~
+ ///
+ /// The characters would be indexed as follows:
+ ///
+ /// | 0 | 1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
+ /// |---|---|---|---|---|---|---|---|---|---|---|---|----|----|----|----|----|
+ /// | I | | l | i | k | e | | t | r | u | f | f | l | e | s | , | |
+ ///
+ /// | 0 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+ /// |---------|---|---|---|---|---|---|---|---|---|---|
+ /// | `<img>` | | y | e | s | | I | | d | o | . |
+ pub char_index: CharIndex,
+}
+
+impl RangeIndex for LineIndices {}
+
+impl Add<LineIndices, LineIndices> for LineIndices {
+ fn add(&self, other: &LineIndices) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
+ "Attempted to add {} to {}. Both the fragment_index and \
+ char_index of the RHS are non-zero. This probably was a \
+ mistake!", self, other);
+ }
+ LineIndices {
+ fragment_index: self.fragment_index + other.fragment_index,
+ char_index: self.char_index + other.char_index,
+ }
+ }
+}
+
+impl Sub<LineIndices, LineIndices> for LineIndices {
+ fn sub(&self, other: &LineIndices) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(other.fragment_index == num::zero() || other.char_index == num::zero(),
+ "Attempted to subtract {} from {}. Both the fragment_index \
+ and char_index of the RHS are non-zero. This probably was \
+ a mistake!", self, other);
+ }
+ LineIndices {
+ fragment_index: self.fragment_index - other.fragment_index,
+ char_index: self.char_index - other.char_index,
+ }
+ }
+}
+
+impl Neg<LineIndices> for LineIndices {
+ fn neg(&self) -> LineIndices {
+ // TODO: use debug_assert! after rustc upgrade
+ if cfg!(not(ndebug)) {
+ assert!(self.fragment_index == num::zero() || self.char_index == num::zero(),
+ "Attempted to negate {}. Both the fragment_index and \
+ char_index are non-zero. This probably was a mistake!",
+ self);
+ }
+ LineIndices {
+ fragment_index: -self.fragment_index,
+ char_index: -self.char_index,
+ }
+ }
+}
+
+impl fmt::Show for LineIndices {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}.{}", self.fragment_index, self.char_index)
+ }
+}
+
+pub fn each_fragment_index(range: &Range<LineIndices>) -> EachIndex<int, FragmentIndex> {
+ range::each_index(range.begin().fragment_index, range.end().fragment_index)
+}
+
+pub fn each_char_index(range: &Range<LineIndices>) -> EachIndex<int, CharIndex> {
+ range::each_index(range.begin().char_index, range.end().char_index)
+}
+
+struct LineBreaker {
+ pub floats: Floats,
+ pub new_fragments: Vec<Fragment>,
+ pub work_list: RingBuf<Fragment>,
+ pub pending_line: Line,
+ pub lines: Vec<Line>,
+ pub cur_b: Au, // Current position on the block direction
+}
+
+impl LineBreaker {
+ pub fn new(float_ctx: Floats) -> LineBreaker {
+ LineBreaker {
+ new_fragments: Vec::new(),
+ work_list: RingBuf::new(),
+ pending_line: Line {
+ range: Range::empty(),
+ bounds: LogicalRect::zero(float_ctx.writing_mode),
+ green_zone: LogicalSize::zero(float_ctx.writing_mode)
+ },
+ floats: float_ctx,
+ lines: Vec::new(),
+ cur_b: Au::new(0)
+ }
+ }
+
+ pub fn floats(&mut self) -> Floats {
+ self.floats.clone()
+ }
+
+ fn reset_scanner(&mut self) {
+ debug!("Resetting LineBreaker's state for flow.");
+ self.lines = Vec::new();
+ self.new_fragments = Vec::new();
+ self.cur_b = Au(0);
+ self.reset_line();
+ }
+
+ fn reset_line(&mut self) {
+ self.pending_line.range.reset(num::zero(), num::zero());
+ self.pending_line.bounds = LogicalRect::new(
+ self.floats.writing_mode, Au::new(0), self.cur_b, Au::new(0), Au::new(0));
+ self.pending_line.green_zone = LogicalSize::zero(self.floats.writing_mode)
+ }
+
+ pub fn scan_for_lines(&mut self, flow: &mut InlineFlow, layout_context: &LayoutContext) {
+ self.reset_scanner();
+
+ let mut old_fragments = mem::replace(&mut flow.fragments, InlineFragments::new());
+
+ { // Enter a new scope so that old_fragment_iter's borrow is released
+ let mut old_fragment_iter = old_fragments.fragments.iter();
+ loop {
+ // acquire the next fragment to lay out from work list or fragment list
+ let cur_fragment = if self.work_list.is_empty() {
+ match old_fragment_iter.next() {
+ None => break,
+ Some(fragment) => {
+ debug!("LineBreaker: Working with fragment from flow: b{}",
+ fragment.debug_id());
+ (*fragment).clone()
+ }
+ }
+ } else {
+ let fragment = self.work_list.pop_front().unwrap();
+ debug!("LineBreaker: Working with fragment from work list: b{}",
+ fragment.debug_id());
+ fragment
+ };
+
+ let fragment_was_appended = match cur_fragment.white_space() {
+ white_space::normal => self.try_append_to_line(cur_fragment, flow, layout_context),
+ white_space::pre => self.try_append_to_line_by_new_line(cur_fragment),
+ };
+
+ if !fragment_was_appended {
+ debug!("LineBreaker: Fragment wasn't appended, because line {:u} was full.",
+ self.lines.len());
+ self.flush_current_line();
+ } else {
+ debug!("LineBreaker: appended a fragment to line {:u}", self.lines.len());
+ }
+ }
+
+ if self.pending_line.range.length() > num::zero() {
+ debug!("LineBreaker: Partially full line {:u} inline_start at end of scanning.",
+ self.lines.len());
+ self.flush_current_line();
+ }
+ }
+
+ old_fragments.fragments = mem::replace(&mut self.new_fragments, vec![]);
+ flow.fragments = old_fragments;
+ flow.lines = mem::replace(&mut self.lines, Vec::new());
+ }
+
+ fn flush_current_line(&mut self) {
+ debug!("LineBreaker: Flushing line {:u}: {:?}",
+ self.lines.len(), self.pending_line);
+
+ // clear line and add line mapping
+ debug!("LineBreaker: Saving information for flushed line {:u}.", self.lines.len());
+ self.lines.push(self.pending_line);
+ self.cur_b = self.pending_line.bounds.start.b + self.pending_line.bounds.size.block;
+ self.reset_line();
+ }
+
+ // FIXME(eatkinson): this assumes that the tallest fragment in the line determines the line block-size
+ // This might not be the case with some weird text fonts.
+ fn new_block_size_for_line(&self, new_fragment: &Fragment, layout_context: &LayoutContext) -> Au {
+ let fragment_block_size = new_fragment.content_block_size(layout_context);
+ if fragment_block_size > self.pending_line.bounds.size.block {
+ fragment_block_size
+ } else {
+ self.pending_line.bounds.size.block
+ }
+ }
+
+ /// Computes the position of a line that has only the provided fragment. Returns the bounding
+ /// rect of the line's green zone (whose origin coincides with the line's origin) and the actual
+ /// inline-size of the first fragment after splitting.
+ fn initial_line_placement(&self, first_fragment: &Fragment, ceiling: Au, flow: &InlineFlow)
+ -> (LogicalRect<Au>, Au) {
+ debug!("LineBreaker: Trying to place first fragment of line {}", self.lines.len());
+
+ let first_fragment_size = first_fragment.border_box.size;
+ let splittable = first_fragment.can_split();
+ debug!("LineBreaker: fragment size: {}, splittable: {}", first_fragment_size, splittable);
+
+ // Initally, pretend a splittable fragment has 0 inline-size.
+ // We will move it later if it has nonzero inline-size
+ // and that causes problems.
+ let placement_inline_size = if splittable {
+ Au::new(0)
+ } else {
+ first_fragment_size.inline
+ };
+
+ let info = PlacementInfo {
+ size: LogicalSize::new(
+ self.floats.writing_mode, placement_inline_size, first_fragment_size.block),
+ ceiling: ceiling,
+ max_inline_size: flow.base.position.size.inline,
+ kind: FloatLeft,
+ };
+
+ let line_bounds = self.floats.place_between_floats(&info);
+
+ debug!("LineBreaker: found position for line: {} using placement_info: {:?}",
+ line_bounds,
+ info);
+
+ // Simple case: if the fragment fits, then we can stop here
+ if line_bounds.size.inline > first_fragment_size.inline {
+ debug!("LineBreaker: case=fragment fits");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ // If not, but we can't split the fragment, then we'll place
+ // the line here and it will overflow.
+ if !splittable {
+ debug!("LineBreaker: case=line doesn't fit, but is unsplittable");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ debug!("LineBreaker: used to call split_to_inline_size here");
+ return (line_bounds, first_fragment_size.inline);
+ }
+
+ /// Performs float collision avoidance. This is called when adding a fragment is going to increase
+ /// the block-size, and because of that we will collide with some floats.
+ ///
+ /// We have two options here:
+ /// 1) Move the entire line so that it doesn't collide any more.
+ /// 2) Break the line and put the new fragment on the next line.
+ ///
+ /// The problem with option 1 is that we might move the line and then wind up breaking anyway,
+ /// which violates the standard.
+ /// But option 2 is going to look weird sometimes.
+ ///
+ /// So we'll try to move the line whenever we can, but break if we have to.
+ ///
+ /// Returns false if and only if we should break the line.
+ fn avoid_floats(&mut self,
+ in_fragment: Fragment,
+ flow: &InlineFlow,
+ new_block_size: Au,
+ line_is_empty: bool)
+ -> bool {
+ debug!("LineBreaker: entering float collision avoider!");
+
+ // First predict where the next line is going to be.
+ let this_line_y = self.pending_line.bounds.start.b;
+ let (next_line, first_fragment_inline_size) = self.initial_line_placement(&in_fragment, this_line_y, flow);
+ let next_green_zone = next_line.size;
+
+ let new_inline_size = self.pending_line.bounds.size.inline + first_fragment_inline_size;
+
+ // Now, see if everything can fit at the new location.
+ if next_green_zone.inline >= new_inline_size && next_green_zone.block >= new_block_size {
+ debug!("LineBreaker: case=adding fragment collides vertically with floats: moving line");
+
+ self.pending_line.bounds.start = next_line.start;
+ self.pending_line.green_zone = next_green_zone;
+
+ assert!(!line_is_empty, "Non-terminating line breaking");
+ self.work_list.push_front(in_fragment);
+ return true
+ }
+
+ debug!("LineBreaker: case=adding fragment collides vertically with floats: breaking line");
+ self.work_list.push_front(in_fragment);
+ false
+ }
+
+ fn try_append_to_line_by_new_line(&mut self, in_fragment: Fragment) -> bool {
+ if in_fragment.new_line_pos.len() == 0 {
+ debug!("LineBreaker: Did not find a new-line character, so pushing the fragment to \
+ the line without splitting.");
+ self.push_fragment_to_line(in_fragment);
+ true
+ } else {
+ debug!("LineBreaker: Found a new-line character, so splitting theline.");
+
+ let (inline_start, inline_end, run) = in_fragment.find_split_info_by_new_line()
+ .expect("LineBreaker: This split case makes no sense!");
+ let writing_mode = self.floats.writing_mode;
+
+ // TODO(bjz): Remove fragment splitting
+ let split_fragment = |split: SplitInfo| {
+ let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
+ let specific = ScannedTextFragment(info);
+ let size = LogicalSize::new(
+ writing_mode, split.inline_size, in_fragment.border_box.size.block);
+ in_fragment.transform(size, specific)
+ };
+
+ debug!("LineBreaker: Pushing the fragment to the inline_start of the new-line character \
+ to the line.");
+ let mut inline_start = split_fragment(inline_start);
+ inline_start.new_line_pos = vec![];
+ self.push_fragment_to_line(inline_start);
+
+ for inline_end in inline_end.move_iter() {
+ debug!("LineBreaker: Deferring the fragment to the inline_end of the new-line \
+ character to the line.");
+ let mut inline_end = split_fragment(inline_end);
+ inline_end.new_line_pos = in_fragment.new_line_pos.clone();
+ self.work_list.push_front(inline_end);
+ }
+ false
+ }
+ }
+
+ /// Tries to append the given fragment to the line, splitting it if necessary. Returns false only if
+ /// we should break the line.
+ fn try_append_to_line(&mut self, in_fragment: Fragment, flow: &InlineFlow, layout_context: &LayoutContext) -> bool {
+ let line_is_empty = self.pending_line.range.length() == num::zero();
+ if line_is_empty {
+ let (line_bounds, _) = self.initial_line_placement(&in_fragment, self.cur_b, flow);
+ self.pending_line.bounds.start = line_bounds.start;
+ self.pending_line.green_zone = line_bounds.size;
+ }
+
+ debug!("LineBreaker: Trying to append fragment to line {:u} (fragment size: {}, green zone: \
+ {}): {}",
+ self.lines.len(),
+ in_fragment.border_box.size,
+ self.pending_line.green_zone,
+ in_fragment);
+
+ let green_zone = self.pending_line.green_zone;
+
+ // NB: At this point, if `green_zone.inline-size < self.pending_line.bounds.size.inline-size` or
+ // `green_zone.block-size < self.pending_line.bounds.size.block-size`, then we committed a line
+ // that overlaps with floats.
+
+ let new_block_size = self.new_block_size_for_line(&in_fragment, layout_context);
+ if new_block_size > green_zone.block {
+ // Uh-oh. Float collision imminent. Enter the float collision avoider
+ return self.avoid_floats(in_fragment, flow, new_block_size, line_is_empty)
+ }
+
+ // If we're not going to overflow the green zone vertically, we might still do so
+ // horizontally. We'll try to place the whole fragment on this line and break somewhere if it
+ // doesn't fit.
+
+ let new_inline_size = self.pending_line.bounds.size.inline + in_fragment.border_box.size.inline;
+ if new_inline_size <= green_zone.inline {
+ debug!("LineBreaker: case=fragment fits without splitting");
+ self.push_fragment_to_line(in_fragment);
+ return true
+ }
+
+ if !in_fragment.can_split() {
+ // TODO(eatkinson, issue #224): Signal that horizontal overflow happened?
+ if line_is_empty {
+ debug!("LineBreaker: case=fragment can't split and line {:u} is empty, so \
+ overflowing.",
+ self.lines.len());
+ self.push_fragment_to_line(in_fragment);
+ return true
+ }
+ }
+
+ let available_inline_size = green_zone.inline - self.pending_line.bounds.size.inline;
+ let split = in_fragment.find_split_info_for_inline_size(CharIndex(0), available_inline_size, line_is_empty);
+ match split.map(|(inline_start, inline_end, run)| {
+ // TODO(bjz): Remove fragment splitting
+ let split_fragment = |split: SplitInfo| {
+ let info = ScannedTextFragmentInfo::new(run.clone(), split.range);
+ let specific = ScannedTextFragment(info);
+ let size = LogicalSize::new(
+ self.floats.writing_mode, split.inline_size, in_fragment.border_box.size.block);
+ in_fragment.transform(size, specific)
+ };
+
+ (inline_start.map(|x| { debug!("LineBreaker: Left split {}", x); split_fragment(x) }),
+ inline_end.map(|x| { debug!("LineBreaker: Right split {}", x); split_fragment(x) }))
+ }) {
+ None => {
+ debug!("LineBreaker: Tried to split unsplittable render fragment! Deferring to next \
+ line. {}", in_fragment);
+ self.work_list.push_front(in_fragment);
+ false
+ },
+ Some((Some(inline_start_fragment), Some(inline_end_fragment))) => {
+ debug!("LineBreaker: Line break found! Pushing inline_start fragment to line and deferring \
+ inline_end fragment to next line.");
+ self.push_fragment_to_line(inline_start_fragment);
+ self.work_list.push_front(inline_end_fragment);
+ true
+ },
+ Some((Some(inline_start_fragment), None)) => {
+ debug!("LineBreaker: Pushing inline_start fragment to line.");
+ self.push_fragment_to_line(inline_start_fragment);
+ true
+ },
+ Some((None, Some(inline_end_fragment))) => {
+ debug!("LineBreaker: Pushing inline_end fragment to line.");
+ self.push_fragment_to_line(inline_end_fragment);
+ true
+ },
+ Some((None, None)) => {
+ error!("LineBreaker: This split case makes no sense!");
+ true
+ },
+ }
+ }
+
+ // An unconditional push
+ fn push_fragment_to_line(&mut self, fragment: Fragment) {
+ debug!("LineBreaker: Pushing fragment {} to line {:u}", fragment.debug_id(), self.lines.len());
+
+ if self.pending_line.range.length() == num::zero() {
+ assert!(self.new_fragments.len() <= (u16::MAX as uint));
+ self.pending_line.range.reset(
+ LineIndices {
+ fragment_index: FragmentIndex(self.new_fragments.len() as int),
+ char_index: CharIndex(0) /* unused for now */,
+ },
+ num::zero()
+ );
+ }
+ self.pending_line.range.extend_by(LineIndices {
+ fragment_index: FragmentIndex(1),
+ char_index: CharIndex(0) /* unused for now */ ,
+ });
+ self.pending_line.bounds.size.inline = self.pending_line.bounds.size.inline +
+ fragment.border_box.size.inline;
+ self.pending_line.bounds.size.block = Au::max(self.pending_line.bounds.size.block,
+ fragment.border_box.size.block);
+ self.new_fragments.push(fragment);
+ }
+}
+
+/// Represents a list of inline fragments, including element ranges.
+#[deriving(Encodable)]
+pub struct InlineFragments {
+ /// The fragments themselves.
+ pub fragments: Vec<Fragment>,
+}
+
+impl InlineFragments {
+ /// Creates an empty set of inline fragments.
+ pub fn new() -> InlineFragments {
+ InlineFragments {
+ fragments: vec![],
+ }
+ }
+
+ /// Returns the number of inline fragments.
+ pub fn len(&self) -> uint {
+ self.fragments.len()
+ }
+
+ /// Returns true if this list contains no fragments and false if it contains at least one fragment.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Pushes a new inline fragment.
+ pub fn push(&mut self, fragment: &mut Fragment, style: Arc<ComputedValues>) {
+ fragment.add_inline_context_style(style);
+ self.fragments.push(fragment.clone());
+ }
+
+ /// Merges another set of inline fragments with this one.
+ pub fn push_all(&mut self, fragments: InlineFragments) {
+ self.fragments.push_all_move(fragments.fragments);
+ }
+
+ /// A convenience function to return the fragment at a given index.
+ pub fn get<'a>(&'a self, index: uint) -> &'a Fragment {
+ &self.fragments[index]
+ }
+
+ /// A convenience function to return a mutable reference to the fragment at a given index.
+ pub fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut Fragment {
+ self.fragments.get_mut(index)
+ }
+
+ /// Strips ignorable whitespace from the start of a list of fragments.
+ pub fn strip_ignorable_whitespace_from_start(&mut self) {
+ if self.is_empty() { return }; // Fast path
+
+ // FIXME (rust#16151): This can be reverted back to using skip_while once
+ // the upstream bug is fixed.
+ let mut fragments = mem::replace(&mut self.fragments, vec![]).move_iter();
+ let mut new_fragments = Vec::new();
+ let mut skipping = true;
+ for fragment in fragments {
+ if skipping && fragment.is_whitespace_only() {
+ debug!("stripping ignorable whitespace from start");
+ continue
+ }
+
+ skipping = false;
+ new_fragments.push(fragment);
+ }
+
+ self.fragments = new_fragments;
+ }
+
+ /// Strips ignorable whitespace from the end of a list of fragments.
+ pub fn strip_ignorable_whitespace_from_end(&mut self) {
+ if self.is_empty() {
+ return;
+ }
+
+ let mut new_fragments = self.fragments.clone();
+ while new_fragments.len() > 0 && new_fragments.as_slice().last().get_ref().is_whitespace_only() {
+ debug!("stripping ignorable whitespace from end");
+ drop(new_fragments.pop());
+ }
+
+
+ self.fragments = new_fragments;
+ }
+}
+
+/// Flows for inline layout.
+#[deriving(Encodable)]
+pub struct InlineFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// A vector of all inline fragments. Several fragments may correspond to one node/element.
+ pub fragments: InlineFragments,
+
+ /// A vector of ranges into fragments that represents line positions. These ranges are disjoint and
+ /// are the result of inline layout. This also includes some metadata used for positioning
+ /// lines.
+ pub lines: Vec<Line>,
+
+ /// The minimum block-size above the baseline for each line, as specified by the line block-size and
+ /// font style.
+ pub minimum_block_size_above_baseline: Au,
+
+ /// The minimum depth below the baseline for each line, as specified by the line block-size and
+ /// font style.
+ pub minimum_depth_below_baseline: Au,
+}
+
+impl InlineFlow {
+ pub fn from_fragments(node: ThreadSafeLayoutNode, fragments: InlineFragments) -> InlineFlow {
+ InlineFlow {
+ base: BaseFlow::new(node),
+ fragments: fragments,
+ lines: Vec::new(),
+ minimum_block_size_above_baseline: Au(0),
+ minimum_depth_below_baseline: Au(0),
+ }
+ }
+
+ pub fn build_display_list_inline(&mut self, layout_context: &LayoutContext) {
+ let size = self.base.position.size.to_physical(self.base.writing_mode);
+ if !Rect(self.base.abs_position, size).intersects(&layout_context.shared.dirty) {
+ return
+ }
+
+ // TODO(#228): Once we form lines and have their cached bounds, we can be smarter and
+ // not recurse on a line if nothing in it can intersect the dirty region.
+ debug!("Flow: building display list for {:u} inline fragments", self.fragments.len());
+
+ for fragment in self.fragments.fragments.mut_iter() {
+ let rel_offset = fragment.relative_position(&self.base
+ .absolute_position_info
+ .relative_containing_block_size);
+ drop(fragment.build_display_list(&mut self.base.display_list,
+ layout_context,
+ self.base.abs_position.add_size(
+ &rel_offset.to_physical(self.base.writing_mode)),
+ ContentLevel));
+ }
+
+ // TODO(#225): Should `inline-block` elements have flows as children of the inline flow or
+ // should the flow be nested inside the fragment somehow?
+
+ // For now, don't traverse the subtree rooted here.
+ }
+
+ /// Returns the distance from the baseline for the logical block-start inline-start corner of this fragment,
+ /// taking into account the value of the CSS `vertical-align` property. Negative values mean
+ /// "toward the logical block-start" and positive values mean "toward the logical block-end".
+ ///
+ /// The extra boolean is set if and only if `biggest_block-start` and/or `biggest_block-end` were updated.
+ /// That is, if the box has a `block-start` or `block-end` value, true is returned.
+ fn distance_from_baseline(fragment: &Fragment,
+ ascent: Au,
+ parent_text_block_start: Au,
+ parent_text_block_end: Au,
+ block_size_above_baseline: &mut Au,
+ depth_below_baseline: &mut Au,
+ largest_block_size_for_top_fragments: &mut Au,
+ largest_block_size_for_bottom_fragments: &mut Au,
+ layout_context: &LayoutContext)
+ -> (Au, bool) {
+ match fragment.vertical_align() {
+ vertical_align::baseline => (-ascent, false),
+ vertical_align::middle => {
+ // TODO: x-block-size value should be used from font info.
+ let xblock_size = Au(0);
+ let fragment_block_size = fragment.content_block_size(layout_context);
+ let offset_block_start = -(xblock_size + fragment_block_size).scale_by(0.5);
+ *block_size_above_baseline = offset_block_start.scale_by(-1.0);
+ *depth_below_baseline = fragment_block_size - *block_size_above_baseline;
+ (offset_block_start, false)
+ },
+ vertical_align::sub => {
+ // TODO: The proper position for subscripts should be used. Lower the baseline to
+ // the proper position for subscripts.
+ let sub_offset = Au(0);
+ (sub_offset - ascent, false)
+ },
+ vertical_align::super_ => {
+ // TODO: The proper position for superscripts should be used. Raise the baseline to
+ // the proper position for superscripts.
+ let super_offset = Au(0);
+ (-super_offset - ascent, false)
+ },
+ vertical_align::text_top => {
+ let fragment_block_size = *block_size_above_baseline + *depth_below_baseline;
+ let prev_depth_below_baseline = *depth_below_baseline;
+ *block_size_above_baseline = parent_text_block_start;
+ *depth_below_baseline = fragment_block_size - *block_size_above_baseline;
+ (*depth_below_baseline - prev_depth_below_baseline - ascent, false)
+ },
+ vertical_align::text_bottom => {
+ let fragment_block_size = *block_size_above_baseline + *depth_below_baseline;
+ let prev_depth_below_baseline = *depth_below_baseline;
+ *depth_below_baseline = parent_text_block_end;
+ *block_size_above_baseline = fragment_block_size - *depth_below_baseline;
+ (*depth_below_baseline - prev_depth_below_baseline - ascent, false)
+ },
+ vertical_align::top => {
+ *largest_block_size_for_top_fragments =
+ Au::max(*largest_block_size_for_top_fragments,
+ *block_size_above_baseline + *depth_below_baseline);
+ let offset_top = *block_size_above_baseline - ascent;
+ (offset_top, true)
+ },
+ vertical_align::bottom => {
+ *largest_block_size_for_bottom_fragments =
+ Au::max(*largest_block_size_for_bottom_fragments,
+ *block_size_above_baseline + *depth_below_baseline);
+ let offset_bottom = -(*depth_below_baseline + ascent);
+ (offset_bottom, true)
+ },
+ vertical_align::Length(length) => (-(length + ascent), false),
+ vertical_align::Percentage(p) => {
+ let line_height = fragment.calculate_line_height(layout_context);
+ let percent_offset = line_height.scale_by(p);
+ (-(percent_offset + ascent), false)
+ }
+ }
+ }
+
+ /// Sets fragment X positions based on alignment for one line.
+ fn set_horizontal_fragment_positions(fragments: &mut InlineFragments,
+ line: &Line,
+ line_align: text_align::T) {
+ // Figure out how much inline-size we have.
+ let slack_inline_size = Au::max(Au(0), line.green_zone.inline - line.bounds.size.inline);
+
+ // Set the fragment x positions based on that alignment.
+ let mut offset_x = line.bounds.start.i;
+ offset_x = offset_x + match line_align {
+ // So sorry, but justified text is more complicated than shuffling line
+ // coordinates.
+ //
+ // TODO(burg, issue #213): Implement `text-align: justify`.
+ text_align::left | text_align::justify => Au(0),
+ text_align::center => slack_inline_size.scale_by(0.5),
+ text_align::right => slack_inline_size,
+ };
+
+ for i in each_fragment_index(&line.range) {
+ let fragment = fragments.get_mut(i.to_uint());
+ let size = fragment.border_box.size;
+ fragment.border_box = LogicalRect::new(
+ fragment.style.writing_mode, offset_x, fragment.border_box.start.b,
+ size.inline, size.block);
+ offset_x = offset_x + size.inline;
+ }
+ }
+
+ /// Computes the minimum ascent and descent for each line. This is done during flow
+ /// construction.
+ ///
+ /// `style` is the style of the block.
+ pub fn compute_minimum_ascent_and_descent(&self,
+ font_context: &mut FontContext,
+ style: &ComputedValues) -> (Au, Au) {
+ let font_style = text::computed_style_to_font_style(style);
+ let font_metrics = text::font_metrics_for_style(font_context, &font_style);
+ let line_height = text::line_height_from_style(style, &font_metrics);
+ let inline_metrics = InlineMetrics::from_font_metrics(&font_metrics, line_height);
+ (inline_metrics.block_size_above_baseline, inline_metrics.depth_below_baseline)
+ }
+}
+
+impl Flow for InlineFlow {
+ fn class(&self) -> FlowClass {
+ InlineFlowClass
+ }
+
+ fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
+ self
+ }
+
+ fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::bubble_inline_sizes {:s}", self.base.debug_id());
+
+ let writing_mode = self.base.writing_mode;
+ for kid in self.base.child_iter() {
+ flow::mut_base(kid).floats = Floats::new(writing_mode);
+ }
+
+ let mut intrinsic_inline_sizes = IntrinsicISizes::new();
+ for fragment in self.fragments.fragments.mut_iter() {
+ debug!("Flow: measuring {}", *fragment);
+
+ let fragment_intrinsic_inline_sizes =
+ fragment.intrinsic_inline_sizes();
+ intrinsic_inline_sizes.minimum_inline_size = geometry::max(
+ intrinsic_inline_sizes.minimum_inline_size,
+ fragment_intrinsic_inline_sizes.minimum_inline_size);
+ intrinsic_inline_sizes.preferred_inline_size =
+ intrinsic_inline_sizes.preferred_inline_size +
+ fragment_intrinsic_inline_sizes.preferred_inline_size;
+ }
+
+ self.base.intrinsic_inline_sizes = intrinsic_inline_sizes;
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
+ /// on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, _: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::assign_inline_sizes {:s}", self.base.debug_id());
+
+ // Initialize content fragment inline-sizes if they haven't been initialized already.
+ //
+ // TODO: Combine this with `LineBreaker`'s walk in the fragment list, or put this into `Fragment`.
+
+ debug!("InlineFlow::assign_inline_sizes: floats in: {:?}", self.base.floats);
+
+ {
+ let inline_size = self.base.position.size.inline;
+ let this = &mut *self;
+ for fragment in this.fragments.fragments.mut_iter() {
+ fragment.assign_replaced_inline_size_if_necessary(inline_size);
+ }
+ }
+
+ assert!(self.base.children.len() == 0,
+ "InlineFlow: should not have children flows in the current layout implementation.");
+
+ // There are no child contexts, so stop here.
+
+ // TODO(Issue #225): once there are 'inline-block' elements, this won't be
+ // true. In that case, set the InlineBlockFragment's inline-size to the
+ // shrink-to-fit inline-size, perform inline flow, and set the block
+ // flow context's inline-size as the assigned inline-size of the
+ // 'inline-block' fragment that created this flow before recursing.
+ }
+
+ /// Calculate and set the block-size of this flow. See CSS 2.1 § 10.6.1.
+ fn assign_block_size(&mut self, ctx: &LayoutContext) {
+ let _scope = layout_debug_scope!("inline::assign_block_size {:s}", self.base.debug_id());
+
+ // Divide the fragments into lines.
+ //
+ // TODO(#226): Get the CSS `line-block-size` property from the containing block's style to
+ // determine minimum line block-size.
+ //
+ // TODO(#226): Get the CSS `line-block-size` property from each non-replaced inline element to
+ // determine its block-size for computing line block-size.
+ //
+ // TODO(pcwalton): Cache the line scanner?
+ debug!("assign_block_size_inline: floats in: {:?}", self.base.floats);
+
+ // assign block-size for inline fragments
+ for fragment in self.fragments.fragments.mut_iter() {
+ fragment.assign_replaced_block_size_if_necessary();
+ }
+
+ let scanner_floats = self.base.floats.clone();
+ let mut scanner = LineBreaker::new(scanner_floats);
+ scanner.scan_for_lines(self, ctx);
+
+ // All lines use text alignment of the flow.
+ let text_align = self.base.flags.text_align();
+
+ // Now, go through each line and lay out the fragments inside.
+ let mut line_distance_from_flow_block_start = Au(0);
+ for line in self.lines.mut_iter() {
+ // Lay out fragments horizontally.
+ InlineFlow::set_horizontal_fragment_positions(&mut self.fragments, line, text_align);
+
+ // Set the block-start y position of the current line.
+ // `line_height_offset` is updated at the end of the previous loop.
+ line.bounds.start.b = line_distance_from_flow_block_start;
+
+ // Calculate the distance from the baseline to the block-start and block-end of the line.
+ let mut largest_block_size_above_baseline = self.minimum_block_size_above_baseline;
+ let mut largest_depth_below_baseline = self.minimum_depth_below_baseline;
+
+ // Calculate the largest block-size among fragments with 'top' and 'bottom' values
+ // respectively.
+ let (mut largest_block_size_for_top_fragments, mut largest_block_size_for_bottom_fragments) =
+ (Au(0), Au(0));
+
+ for fragment_i in each_fragment_index(&line.range) {
+ let fragment = self.fragments.fragments.get_mut(fragment_i.to_uint());
+
+ let InlineMetrics {
+ block_size_above_baseline: mut block_size_above_baseline,
+ depth_below_baseline: mut depth_below_baseline,
+ ascent
+ } = fragment.inline_metrics(ctx);
+
+ // To calculate text-top and text-bottom value when `vertical-align` is involved,
+ // we should find the top and bottom of the content area of the parent fragment.
+ // "Content area" is defined in CSS 2.1 § 10.6.1.
+ //
+ // TODO: We should extract em-box info from the font size of the parent and
+ // calculate the distances from the baseline to the block-start and the block-end of the
+ // parent's content area.
+
+ // We should calculate the distance from baseline to the top of parent's content
+ // area. But for now we assume it's the font size.
+ //
+ // CSS 2.1 does not state which font to use. Previous versions of the code used
+ // the parent's font; this code uses the current font.
+ let parent_text_top = fragment.style().get_font().font_size;
+
+ // We should calculate the distance from baseline to the bottom of the parent's
+ // content area. But for now we assume it's zero.
+ let parent_text_bottom = Au(0);
+
+ // Calculate the final block-size above the baseline for this fragment.
+ //
+ // The no-update flag decides whether `largest_block-size_for_top_fragments` and
+ // `largest_block-size_for_bottom_fragments` are to be updated or not. This will be set
+ // if and only if the fragment has `vertical-align` set to `top` or `bottom`.
+ let (distance_from_baseline, no_update_flag) =
+ InlineFlow::distance_from_baseline(
+ fragment,
+ ascent,
+ parent_text_top,
+ parent_text_bottom,
+ &mut block_size_above_baseline,
+ &mut depth_below_baseline,
+ &mut largest_block_size_for_top_fragments,
+ &mut largest_block_size_for_bottom_fragments,
+ ctx);
+
+ // Unless the current fragment has `vertical-align` set to `top` or `bottom`,
+ // `largest_block-size_above_baseline` and `largest_depth_below_baseline` are updated.
+ if !no_update_flag {
+ largest_block_size_above_baseline = Au::max(block_size_above_baseline,
+ largest_block_size_above_baseline);
+ largest_depth_below_baseline = Au::max(depth_below_baseline,
+ largest_depth_below_baseline);
+ }
+
+ // Temporarily use `fragment.border_box.start.b` to mean "the distance from the
+ // baseline". We will assign the real value later.
+ fragment.border_box.start.b = distance_from_baseline
+ }
+
+ // Calculate the distance from the baseline to the top of the largest fragment with a
+ // value for `bottom`. Then, if necessary, update `largest_block-size_above_baseline`.
+ largest_block_size_above_baseline =
+ Au::max(largest_block_size_above_baseline,
+ largest_block_size_for_bottom_fragments - largest_depth_below_baseline);
+
+ // Calculate the distance from baseline to the bottom of the largest fragment with a value
+ // for `top`. Then, if necessary, update `largest_depth_below_baseline`.
+ largest_depth_below_baseline =
+ Au::max(largest_depth_below_baseline,
+ largest_block_size_for_top_fragments - largest_block_size_above_baseline);
+
+ // Now, the distance from the logical block-start of the line to the baseline can be
+ // computed as `largest_block-size_above_baseline`.
+ let baseline_distance_from_block_start = largest_block_size_above_baseline;
+
+ // Compute the final positions in the block direction of each fragment. Recall that
+ // `fragment.border_box.start.b` was set to the distance from the baseline above.
+ for fragment_i in each_fragment_index(&line.range) {
+ let fragment = self.fragments.get_mut(fragment_i.to_uint());
+ match fragment.vertical_align() {
+ vertical_align::top => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start
+ }
+ vertical_align::bottom => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start + baseline_distance_from_block_start +
+ largest_depth_below_baseline
+ }
+ _ => {
+ fragment.border_box.start.b = fragment.border_box.start.b +
+ line_distance_from_flow_block_start + baseline_distance_from_block_start
+ }
+ }
+ }
+
+ // This is used to set the block-start y position of the next line in the next loop.
+ line.bounds.size.block = largest_block_size_above_baseline + largest_depth_below_baseline;
+ line_distance_from_flow_block_start = line_distance_from_flow_block_start + line.bounds.size.block;
+ } // End of `lines.each` loop.
+
+ self.base.position.size.block = match self.lines.as_slice().last() {
+ Some(ref last_line) => last_line.bounds.start.b + last_line.bounds.size.block,
+ None => Au::new(0)
+ };
+
+ self.base.floats = scanner.floats();
+ self.base.floats.translate(LogicalSize::new(
+ self.base.writing_mode, Au::new(0), -self.base.position.size.block));
+ }
+}
+
+impl fmt::Show for InlineFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ try!(write!(f, "InlineFlow"));
+ for (i, fragment) in self.fragments.fragments.iter().enumerate() {
+ if i == 0 {
+ try!(write!(f, ": {}", fragment))
+ } else {
+ try!(write!(f, ", {}", fragment))
+ }
+ }
+ Ok(())
+ }
+}
+
+#[deriving(Clone)]
+pub struct InlineFragmentContext {
+ pub styles: Vec<Arc<ComputedValues>>,
+}
+
+impl InlineFragmentContext {
+ pub fn new() -> InlineFragmentContext {
+ InlineFragmentContext {
+ styles: vec!()
+ }
+ }
+}
+
+/// BSize above the baseline, depth below the baseline, and ascent for a fragment. See CSS 2.1 §
+/// 10.8.1.
+pub struct InlineMetrics {
+ pub block_size_above_baseline: Au,
+ pub depth_below_baseline: Au,
+ pub ascent: Au,
+}
+
+impl InlineMetrics {
+ /// Calculates inline metrics from font metrics and line block-size per CSS 2.1 § 10.8.1.
+ #[inline]
+ pub fn from_font_metrics(font_metrics: &FontMetrics, line_height: Au) -> InlineMetrics {
+ let leading = line_height - (font_metrics.ascent + font_metrics.descent);
+ InlineMetrics {
+ block_size_above_baseline: font_metrics.ascent + leading.scale_by(0.5),
+ depth_below_baseline: font_metrics.descent + leading.scale_by(0.5),
+ ascent: font_metrics.ascent,
+ }
+ }
+}
+
diff --git a/components/layout/layout_debug.rs b/components/layout/layout_debug.rs
new file mode 100644
index 00000000000..58db599c9e2
--- /dev/null
+++ b/components/layout/layout_debug.rs
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Supports writing a trace file created during each layout scope
+//! that can be viewed by an external tool to make layout debugging easier.
+
+#![macro_escape]
+
+use flow_ref::FlowRef;
+use serialize::json;
+use std::cell::RefCell;
+use std::io::File;
+use std::sync::atomics::{AtomicUint, SeqCst, INIT_ATOMIC_UINT};
+
+local_data_key!(state_key: RefCell<State>)
+
+static mut DEBUG_ID_COUNTER: AtomicUint = INIT_ATOMIC_UINT;
+
+pub struct Scope;
+
+#[macro_export]
+macro_rules! layout_debug_scope(
+ ($($arg:tt)*) => (
+ if cfg!(not(ndebug)) {
+ layout_debug::Scope::new(format!($($arg)*))
+ } else {
+ layout_debug::Scope
+ }
+ )
+)
+
+#[deriving(Encodable)]
+struct ScopeData {
+ name: String,
+ pre: String,
+ post: String,
+ children: Vec<Box<ScopeData>>,
+}
+
+impl ScopeData {
+ fn new(name: String, pre: String) -> ScopeData {
+ ScopeData {
+ name: name,
+ pre: pre,
+ post: String::new(),
+ children: vec!(),
+ }
+ }
+}
+
+struct State {
+ flow_root: FlowRef,
+ scope_stack: Vec<Box<ScopeData>>,
+}
+
+/// A layout debugging scope. The entire state of the flow tree
+/// will be output at the beginning and end of this scope.
+impl Scope {
+ pub fn new(name: String) -> Scope {
+ let maybe_refcell = state_key.get();
+ match maybe_refcell {
+ Some(refcell) => {
+ let mut state = refcell.borrow_mut();
+ let flow_trace = json::encode(&state.flow_root.get());
+ let data = box ScopeData::new(name, flow_trace);
+ state.scope_stack.push(data);
+ }
+ None => {}
+ }
+ Scope
+ }
+}
+
+#[cfg(not(ndebug))]
+impl Drop for Scope {
+ fn drop(&mut self) {
+ let maybe_refcell = state_key.get();
+ match maybe_refcell {
+ Some(refcell) => {
+ let mut state = refcell.borrow_mut();
+ let mut current_scope = state.scope_stack.pop().unwrap();
+ current_scope.post = json::encode(&state.flow_root.get());
+ let previous_scope = state.scope_stack.mut_last().unwrap();
+ previous_scope.children.push(current_scope);
+ }
+ None => {}
+ }
+ }
+}
+
+/// Generate a unique ID. This is used for items such as Fragment
+/// which are often reallocated but represent essentially the
+/// same data.
+pub fn generate_unique_debug_id() -> uint {
+ unsafe { DEBUG_ID_COUNTER.fetch_add(1, SeqCst) }
+}
+
+/// Begin a layout debug trace. If this has not been called,
+/// creating debug scopes has no effect.
+pub fn begin_trace(flow_root: FlowRef) {
+ assert!(state_key.get().is_none());
+
+ let flow_trace = json::encode(&flow_root.get());
+ let state = State {
+ scope_stack: vec![box ScopeData::new("root".to_string(), flow_trace)],
+ flow_root: flow_root,
+ };
+ state_key.replace(Some(RefCell::new(state)));
+}
+
+/// End the debug layout trace. This will write the layout
+/// trace to disk in the current directory. The output
+/// file can then be viewed with an external tool.
+pub fn end_trace() {
+ let task_state_cell = state_key.replace(None).unwrap();
+ let mut task_state = task_state_cell.borrow_mut();
+ assert!(task_state.scope_stack.len() == 1);
+ let mut root_scope = task_state.scope_stack.pop().unwrap();
+ root_scope.post = json::encode(&task_state.flow_root.get());
+
+ let result = json::encode(&root_scope);
+ let path = Path::new("layout_trace.json");
+ let mut file = File::create(&path).unwrap();
+ file.write_str(result.as_slice()).unwrap();
+}
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs
new file mode 100644
index 00000000000..c3c463408e6
--- /dev/null
+++ b/components/layout/layout_task.rs
@@ -0,0 +1,1020 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The layout task. Performs layout on the DOM, builds display lists and sends them to be
+//! rendered.
+
+use css::matching::{ApplicableDeclarations, MatchMethods};
+use css::node_style::StyledNode;
+use construct::{FlowConstructionResult, NoConstructionResult};
+use context::{LayoutContext, SharedLayoutContext};
+use flow::{Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
+use flow::{PreorderFlowTraversal, PostorderFlowTraversal};
+use flow;
+use flow_ref::FlowRef;
+use incremental::RestyleDamage;
+use layout_debug;
+use parallel::UnsafeFlow;
+use parallel;
+use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods, ToGfxColor};
+use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
+
+use collections::dlist::DList;
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+use gfx::display_list::{ClipDisplayItemClass, ContentStackingLevel, DisplayItem};
+use gfx::display_list::{DisplayItemIterator, DisplayList, OpaqueNode};
+use gfx::render_task::{RenderInitMsg, RenderChan, RenderLayer};
+use gfx::{render_task, color};
+use layout_traits;
+use layout_traits::{LayoutControlMsg, LayoutTaskFactory};
+use script::dom::bindings::js::JS;
+use script::dom::node::{ElementNodeTypeId, LayoutDataRef, Node};
+use script::dom::element::{HTMLBodyElementTypeId, HTMLHtmlElementTypeId};
+use script::layout_interface::{AddStylesheetMsg, ScriptLayoutChan};
+use script::layout_interface::{TrustedNodeAddress, ContentBoxesResponse, ExitNowMsg};
+use script::layout_interface::{ContentBoxResponse, HitTestResponse, MouseOverResponse};
+use script::layout_interface::{ContentChangedDocumentDamage, LayoutChan, Msg, PrepareToExitMsg};
+use script::layout_interface::{GetRPCMsg, LayoutRPC, ReapLayoutDataMsg, Reflow, UntrustedNodeAddress};
+use script::layout_interface::{ReflowForDisplay, ReflowMsg};
+use script_traits::{SendEventMsg, ReflowEvent, ReflowCompleteMsg, OpaqueScriptLayoutChannel, ScriptControlChan};
+use servo_msg::compositor_msg::Scrollable;
+use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, FailureMsg};
+use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
+use gfx::font_cache_task::{FontCacheTask};
+use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::LogicalPoint;
+use servo_util::opts::Opts;
+use servo_util::smallvec::{SmallVec, SmallVec1};
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use servo_util::task::spawn_named_with_send_on_failure;
+use servo_util::workqueue::WorkQueue;
+use std::comm::{channel, Sender, Receiver, Select};
+use std::mem;
+use std::ptr;
+use style::{AuthorOrigin, Stylesheet, Stylist};
+use style::iter_font_face_rules;
+use sync::{Arc, Mutex};
+use url::Url;
+
+/// Mutable data belonging to the LayoutTask.
+///
+/// This needs to be protected by a mutex so we can do fast RPCs.
+pub struct LayoutTaskData {
+ /// The local image cache.
+ pub local_image_cache: Arc<Mutex<LocalImageCache>>,
+
+ /// The size of the viewport.
+ pub screen_size: Size2D<Au>,
+
+ /// A cached display list.
+ pub display_list: Option<Arc<DisplayList>>,
+
+ pub stylist: Box<Stylist>,
+
+ /// The workers that we use for parallel operation.
+ pub parallel_traversal: Option<WorkQueue<*const SharedLayoutContext, UnsafeFlow>>,
+
+ /// The dirty rect. Used during display list construction.
+ pub dirty: Rect<Au>,
+}
+
+/// Information needed by the layout task.
+pub struct LayoutTask {
+ /// The ID of the pipeline that we belong to.
+ pub id: PipelineId,
+
+ /// The port on which we receive messages from the script task.
+ pub port: Receiver<Msg>,
+
+ /// The port on which we receive messages from the constellation
+ pub pipeline_port: Receiver<LayoutControlMsg>,
+
+ //// The channel to send messages to ourself.
+ pub chan: LayoutChan,
+
+ /// The channel on which messages can be sent to the constellation.
+ pub constellation_chan: ConstellationChan,
+
+ /// The channel on which messages can be sent to the script task.
+ pub script_chan: ScriptControlChan,
+
+ /// The channel on which messages can be sent to the painting task.
+ pub render_chan: RenderChan,
+
+ /// The channel on which messages can be sent to the time profiler.
+ pub time_profiler_chan: TimeProfilerChan,
+
+ /// The channel on which messages can be sent to the image cache.
+ pub image_cache_task: ImageCacheTask,
+
+ /// Public interface to the font cache task.
+ pub font_cache_task: FontCacheTask,
+
+ /// The command-line options.
+ pub opts: Opts,
+
+ /// A mutex to allow for fast, read-only RPC of layout's internal data
+ /// structures, while still letting the LayoutTask modify them.
+ ///
+ /// All the other elements of this struct are read-only.
+ pub rw_data: Arc<Mutex<LayoutTaskData>>,
+}
+
+/// The damage computation traversal.
+#[deriving(Clone)]
+struct ComputeDamageTraversal;
+
+impl PostorderFlowTraversal for ComputeDamageTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let mut damage = flow::base(flow).restyle_damage;
+ for child in flow::child_iter(flow) {
+ damage.insert(flow::base(child).restyle_damage.propagate_up())
+ }
+ flow::mut_base(flow).restyle_damage = damage;
+ true
+ }
+}
+
+/// Propagates restyle damage up and down the tree as appropriate.
+///
+/// FIXME(pcwalton): Merge this with flow tree building and/or other traversals.
+struct PropagateDamageTraversal {
+ all_style_damage: bool,
+}
+
+impl PreorderFlowTraversal for PropagateDamageTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ if self.all_style_damage {
+ flow::mut_base(flow).restyle_damage.insert(RestyleDamage::all())
+ }
+ debug!("restyle damage = {:?}", flow::base(flow).restyle_damage);
+
+ let prop = flow::base(flow).restyle_damage.propagate_down();
+ if !prop.is_empty() {
+ for kid_ctx in flow::child_iter(flow) {
+ flow::mut_base(kid_ctx).restyle_damage.insert(prop)
+ }
+ }
+ true
+ }
+}
+
+/// The flow tree verification traversal. This is only on in debug builds.
+#[cfg(debug)]
+struct FlowTreeVerificationTraversal;
+
+#[cfg(debug)]
+impl PreorderFlowTraversal for FlowTreeVerificationTraversal {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ let base = flow::base(flow);
+ if !base.flags.is_leaf() && !base.flags.is_nonleaf() {
+ println("flow tree verification failed: flow wasn't a leaf or a nonleaf!");
+ flow.dump();
+ fail!("flow tree verification failed")
+ }
+ true
+ }
+}
+
+/// The bubble-inline-sizes traversal, the first part of layout computation. This computes preferred
+/// and intrinsic inline-sizes and bubbles them up the tree.
+pub struct BubbleISizesTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for BubbleISizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.bubble_inline_sizes(self.layout_context);
+ true
+ }
+
+ // FIXME: We can't prune until we start reusing flows
+ /*
+ #[inline]
+ fn should_prune(&mut self, flow: &mut Flow) -> bool {
+ flow::mut_base(flow).restyle_damage.lacks(BubbleISizes)
+ }
+ */
+}
+
+/// The assign-inline-sizes traversal. In Gecko this corresponds to `Reflow`.
+pub struct AssignISizesTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PreorderFlowTraversal for AssignISizesTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.assign_inline_sizes(self.layout_context);
+ true
+ }
+}
+
+/// The assign-block-sizes-and-store-overflow traversal, the last (and most expensive) part of layout
+/// computation. Determines the final block-sizes for all layout objects, computes positions, and
+/// computes overflow regions. In Gecko this corresponds to `FinishAndStoreOverflow`.
+pub struct AssignBSizesAndStoreOverflowTraversal<'a> {
+ pub layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> PostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) -> bool {
+ flow.assign_block_size(self.layout_context);
+ // Skip store-overflow for absolutely positioned flows. That will be
+ // done in a separate traversal.
+ if !flow.is_store_overflow_delayed() {
+ flow.store_overflow(self.layout_context);
+ }
+ true
+ }
+
+ #[inline]
+ fn should_process(&mut self, flow: &mut Flow) -> bool {
+ !flow::base(flow).flags.impacted_by_floats()
+ }
+}
+
+/// The display list construction traversal.
+pub struct BuildDisplayListTraversal<'a> {
+ layout_context: &'a LayoutContext<'a>,
+}
+
+impl<'a> BuildDisplayListTraversal<'a> {
+ #[inline]
+ fn process(&mut self, flow: &mut Flow) {
+ flow.compute_absolute_position();
+
+ for kid in flow::mut_base(flow).child_iter() {
+ if !kid.is_absolutely_positioned() {
+ self.process(kid)
+ }
+ }
+
+ for absolute_descendant_link in flow::mut_base(flow).abs_descendants.iter() {
+ self.process(absolute_descendant_link)
+ }
+
+ flow.build_display_list(self.layout_context)
+ }
+}
+
+struct LayoutImageResponder {
+ id: PipelineId,
+ script_chan: ScriptControlChan,
+}
+
+impl ImageResponder for LayoutImageResponder {
+ fn respond(&self) -> proc(ImageResponseMsg):Send {
+ let id = self.id.clone();
+ let script_chan = self.script_chan.clone();
+ let f: proc(ImageResponseMsg):Send = proc(_) {
+ let ScriptControlChan(chan) = script_chan;
+ drop(chan.send_opt(SendEventMsg(id.clone(), ReflowEvent)))
+ };
+ f
+ }
+}
+
+impl LayoutTaskFactory for LayoutTask {
+ /// Spawns a new layout task.
+ fn create(_phantom: Option<&mut LayoutTask>,
+ id: PipelineId,
+ chan: OpaqueScriptLayoutChannel,
+ pipeline_port: Receiver<LayoutControlMsg>,
+ constellation_chan: ConstellationChan,
+ failure_msg: Failure,
+ script_chan: ScriptControlChan,
+ render_chan: RenderChan,
+ img_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ opts: Opts,
+ time_profiler_chan: TimeProfilerChan,
+ shutdown_chan: Sender<()>) {
+ let ConstellationChan(con_chan) = constellation_chan.clone();
+ spawn_named_with_send_on_failure("LayoutTask", proc() {
+ { // Ensures layout task is destroyed before we send shutdown message
+ let sender = chan.sender();
+ let layout =
+ LayoutTask::new(
+ id,
+ chan.receiver(),
+ LayoutChan(sender),
+ pipeline_port,
+ constellation_chan,
+ script_chan,
+ render_chan,
+ img_cache_task,
+ font_cache_task,
+ &opts,
+ time_profiler_chan);
+ layout.start();
+ }
+ shutdown_chan.send(());
+ }, FailureMsg(failure_msg), con_chan, false);
+ }
+}
+
+impl LayoutTask {
+ /// Creates a new `LayoutTask` structure.
+ fn new(id: PipelineId,
+ port: Receiver<Msg>,
+ chan: LayoutChan,
+ pipeline_port: Receiver<LayoutControlMsg>,
+ constellation_chan: ConstellationChan,
+ script_chan: ScriptControlChan,
+ render_chan: RenderChan,
+ image_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ opts: &Opts,
+ time_profiler_chan: TimeProfilerChan)
+ -> LayoutTask {
+ let local_image_cache = Arc::new(Mutex::new(LocalImageCache::new(image_cache_task.clone())));
+ let screen_size = Size2D(Au(0), Au(0));
+ let parallel_traversal = if opts.layout_threads != 1 {
+ Some(WorkQueue::new("LayoutWorker", opts.layout_threads, ptr::null()))
+ } else {
+ None
+ };
+
+ LayoutTask {
+ id: id,
+ port: port,
+ pipeline_port: pipeline_port,
+ chan: chan,
+ constellation_chan: constellation_chan,
+ script_chan: script_chan,
+ render_chan: render_chan,
+ time_profiler_chan: time_profiler_chan,
+ image_cache_task: image_cache_task.clone(),
+ font_cache_task: font_cache_task,
+ opts: opts.clone(),
+ rw_data: Arc::new(Mutex::new(
+ LayoutTaskData {
+ local_image_cache: local_image_cache,
+ screen_size: screen_size,
+ display_list: None,
+ stylist: box Stylist::new(),
+ parallel_traversal: parallel_traversal,
+ dirty: Rect::zero(),
+ })),
+ }
+ }
+
+ /// Starts listening on the port.
+ fn start(self) {
+ while self.handle_request() {
+ // Loop indefinitely.
+ }
+ }
+
+ // Create a layout context for use in building display lists, hit testing, &c.
+ fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, reflow_root: &LayoutNode, url: &Url) -> SharedLayoutContext {
+ SharedLayoutContext {
+ image_cache: rw_data.local_image_cache.clone(),
+ screen_size: rw_data.screen_size.clone(),
+ constellation_chan: self.constellation_chan.clone(),
+ layout_chan: self.chan.clone(),
+ font_cache_task: self.font_cache_task.clone(),
+ stylist: &*rw_data.stylist,
+ url: (*url).clone(),
+ reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root),
+ opts: self.opts.clone(),
+ dirty: Rect::zero(),
+ }
+ }
+
+ /// Receives and dispatches messages from the script and constellation tasks
+ fn handle_request(&self) -> bool {
+ enum PortToRead {
+ Pipeline,
+ Script,
+ }
+
+ let port_to_read = {
+ let sel = Select::new();
+ let mut port1 = sel.handle(&self.port);
+ let mut port2 = sel.handle(&self.pipeline_port);
+ unsafe {
+ port1.add();
+ port2.add();
+ }
+ let ret = sel.wait();
+ if ret == port1.id() {
+ Script
+ } else if ret == port2.id() {
+ Pipeline
+ } else {
+ fail!("invalid select result");
+ }
+ };
+
+ match port_to_read {
+ Pipeline => match self.pipeline_port.recv() {
+ layout_traits::ExitNowMsg => self.handle_script_request(ExitNowMsg),
+ },
+ Script => {
+ let msg = self.port.recv();
+ self.handle_script_request(msg)
+ }
+ }
+ }
+
+ /// Receives and dispatches messages from the script task.
+ fn handle_script_request(&self, request: Msg) -> bool {
+ match request {
+ AddStylesheetMsg(sheet) => self.handle_add_stylesheet(sheet),
+ GetRPCMsg(response_chan) => {
+ response_chan.send(
+ box LayoutRPCImpl(
+ self.rw_data.clone()) as Box<LayoutRPC + Send>);
+ },
+ ReflowMsg(data) => {
+ profile(time::LayoutPerformCategory, self.time_profiler_chan.clone(), || {
+ self.handle_reflow(&*data);
+ });
+ },
+ ReapLayoutDataMsg(dead_layout_data) => {
+ unsafe {
+ LayoutTask::handle_reap_layout_data(dead_layout_data)
+ }
+ },
+ PrepareToExitMsg(response_chan) => {
+ debug!("layout: PrepareToExitMsg received");
+ self.prepare_to_exit(response_chan);
+ return false
+ },
+ ExitNowMsg => {
+ debug!("layout: ExitNowMsg received");
+ self.exit_now();
+ return false
+ }
+ }
+
+ true
+ }
+
+ /// Enters a quiescent state in which no new messages except for `ReapLayoutDataMsg` will be
+ /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given
+ /// response channel.
+ fn prepare_to_exit(&self, response_chan: Sender<()>) {
+ response_chan.send(());
+ loop {
+ match self.port.recv() {
+ ReapLayoutDataMsg(dead_layout_data) => {
+ unsafe {
+ LayoutTask::handle_reap_layout_data(dead_layout_data)
+ }
+ }
+ ExitNowMsg => {
+ debug!("layout task is exiting...");
+ self.exit_now();
+ break
+ }
+ _ => {
+ fail!("layout: message that wasn't `ExitNowMsg` received after \
+ `PrepareToExitMsg`")
+ }
+ }
+ }
+ }
+
+ /// Shuts down the layout task now. If there are any DOM nodes left, layout will now (safely)
+ /// crash.
+ fn exit_now(&self) {
+ let (response_chan, response_port) = channel();
+
+ {
+ let mut rw_data = self.rw_data.lock();
+ match rw_data.deref_mut().parallel_traversal {
+ None => {}
+ Some(ref mut traversal) => traversal.shutdown(),
+ }
+ }
+
+ self.render_chan.send(render_task::ExitMsg(Some(response_chan)));
+ response_port.recv()
+ }
+
+ fn handle_add_stylesheet(&self, sheet: Stylesheet) {
+ // Find all font-face rules and notify the font cache of them.
+ // GWTODO: Need to handle unloading web fonts (when we handle unloading stylesheets!)
+ iter_font_face_rules(&sheet, |family, url| {
+ self.font_cache_task.add_web_font(family.to_string(), url.clone());
+ });
+ let mut rw_data = self.rw_data.lock();
+ rw_data.stylist.add_stylesheet(sheet, AuthorOrigin);
+ }
+
+ /// Retrieves the flow tree root from the root node.
+ fn get_layout_root(&self, node: LayoutNode) -> FlowRef {
+ let mut layout_data_ref = node.mutate_layout_data();
+ let result = match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ mem::replace(&mut layout_data.data.flow_construction_result, NoConstructionResult)
+ }
+ &None => fail!("no layout data for root node"),
+ };
+ let mut flow = match result {
+ FlowConstructionResult(mut flow, abs_descendants) => {
+ // Note: Assuming that the root has display 'static' (as per
+ // CSS Section 9.3.1). Otherwise, if it were absolutely
+ // positioned, it would return a reference to itself in
+ // `abs_descendants` and would lead to a circular reference.
+ // Set Root as CB for any remaining absolute descendants.
+ flow.set_abs_descendants(abs_descendants);
+ flow
+ }
+ _ => fail!("Flow construction didn't result in a flow at the root of the tree!"),
+ };
+ flow.get_mut().mark_as_root();
+ flow
+ }
+
+ /// Performs layout constraint solving.
+ ///
+ /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
+ /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
+ #[inline(never)]
+ fn solve_constraints<'a>(&self,
+ layout_root: &mut Flow,
+ layout_context: &'a LayoutContext<'a>) {
+ let _scope = layout_debug_scope!("solve_constraints");
+
+ if layout_context.shared.opts.bubble_inline_sizes_separately {
+ let mut traversal = BubbleISizesTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_postorder(&mut traversal);
+ }
+
+ // FIXME(kmc): We want to prune nodes without the Reflow restyle damage
+ // bit, but FloatContext values can't be reused, so we need to
+ // recompute them every time.
+ // NOTE: this currently computes borders, so any pruning should separate that operation
+ // out.
+ {
+ let mut traversal = AssignISizesTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_preorder(&mut traversal);
+ }
+
+ // FIXME(pcwalton): Prune this pass as well.
+ {
+ let mut traversal = AssignBSizesAndStoreOverflowTraversal {
+ layout_context: layout_context,
+ };
+ layout_root.traverse_postorder(&mut traversal);
+ }
+ }
+
+ /// Performs layout constraint solving in parallel.
+ ///
+ /// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
+ /// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
+ #[inline(never)]
+ fn solve_constraints_parallel(&self,
+ rw_data: &mut LayoutTaskData,
+ layout_root: &mut FlowRef,
+ shared_layout_context: &SharedLayoutContext) {
+ if shared_layout_context.opts.bubble_inline_sizes_separately {
+ let mut traversal = BubbleISizesTraversal {
+ layout_context: &LayoutContext::new(shared_layout_context),
+ };
+ layout_root.get_mut().traverse_postorder(&mut traversal);
+ }
+
+ match rw_data.parallel_traversal {
+ None => fail!("solve_contraints_parallel() called with no parallel traversal ready"),
+ Some(ref mut traversal) => {
+ // NOTE: this currently computes borders, so any pruning should separate that
+ // operation out.
+ parallel::traverse_flow_tree_preorder(layout_root,
+ self.time_profiler_chan.clone(),
+ shared_layout_context,
+ traversal);
+ }
+ }
+ }
+
+ /// Verifies that every node was either marked as a leaf or as a nonleaf in the flow tree.
+ /// This is only on in debug builds.
+ #[inline(never)]
+ #[cfg(debug)]
+ fn verify_flow_tree(&self, layout_root: &mut FlowRef) {
+ let mut traversal = FlowTreeVerificationTraversal;
+ layout_root.traverse_preorder(&mut traversal);
+ }
+
+ #[cfg(not(debug))]
+ fn verify_flow_tree(&self, _: &mut FlowRef) {
+ }
+
+ /// The high-level routine that performs layout tasks.
+ fn handle_reflow(&self, data: &Reflow) {
+ // FIXME: Isolate this transmutation into a "bridge" module.
+ // FIXME(rust#16366): The following line had to be moved because of a
+ // rustc bug. It should be in the next unsafe block.
+ let mut node: JS<Node> = unsafe { JS::from_trusted_node_address(data.document_root) };
+ let node: &mut LayoutNode = unsafe {
+ mem::transmute(&mut node)
+ };
+
+ debug!("layout: received layout request for: {:s}", data.url.serialize());
+ debug!("layout: damage is {:?}", data.damage);
+ debug!("layout: parsed Node tree");
+ debug!("{:?}", node.dump());
+
+ let mut rw_data = self.rw_data.lock();
+
+ {
+ // Reset the image cache.
+ let mut local_image_cache = rw_data.local_image_cache.lock();
+ local_image_cache.next_round(self.make_on_image_available_cb());
+ }
+
+ // true => Do the reflow with full style damage, because content
+ // changed or the window was resized.
+ let mut all_style_damage = match data.damage.level {
+ ContentChangedDocumentDamage => true,
+ _ => false
+ };
+
+ // TODO: Calculate the "actual viewport":
+ // http://www.w3.org/TR/css-device-adapt/#actual-viewport
+ let viewport_size = data.window_size.initial_viewport;
+
+ let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()),
+ Au::from_frac32_px(viewport_size.height.get()));
+ if rw_data.screen_size != current_screen_size {
+ all_style_damage = true
+ }
+ rw_data.screen_size = current_screen_size;
+
+ // Create a layout context for use throughout the following passes.
+ let mut shared_layout_ctx = self.build_shared_layout_context(rw_data.deref(), node, &data.url);
+
+ let mut layout_root = profile(time::LayoutStyleRecalcCategory,
+ self.time_profiler_chan.clone(),
+ || {
+ // Perform CSS selector matching and flow construction.
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ let mut applicable_declarations = ApplicableDeclarations::new();
+ node.recalc_style_for_subtree(&*rw_data.stylist,
+ &layout_ctx,
+ &mut applicable_declarations,
+ None)
+ }
+ Some(ref mut traversal) => {
+ parallel::recalc_style_for_subtree(node, &mut shared_layout_ctx, traversal)
+ }
+ }
+
+ self.get_layout_root((*node).clone())
+ });
+
+ // Verification of the flow tree, which ensures that all nodes were either marked as leaves
+ // or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to
+ // memory safety but is a useful debugging tool.)
+ self.verify_flow_tree(&mut layout_root);
+
+ if self.opts.trace_layout {
+ layout_debug::begin_trace(layout_root.clone());
+ }
+
+ // Propagate damage.
+ profile(time::LayoutDamagePropagateCategory, self.time_profiler_chan.clone(), || {
+ layout_root.get_mut().traverse_preorder(&mut PropagateDamageTraversal {
+ all_style_damage: all_style_damage
+ });
+ layout_root.get_mut().traverse_postorder(&mut ComputeDamageTraversal.clone());
+ });
+
+ // Perform the primary layout passes over the flow tree to compute the locations of all
+ // the boxes.
+ profile(time::LayoutMainCategory, self.time_profiler_chan.clone(), || {
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ // Sequential mode.
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ self.solve_constraints(layout_root.get_mut(), &layout_ctx)
+ }
+ Some(_) => {
+ // Parallel mode.
+ self.solve_constraints_parallel(rw_data, &mut layout_root, &mut shared_layout_ctx)
+ }
+ }
+ });
+
+ // Build the display list if necessary, and send it to the renderer.
+ if data.goal == ReflowForDisplay {
+ let writing_mode = flow::base(layout_root.get()).writing_mode;
+ profile(time::LayoutDispListBuildCategory, self.time_profiler_chan.clone(), || {
+ shared_layout_ctx.dirty = flow::base(layout_root.get()).position.to_physical(
+ writing_mode, rw_data.screen_size);
+ flow::mut_base(layout_root.get_mut()).abs_position =
+ LogicalPoint::zero(writing_mode).to_physical(writing_mode, rw_data.screen_size);
+
+ let rw_data = rw_data.deref_mut();
+ match rw_data.parallel_traversal {
+ None => {
+ let layout_ctx = LayoutContext::new(&shared_layout_ctx);
+ let mut traversal = BuildDisplayListTraversal {
+ layout_context: &layout_ctx,
+ };
+ traversal.process(layout_root.get_mut());
+ }
+ Some(ref mut traversal) => {
+ parallel::build_display_list_for_subtree(&mut layout_root,
+ self.time_profiler_chan.clone(),
+ &mut shared_layout_ctx,
+ traversal);
+ }
+ }
+
+ let root_display_list =
+ mem::replace(&mut flow::mut_base(layout_root.get_mut()).display_list,
+ DisplayList::new());
+ root_display_list.debug();
+ let display_list = Arc::new(root_display_list.flatten(ContentStackingLevel));
+
+ // FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor
+ // it with extreme prejudice.
+ let mut color = color::rgba(1.0, 1.0, 1.0, 1.0);
+ for child in node.traverse_preorder() {
+ if child.type_id() == Some(ElementNodeTypeId(HTMLHtmlElementTypeId)) ||
+ child.type_id() == Some(ElementNodeTypeId(HTMLBodyElementTypeId)) {
+ let element_bg_color = {
+ let thread_safe_child = ThreadSafeLayoutNode::new(&child);
+ thread_safe_child.style()
+ .resolve_color(thread_safe_child.style()
+ .get_background()
+ .background_color)
+ .to_gfx_color()
+ };
+ match element_bg_color {
+ color::rgba(0., 0., 0., 0.) => {}
+ _ => {
+ color = element_bg_color;
+ break;
+ }
+ }
+ }
+ }
+
+ let root_size = {
+ let root_flow = flow::base(layout_root.get());
+ root_flow.position.size.to_physical(root_flow.writing_mode)
+ };
+ let root_size = Size2D(root_size.width.to_nearest_px() as uint,
+ root_size.height.to_nearest_px() as uint);
+ let render_layer = RenderLayer {
+ id: layout_root.get().layer_id(0),
+ display_list: display_list.clone(),
+ position: Rect(Point2D(0u, 0u), root_size),
+ background_color: color,
+ scroll_policy: Scrollable,
+ };
+
+ rw_data.display_list = Some(display_list.clone());
+
+ // TODO(pcwalton): Eventually, when we have incremental reflow, this will have to
+ // be smarter in order to handle retained layer contents properly from reflow to
+ // reflow.
+ let mut layers = SmallVec1::new();
+ layers.push(render_layer);
+ for layer in mem::replace(&mut flow::mut_base(layout_root.get_mut()).layers,
+ DList::new()).move_iter() {
+ layers.push(layer)
+ }
+
+ debug!("Layout done!");
+
+ self.render_chan.send(RenderInitMsg(layers));
+ });
+ }
+
+ if self.opts.trace_layout {
+ layout_debug::end_trace();
+ }
+
+ // Tell script that we're done.
+ //
+ // FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without
+ // either select or a filtered recv() that only looks for messages of a given type.
+ data.script_join_chan.send(());
+ let ScriptControlChan(ref chan) = data.script_chan;
+ chan.send(ReflowCompleteMsg(self.id, data.id));
+ }
+
+
+ // When images can't be loaded in time to display they trigger
+ // this callback in some task somewhere. This will send a message
+ // to the script task, and ultimately cause the image to be
+ // re-requested. We probably don't need to go all the way back to
+ // the script task for this.
+ fn make_on_image_available_cb(&self) -> Box<ImageResponder+Send> {
+ // This has a crazy signature because the image cache needs to
+ // make multiple copies of the callback, and the dom event
+ // channel is not a copyable type, so this is actually a
+ // little factory to produce callbacks
+ box LayoutImageResponder {
+ id: self.id.clone(),
+ script_chan: self.script_chan.clone(),
+ } as Box<ImageResponder+Send>
+ }
+
+ /// Handles a message to destroy layout data. Layout data must be destroyed on *this* task
+ /// because it contains local managed pointers.
+ unsafe fn handle_reap_layout_data(layout_data: LayoutDataRef) {
+ let mut layout_data_ref = layout_data.borrow_mut();
+ let _: Option<LayoutDataWrapper> = mem::transmute(
+ mem::replace(&mut *layout_data_ref, None));
+ }
+}
+
+struct LayoutRPCImpl(Arc<Mutex<LayoutTaskData>>);
+
+impl LayoutRPC for LayoutRPCImpl {
+ // The neat thing here is that in order to answer the following two queries we only
+ // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`.
+ fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse {
+ let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node);
+ fn union_boxes_for_node(accumulator: &mut Option<Rect<Au>>,
+ mut iter: DisplayItemIterator,
+ node: OpaqueNode) {
+ for item in iter {
+ union_boxes_for_node(accumulator, item.children(), node);
+ if item.base().node == node {
+ match *accumulator {
+ None => *accumulator = Some(item.base().bounds),
+ Some(ref mut acc) => *acc = acc.union(&item.base().bounds),
+ }
+ }
+ }
+ }
+
+ let mut rect = None;
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ union_boxes_for_node(&mut rect, display_list.iter(), node)
+ }
+ }
+ }
+ ContentBoxResponse(rect.unwrap_or(Rect::zero()))
+ }
+
+ /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
+ fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse {
+ let node: OpaqueNode = OpaqueNodeMethods::from_script_node(node);
+
+ fn add_boxes_for_node(accumulator: &mut Vec<Rect<Au>>,
+ mut iter: DisplayItemIterator,
+ node: OpaqueNode) {
+ for item in iter {
+ add_boxes_for_node(accumulator, item.children(), node);
+ if item.base().node == node {
+ accumulator.push(item.base().bounds)
+ }
+ }
+ }
+
+ let mut boxes = vec!();
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ add_boxes_for_node(&mut boxes, display_list.iter(), node)
+ }
+ }
+ }
+ ContentBoxesResponse(boxes)
+ }
+
+ /// Requests the node containing the point of interest
+ fn hit_test(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()> {
+ fn hit_test<'a,I:Iterator<&'a DisplayItem>>(x: Au, y: Au, mut iterator: I)
+ -> Option<HitTestResponse> {
+ for item in iterator {
+ match *item {
+ ClipDisplayItemClass(ref cc) => {
+ if geometry::rect_contains_point(cc.base.bounds, Point2D(x, y)) {
+ let ret = hit_test(x, y, cc.children.list.iter().rev());
+ if !ret.is_none() {
+ return ret
+ }
+ }
+ continue
+ }
+ _ => {}
+ }
+
+ let bounds = item.bounds();
+
+ // TODO(tikue): This check should really be performed by a method of
+ // DisplayItem.
+ if x < bounds.origin.x + bounds.size.width &&
+ bounds.origin.x <= x &&
+ y < bounds.origin.y + bounds.size.height &&
+ bounds.origin.y <= y {
+ return Some(HitTestResponse(item.base()
+ .node
+ .to_untrusted_node_address()))
+ }
+ }
+ let ret: Option<HitTestResponse> = None;
+ ret
+ }
+ let (x, y) = (Au::from_frac_px(point.x as f64),
+ Au::from_frac_px(point.y as f64));
+
+ let resp = {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => hit_test(x, y, display_list.list.iter().rev()),
+ }
+ };
+
+ if resp.is_some() {
+ return Ok(resp.unwrap());
+ }
+ Err(())
+ }
+
+ fn mouse_over(&self, _: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()> {
+ fn mouse_over_test<'a,
+ I:Iterator<&'a DisplayItem>>(
+ x: Au,
+ y: Au,
+ mut iterator: I,
+ result: &mut Vec<UntrustedNodeAddress>) {
+ for item in iterator {
+ match *item {
+ ClipDisplayItemClass(ref cc) => {
+ mouse_over_test(x, y, cc.children.list.iter().rev(), result);
+ }
+ _ => {
+ let bounds = item.bounds();
+
+ // TODO(tikue): This check should really be performed by a method
+ // of DisplayItem.
+ if x < bounds.origin.x + bounds.size.width &&
+ bounds.origin.x <= x &&
+ y < bounds.origin.y + bounds.size.height &&
+ bounds.origin.y <= y {
+ result.push(item.base()
+ .node
+ .to_untrusted_node_address());
+ }
+ }
+ }
+ }
+ }
+
+ let mut mouse_over_list: Vec<UntrustedNodeAddress> = vec!();
+ let (x, y) = (Au::from_frac_px(point.x as f64), Au::from_frac_px(point.y as f64));
+
+ {
+ let &LayoutRPCImpl(ref rw_data) = self;
+ let rw_data = rw_data.lock();
+ match rw_data.display_list {
+ None => fail!("no display list!"),
+ Some(ref display_list) => {
+ mouse_over_test(x,
+ y,
+ display_list.list.iter().rev(),
+ &mut mouse_over_list);
+ }
+ };
+ }
+
+ if mouse_over_list.is_empty() {
+ Err(())
+ } else {
+ Ok(MouseOverResponse(mouse_over_list))
+ }
+ }
+}
diff --git a/components/layout/lib.rs b/components/layout/lib.rs
new file mode 100644
index 00000000000..a782f4e1355
--- /dev/null
+++ b/components/layout/lib.rs
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+#![feature(globs, macro_rules, phase, thread_local, unsafe_destructor)]
+
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+
+extern crate geom;
+extern crate gfx;
+extern crate layout_traits;
+extern crate script;
+extern crate script_traits;
+extern crate serialize;
+extern crate style;
+#[phase(plugin)]
+extern crate servo_macros = "macros";
+extern crate servo_net = "net";
+extern crate servo_msg = "msg";
+#[phase(plugin, link)]
+extern crate servo_util = "util";
+
+extern crate collections;
+extern crate green;
+extern crate libc;
+extern crate sync;
+extern crate url;
+
+// Listed first because of macro definitions
+pub mod layout_debug;
+
+pub mod block;
+pub mod construct;
+pub mod context;
+pub mod floats;
+pub mod flow;
+pub mod flow_list;
+pub mod flow_ref;
+pub mod fragment;
+pub mod layout_task;
+pub mod inline;
+pub mod model;
+pub mod parallel;
+pub mod table_wrapper;
+pub mod table;
+pub mod table_caption;
+pub mod table_colgroup;
+pub mod table_rowgroup;
+pub mod table_row;
+pub mod table_cell;
+pub mod text;
+pub mod util;
+pub mod incremental;
+pub mod wrapper;
+pub mod extra;
+
+pub mod css {
+ mod node_util;
+
+ pub mod matching;
+ pub mod node_style;
+}
diff --git a/components/layout/model.rs b/components/layout/model.rs
new file mode 100644
index 00000000000..23647b1b77d
--- /dev/null
+++ b/components/layout/model.rs
@@ -0,0 +1,337 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Borders, padding, and margins.
+
+#![deny(unsafe_block)]
+
+use fragment::Fragment;
+
+use computed = style::computed_values;
+use geom::SideOffsets2D;
+use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage};
+use style::ComputedValues;
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use servo_util::logical_geometry::LogicalMargin;
+use std::fmt;
+
+/// A collapsible margin. See CSS 2.1 § 8.3.1.
+pub struct AdjoiningMargins {
+ /// The value of the greatest positive margin.
+ pub most_positive: Au,
+
+ /// The actual value (not the absolute value) of the negative margin with the largest absolute
+ /// value. Since this is not the absolute value, this is always zero or negative.
+ pub most_negative: Au,
+}
+
+impl AdjoiningMargins {
+ pub fn new() -> AdjoiningMargins {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: Au(0),
+ }
+ }
+
+ pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
+ if margin_value >= Au(0) {
+ AdjoiningMargins {
+ most_positive: margin_value,
+ most_negative: Au(0),
+ }
+ } else {
+ AdjoiningMargins {
+ most_positive: Au(0),
+ most_negative: margin_value,
+ }
+ }
+ }
+
+ pub fn union(&mut self, other: AdjoiningMargins) {
+ self.most_positive = geometry::max(self.most_positive, other.most_positive);
+ self.most_negative = geometry::min(self.most_negative, other.most_negative)
+ }
+
+ pub fn collapse(&self) -> Au {
+ self.most_positive + self.most_negative
+ }
+}
+
+/// Represents the block-start and block-end margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
+pub enum CollapsibleMargins {
+ /// Margins may not collapse with this flow.
+ NoCollapsibleMargins(Au, Au),
+
+ /// Both the block-start and block-end margins (specified here in that order) may collapse, but the
+ /// margins do not collapse through this flow.
+ MarginsCollapse(AdjoiningMargins, AdjoiningMargins),
+
+ /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
+ /// have any border, padding, or out-of-flow (floating or positioned) content
+ MarginsCollapseThrough(AdjoiningMargins),
+}
+
+impl CollapsibleMargins {
+ pub fn new() -> CollapsibleMargins {
+ NoCollapsibleMargins(Au(0), Au(0))
+ }
+}
+
+enum FinalMarginState {
+ MarginsCollapseThroughFinalMarginState,
+ BottomMarginCollapsesFinalMarginState,
+}
+
+pub struct MarginCollapseInfo {
+ pub state: MarginCollapseState,
+ pub block_start_margin: AdjoiningMargins,
+ pub margin_in: AdjoiningMargins,
+}
+
+impl MarginCollapseInfo {
+ /// TODO(#2012, pcwalton): Remove this method once `fragment` is not an `Option`.
+ pub fn new() -> MarginCollapseInfo {
+ MarginCollapseInfo {
+ state: AccumulatingCollapsibleTopMargin,
+ block_start_margin: AdjoiningMargins::new(),
+ margin_in: AdjoiningMargins::new(),
+ }
+ }
+
+ pub fn initialize_block_start_margin(&mut self,
+ fragment: &Fragment,
+ can_collapse_block_start_margin_with_kids: bool) {
+ if !can_collapse_block_start_margin_with_kids {
+ self.state = AccumulatingMarginIn
+ }
+
+ self.block_start_margin = AdjoiningMargins::from_margin(fragment.margin.block_start)
+ }
+
+ pub fn finish_and_compute_collapsible_margins(mut self,
+ fragment: &Fragment,
+ can_collapse_block_end_margin_with_kids: bool)
+ -> (CollapsibleMargins, Au) {
+ let state = match self.state {
+ AccumulatingCollapsibleTopMargin => {
+ match fragment.style().content_block_size() {
+ LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
+ match fragment.style().min_block_size() {
+ LP_Length(Au(0)) | LP_Percentage(0.) => {
+ MarginsCollapseThroughFinalMarginState
+ },
+ _ => {
+ // If the fragment has non-zero min-block-size, margins may not
+ // collapse through it.
+ BottomMarginCollapsesFinalMarginState
+ }
+ }
+ },
+ _ => {
+ // If the fragment has an explicitly specified block-size, margins may not
+ // collapse through it.
+ BottomMarginCollapsesFinalMarginState
+ }
+ }
+ }
+ AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
+ };
+
+ // Different logic is needed here depending on whether this flow can collapse its block-end
+ // margin with its children.
+ let block_end_margin = fragment.margin.block_end;
+ if !can_collapse_block_end_margin_with_kids {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ let advance = self.block_start_margin.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), advance)
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ let advance = self.margin_in.collapse();
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), advance)
+ }
+ }
+ } else {
+ match state {
+ MarginsCollapseThroughFinalMarginState => {
+ self.block_start_margin.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapseThrough(self.block_start_margin), Au(0))
+ }
+ BottomMarginCollapsesFinalMarginState => {
+ self.margin_in.union(AdjoiningMargins::from_margin(block_end_margin));
+ (MarginsCollapse(self.block_start_margin, self.margin_in), Au(0))
+ }
+ }
+ }
+ }
+
+ pub fn current_float_ceiling(&mut self) -> Au {
+ match self.state {
+ AccumulatingCollapsibleTopMargin => self.block_start_margin.collapse(),
+ AccumulatingMarginIn => self.margin_in.collapse(),
+ }
+ }
+
+ /// Adds the child's potentially collapsible block-start margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_block_start_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(block_start, _)) => {
+ self.state = AccumulatingMarginIn;
+ block_start
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(block_start, _)) => {
+ self.block_start_margin.union(block_start);
+ self.state = AccumulatingMarginIn;
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(block_start, _)) => {
+ let previous_margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ previous_margin_value + block_start
+ }
+ (AccumulatingMarginIn, MarginsCollapse(block_start, _)) => {
+ self.margin_in.union(block_start);
+ let margin_value = self.margin_in.collapse();
+ self.margin_in = AdjoiningMargins::new();
+ margin_value
+ }
+ (_, MarginsCollapseThrough(_)) => {
+ // For now, we ignore this; this will be handled by `advance_block-end_margin` below.
+ Au(0)
+ }
+ }
+ }
+
+ /// Adds the child's potentially collapsible block-end margin to the current margin state and
+ /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
+ /// that should be added to the Y offset during block layout.
+ pub fn advance_block_end_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
+ match (self.state, *child_collapsible_margins) {
+ (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
+ (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
+ // Can't happen because the state will have been replaced with
+ // `AccumulatingMarginIn` above.
+ fail!("should not be accumulating collapsible block_start margins anymore!")
+ }
+ (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
+ self.block_start_margin.union(margin);
+ Au(0)
+ }
+ (AccumulatingMarginIn, NoCollapsibleMargins(_, block_end)) => {
+ assert_eq!(self.margin_in.most_positive, Au(0));
+ assert_eq!(self.margin_in.most_negative, Au(0));
+ block_end
+ }
+ (AccumulatingMarginIn, MarginsCollapse(_, block_end)) |
+ (AccumulatingMarginIn, MarginsCollapseThrough(block_end)) => {
+ self.margin_in.union(block_end);
+ Au(0)
+ }
+ }
+ }
+}
+
+pub enum MarginCollapseState {
+ AccumulatingCollapsibleTopMargin,
+ AccumulatingMarginIn,
+}
+
+/// Intrinsic inline-sizes, which consist of minimum and preferred.
+#[deriving(Encodable)]
+pub struct IntrinsicISizes {
+ /// The *minimum inline-size* of the content.
+ pub minimum_inline_size: Au,
+ /// The *preferred inline-size* of the content.
+ pub preferred_inline_size: Au,
+ /// The estimated sum of borders, padding, and margins. Some calculations use this information
+ /// when computing intrinsic inline-sizes.
+ pub surround_inline_size: Au,
+}
+
+impl fmt::Show for IntrinsicISizes {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "min={}, pref={}, surr={}", self.minimum_inline_size, self.preferred_inline_size, self.surround_inline_size)
+ }
+}
+
+impl IntrinsicISizes {
+ pub fn new() -> IntrinsicISizes {
+ IntrinsicISizes {
+ minimum_inline_size: Au(0),
+ preferred_inline_size: Au(0),
+ surround_inline_size: Au(0),
+ }
+ }
+
+ pub fn total_minimum_inline_size(&self) -> Au {
+ self.minimum_inline_size + self.surround_inline_size
+ }
+
+ pub fn total_preferred_inline_size(&self) -> Au {
+ self.preferred_inline_size + self.surround_inline_size
+ }
+}
+
+/// Useful helper data type when computing values for blocks and positioned elements.
+pub enum MaybeAuto {
+ Auto,
+ Specified(Au),
+}
+
+impl MaybeAuto {
+ #[inline]
+ pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
+ -> MaybeAuto {
+ match length {
+ computed::LPA_Auto => Auto,
+ computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
+ computed::LPA_Length(length) => Specified(length)
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_default(&self, default: Au) -> Au {
+ match *self {
+ Auto => default,
+ Specified(value) => value,
+ }
+ }
+
+ #[inline]
+ pub fn specified_or_zero(&self) -> Au {
+ self.specified_or_default(Au::new(0))
+ }
+}
+
+pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
+ match length {
+ computed::LPN_None => None,
+ computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
+ computed::LPN_Length(length) => Some(length),
+ }
+}
+
+pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
+ match length {
+ computed::LP_Length(length) => length,
+ computed::LP_Percentage(p) => containing_length.scale_by(p)
+ }
+}
+
+#[inline]
+pub fn padding_from_style(style: &ComputedValues, containing_block_inline_size: Au)
+ -> LogicalMargin<Au> {
+ let padding_style = style.get_padding();
+ LogicalMargin::from_physical(style.writing_mode, SideOffsets2D::new(
+ specified(padding_style.padding_top, containing_block_inline_size),
+ specified(padding_style.padding_right, containing_block_inline_size),
+ specified(padding_style.padding_bottom, containing_block_inline_size),
+ specified(padding_style.padding_left, containing_block_inline_size)))
+}
+
diff --git a/components/layout/parallel.rs b/components/layout/parallel.rs
new file mode 100644
index 00000000000..a2786e8ba91
--- /dev/null
+++ b/components/layout/parallel.rs
@@ -0,0 +1,561 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Implements parallel traversals over the DOM and flow trees.
+//!
+//! This code is highly unsafe. Keep this file small and easy to audit.
+
+use css::matching::{ApplicableDeclarations, CannotShare, MatchMethods, StyleWasShared};
+use construct::FlowConstructor;
+use context::{LayoutContext, SharedLayoutContext};
+use extra::LayoutAuxMethods;
+use flow::{Flow, MutableFlowUtils, PreorderFlowTraversal, PostorderFlowTraversal};
+use flow;
+use flow_ref::FlowRef;
+use layout_task::{AssignBSizesAndStoreOverflowTraversal, AssignISizesTraversal};
+use layout_task::{BubbleISizesTraversal};
+use util::{LayoutDataAccess, LayoutDataWrapper, OpaqueNodeMethods};
+use wrapper::{layout_node_to_unsafe_layout_node, layout_node_from_unsafe_layout_node, LayoutNode, PostorderNodeMutTraversal};
+use wrapper::{ThreadSafeLayoutNode, UnsafeLayoutNode};
+
+use gfx::display_list::OpaqueNode;
+use servo_util::time::{TimeProfilerChan, profile};
+use servo_util::time;
+use servo_util::workqueue::{WorkQueue, WorkUnit, WorkerProxy};
+use std::mem;
+use std::ptr;
+use std::sync::atomics::{AtomicInt, Relaxed, SeqCst};
+use style::TNode;
+
+#[allow(dead_code)]
+fn static_assertion(node: UnsafeLayoutNode) {
+ unsafe {
+ let _: UnsafeFlow = ::std::intrinsics::transmute(node);
+ }
+}
+
+/// Vtable + pointer representation of a Flow trait object.
+pub type UnsafeFlow = (uint, uint);
+
+fn null_unsafe_flow() -> UnsafeFlow {
+ (0, 0)
+}
+
+pub fn owned_flow_to_unsafe_flow(flow: *const FlowRef) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&*flow)
+ }
+}
+
+pub fn mut_owned_flow_to_unsafe_flow(flow: *mut FlowRef) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&*flow)
+ }
+}
+
+pub fn borrowed_flow_to_unsafe_flow(flow: &Flow) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&flow)
+ }
+}
+
+pub fn mut_borrowed_flow_to_unsafe_flow(flow: &mut Flow) -> UnsafeFlow {
+ unsafe {
+ mem::transmute_copy(&flow)
+ }
+}
+
+/// Information that we need stored in each DOM node.
+pub struct DomParallelInfo {
+ /// The number of children that still need work done.
+ pub children_count: AtomicInt,
+}
+
+impl DomParallelInfo {
+ pub fn new() -> DomParallelInfo {
+ DomParallelInfo {
+ children_count: AtomicInt::new(0),
+ }
+ }
+}
+
+/// Information that we need stored in each flow.
+pub struct FlowParallelInfo {
+ /// The number of children that still need work done.
+ pub children_count: AtomicInt,
+ /// The number of children and absolute descendants that still need work done.
+ pub children_and_absolute_descendant_count: AtomicInt,
+ /// The address of the parent flow.
+ pub parent: UnsafeFlow,
+}
+
+impl FlowParallelInfo {
+ pub fn new() -> FlowParallelInfo {
+ FlowParallelInfo {
+ children_count: AtomicInt::new(0),
+ children_and_absolute_descendant_count: AtomicInt::new(0),
+ parent: null_unsafe_flow(),
+ }
+ }
+}
+
+/// A parallel bottom-up flow traversal.
+trait ParallelPostorderFlowTraversal : PostorderFlowTraversal {
+ /// Process current flow and potentially traverse its ancestors.
+ ///
+ /// If we are the last child that finished processing, recursively process
+ /// our parent. Else, stop.
+ /// Also, stop at the root (obviously :P).
+ ///
+ /// Thus, if we start with all the leaves of a tree, we end up traversing
+ /// the whole tree bottom-up because each parent will be processed exactly
+ /// once (by the last child that finishes processing).
+ ///
+ /// The only communication between siblings is that they both
+ /// fetch-and-subtract the parent's children count.
+ fn run_parallel(&mut self,
+ mut unsafe_flow: UnsafeFlow,
+ _: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ loop {
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Perform the appropriate traversal.
+ if self.should_process(flow.get_mut()) {
+ self.process(flow.get_mut());
+ }
+
+ let base = flow::mut_base(flow.get_mut());
+
+ // Reset the count of children for the next layout traversal.
+ base.parallel.children_count.store(base.children.len() as int, Relaxed);
+
+ // Possibly enqueue the parent.
+ let unsafe_parent = base.parallel.parent;
+ if unsafe_parent == null_unsafe_flow() {
+ // We're done!
+ break
+ }
+
+ // No, we're not at the root yet. Then are we the last child
+ // of our parent to finish processing? If so, we can continue
+ // on with our parent; otherwise, we've gotta wait.
+ let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
+ let parent_base = flow::mut_base(parent.get_mut());
+ if parent_base.parallel.children_count.fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Reflow our parent.
+ unsafe_flow = unsafe_parent
+ } else {
+ // Stop.
+ break
+ }
+ }
+ }
+ }
+}
+
+/// A parallel top-down flow traversal.
+trait ParallelPreorderFlowTraversal : PreorderFlowTraversal {
+ fn run_parallel(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>);
+
+ #[inline(always)]
+ fn run_parallel_helper(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>,
+ top_down_func: extern "Rust" fn(UnsafeFlow,
+ &mut WorkerProxy<*const SharedLayoutContext,
+ UnsafeFlow>),
+ bottom_up_func: extern "Rust" fn(UnsafeFlow,
+ &mut WorkerProxy<*const SharedLayoutContext,
+ UnsafeFlow>)) {
+ let mut had_children = false;
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Perform the appropriate traversal.
+ self.process(flow.get_mut());
+
+ // Possibly enqueue the children.
+ for kid in flow::child_iter(flow.get_mut()) {
+ had_children = true;
+ proxy.push(WorkUnit {
+ fun: top_down_func,
+ data: borrowed_flow_to_unsafe_flow(kid),
+ });
+ }
+
+ }
+
+ // If there were no more children, start assigning block-sizes.
+ if !had_children {
+ bottom_up_func(unsafe_flow, proxy)
+ }
+ }
+}
+
+impl<'a> ParallelPostorderFlowTraversal for BubbleISizesTraversal<'a> {}
+
+impl<'a> ParallelPreorderFlowTraversal for AssignISizesTraversal<'a> {
+ fn run_parallel(&mut self,
+ unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ self.run_parallel_helper(unsafe_flow,
+ proxy,
+ assign_inline_sizes,
+ assign_block_sizes_and_store_overflow)
+ }
+}
+
+impl<'a> ParallelPostorderFlowTraversal for AssignBSizesAndStoreOverflowTraversal<'a> {}
+
+fn recalc_style_for_node(unsafe_layout_node: UnsafeLayoutNode,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeLayoutNode>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+
+ // Get a real layout node.
+ let node: LayoutNode = unsafe {
+ layout_node_from_unsafe_layout_node(&unsafe_layout_node)
+ };
+
+ // Initialize layout data.
+ //
+ // FIXME(pcwalton): Stop allocating here. Ideally this should just be done by the HTML
+ // parser.
+ node.initialize_layout_data(layout_context.shared.layout_chan.clone());
+
+ // Get the parent node.
+ let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
+ let parent_opt = if opaque_node == layout_context.shared.reflow_root {
+ None
+ } else {
+ node.parent_node()
+ };
+
+ // First, check to see whether we can share a style with someone.
+ let style_sharing_candidate_cache = layout_context.style_sharing_candidate_cache();
+ let sharing_result = unsafe {
+ node.share_style_if_possible(style_sharing_candidate_cache,
+ parent_opt.clone())
+ };
+
+ // Otherwise, match and cascade selectors.
+ match sharing_result {
+ CannotShare(mut shareable) => {
+ let mut applicable_declarations = ApplicableDeclarations::new();
+
+ if node.is_element() {
+ // Perform the CSS selector matching.
+ let stylist = unsafe { &*layout_context.shared.stylist };
+ node.match_node(stylist, &mut applicable_declarations, &mut shareable);
+ }
+
+ // Perform the CSS cascade.
+ unsafe {
+ node.cascade_node(parent_opt,
+ &applicable_declarations,
+ layout_context.applicable_declarations_cache());
+ }
+
+ // Add ourselves to the LRU cache.
+ if shareable {
+ style_sharing_candidate_cache.insert_if_possible(&node);
+ }
+ }
+ StyleWasShared(index) => style_sharing_candidate_cache.touch(index),
+ }
+
+ // Prepare for flow construction by counting the node's children and storing that count.
+ let mut child_count = 0u;
+ for _ in node.children() {
+ child_count += 1;
+ }
+ if child_count != 0 {
+ let mut layout_data_ref = node.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+
+ // It's *very* important that this block is in a separate scope to the block above,
+ // to avoid a data race that can occur (github issue #2308). The block above issues
+ // a borrow on the node layout data. That borrow must be dropped before the child
+ // nodes are actually pushed into the work queue. Otherwise, it's possible for a child
+ // node to get into construct_flows() and move up it's parent hierarchy, which can call
+ // borrow on the layout data before it is dropped from the block above.
+ if child_count != 0 {
+ // Enqueue kids.
+ for kid in node.children() {
+ proxy.push(WorkUnit {
+ fun: recalc_style_for_node,
+ data: layout_node_to_unsafe_layout_node(&kid),
+ });
+ }
+ return
+ }
+
+ // If we got here, we're a leaf. Start construction of flows for this node.
+ construct_flows(unsafe_layout_node, &layout_context)
+}
+
+fn construct_flows(mut unsafe_layout_node: UnsafeLayoutNode,
+ layout_context: &LayoutContext) {
+ loop {
+ // Get a real layout node.
+ let node: LayoutNode = unsafe {
+ layout_node_from_unsafe_layout_node(&unsafe_layout_node)
+ };
+
+ // Construct flows for this node.
+ {
+ let mut flow_constructor = FlowConstructor::new(layout_context);
+ flow_constructor.process(&ThreadSafeLayoutNode::new(&node));
+ }
+
+ // Reset the count of children for the next traversal.
+ //
+ // FIXME(pcwalton): Use children().len() when the implementation of that is efficient.
+ let mut child_count = 0u;
+ for _ in node.children() {
+ child_count += 1
+ }
+ {
+ let mut layout_data_ref = node.mutate_layout_data();
+ match &mut *layout_data_ref {
+ &Some(ref mut layout_data) => {
+ layout_data.data.parallel.children_count.store(child_count as int, Relaxed)
+ }
+ &None => fail!("no layout data"),
+ }
+ }
+
+ // If this is the reflow root, we're done.
+ let opaque_node: OpaqueNode = OpaqueNodeMethods::from_layout_node(&node);
+ if layout_context.shared.reflow_root == opaque_node {
+ break
+ }
+
+ // Otherwise, enqueue the parent.
+ match node.parent_node() {
+ Some(parent) => {
+
+ // No, we're not at the root yet. Then are we the last sibling of our parent?
+ // If so, we can continue on with our parent; otherwise, we've gotta wait.
+ unsafe {
+ match *parent.borrow_layout_data_unchecked() {
+ Some(ref parent_layout_data) => {
+ let parent_layout_data: &mut LayoutDataWrapper = mem::transmute(parent_layout_data);
+ if parent_layout_data.data
+ .parallel
+ .children_count
+ .fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Construct flows for our
+ // parent.
+ unsafe_layout_node = layout_node_to_unsafe_layout_node(&parent)
+ } else {
+ // Get out of here and find another node to work on.
+ break
+ }
+ }
+ None => fail!("no layout data for parent?!"),
+ }
+ }
+ }
+ None => fail!("no parent and weren't at reflow root?!"),
+ }
+ }
+}
+
+fn assign_inline_sizes(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+ let mut assign_inline_sizes_traversal = AssignISizesTraversal {
+ layout_context: &layout_context,
+ };
+ assign_inline_sizes_traversal.run_parallel(unsafe_flow, proxy)
+}
+
+fn assign_block_sizes_and_store_overflow(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+ let mut assign_block_sizes_traversal = AssignBSizesAndStoreOverflowTraversal {
+ layout_context: &layout_context,
+ };
+ assign_block_sizes_traversal.run_parallel(unsafe_flow, proxy)
+}
+
+fn compute_absolute_position(unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let mut had_descendants = false;
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Compute the absolute position for the flow.
+ flow.get_mut().compute_absolute_position();
+
+ // Count the number of absolutely-positioned children, so that we can subtract it from
+ // from `children_and_absolute_descendant_count` to get the number of real children.
+ let mut absolutely_positioned_child_count = 0u;
+ for kid in flow::child_iter(flow.get_mut()) {
+ if kid.is_absolutely_positioned() {
+ absolutely_positioned_child_count += 1;
+ }
+ }
+
+ // Don't enqueue absolutely positioned children.
+ drop(flow::mut_base(flow.get_mut()).parallel
+ .children_and_absolute_descendant_count
+ .fetch_sub(absolutely_positioned_child_count as int,
+ SeqCst));
+
+ // Possibly enqueue the children.
+ for kid in flow::child_iter(flow.get_mut()) {
+ if !kid.is_absolutely_positioned() {
+ had_descendants = true;
+ proxy.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: borrowed_flow_to_unsafe_flow(kid),
+ });
+ }
+ }
+
+ // Possibly enqueue absolute descendants.
+ for absolute_descendant_link in flow::mut_base(flow.get_mut()).abs_descendants.iter() {
+ had_descendants = true;
+ let descendant = absolute_descendant_link;
+ proxy.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: borrowed_flow_to_unsafe_flow(descendant),
+ });
+ }
+
+ // If there were no more descendants, start building the display list.
+ if !had_descendants {
+ build_display_list(mut_owned_flow_to_unsafe_flow(flow),
+ proxy)
+ }
+ }
+}
+
+fn build_display_list(mut unsafe_flow: UnsafeFlow,
+ proxy: &mut WorkerProxy<*const SharedLayoutContext,UnsafeFlow>) {
+ let shared_layout_context = unsafe { &**proxy.user_data() };
+ let layout_context = LayoutContext::new(shared_layout_context);
+
+ loop {
+ unsafe {
+ // Get a real flow.
+ let flow: &mut FlowRef = mem::transmute(&unsafe_flow);
+
+ // Build display lists.
+ flow.get_mut().build_display_list(&layout_context);
+
+ {
+ let base = flow::mut_base(flow.get_mut());
+
+ // Reset the count of children and absolute descendants for the next layout
+ // traversal.
+ let children_and_absolute_descendant_count = base.children.len() +
+ base.abs_descendants.len();
+ base.parallel
+ .children_and_absolute_descendant_count
+ .store(children_and_absolute_descendant_count as int, Relaxed);
+ }
+
+ // Possibly enqueue the parent.
+ let unsafe_parent = if flow.get().is_absolutely_positioned() {
+ match *flow::mut_base(flow.get_mut()).absolute_cb.get() {
+ None => fail!("no absolute containing block for absolutely positioned?!"),
+ Some(ref mut absolute_cb) => {
+ mut_borrowed_flow_to_unsafe_flow(absolute_cb.get_mut())
+ }
+ }
+ } else {
+ flow::mut_base(flow.get_mut()).parallel.parent
+ };
+ if unsafe_parent == null_unsafe_flow() {
+ // We're done!
+ break
+ }
+
+ // No, we're not at the root yet. Then are we the last child
+ // of our parent to finish processing? If so, we can continue
+ // on with our parent; otherwise, we've gotta wait.
+ let parent: &mut FlowRef = mem::transmute(&unsafe_parent);
+ let parent_base = flow::mut_base(parent.get_mut());
+ if parent_base.parallel
+ .children_and_absolute_descendant_count
+ .fetch_sub(1, SeqCst) == 1 {
+ // We were the last child of our parent. Build display lists for our parent.
+ unsafe_flow = unsafe_parent
+ } else {
+ // Stop.
+ break
+ }
+ }
+ }
+}
+
+pub fn recalc_style_for_subtree(root_node: &LayoutNode,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeLayoutNode>) {
+ queue.data = shared_layout_context as *const _;
+
+ // Enqueue the root node.
+ queue.push(WorkUnit {
+ fun: recalc_style_for_node,
+ data: layout_node_to_unsafe_layout_node(root_node),
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
+pub fn traverse_flow_tree_preorder(root: &mut FlowRef,
+ time_profiler_chan: TimeProfilerChan,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
+ queue.data = shared_layout_context as *const _;
+
+ profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
+ queue.push(WorkUnit {
+ fun: assign_inline_sizes,
+ data: mut_owned_flow_to_unsafe_flow(root),
+ })
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
+pub fn build_display_list_for_subtree(root: &mut FlowRef,
+ time_profiler_chan: TimeProfilerChan,
+ shared_layout_context: &SharedLayoutContext,
+ queue: &mut WorkQueue<*const SharedLayoutContext,UnsafeFlow>) {
+ queue.data = shared_layout_context as *const _;
+
+ profile(time::LayoutParallelWarmupCategory, time_profiler_chan, || {
+ queue.push(WorkUnit {
+ fun: compute_absolute_position,
+ data: mut_owned_flow_to_unsafe_flow(root),
+ })
+ });
+
+ queue.run();
+
+ queue.data = ptr::null()
+}
+
diff --git a/components/layout/table.rs b/components/layout/table.rs
new file mode 100644
index 00000000000..98569d68c95
--- /dev/null
+++ b/components/layout/table.rs
@@ -0,0 +1,324 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use block::{ISizeConstraintInput, ISizeConstraintSolution};
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{TableFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use fragment::Fragment;
+use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+use style::computed_values::table_layout;
+
+/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
+/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
+/// not table fragment per CSS 2.1 § 10.5.
+pub struct TableFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::from_node(constructor, node);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> TableFlow {
+ let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ /// Update the corresponding value of self_inline-sizes if a value of kid_inline-sizes has larger value
+ /// than one of self_inline-sizes.
+ pub fn update_col_inline_sizes(self_inline_sizes: &mut Vec<Au>, kid_inline_sizes: &Vec<Au>) -> Au {
+ let mut sum_inline_sizes = Au(0);
+ let mut kid_inline_sizes_it = kid_inline_sizes.iter();
+ for self_inline_size in self_inline_sizes.mut_iter() {
+ match kid_inline_sizes_it.next() {
+ Some(kid_inline_size) => {
+ if *self_inline_size < *kid_inline_size {
+ *self_inline_size = *kid_inline_size;
+ }
+ },
+ None => {}
+ }
+ sum_inline_sizes = sum_inline_sizes + *self_inline_size;
+ }
+ sum_inline_sizes
+ }
+
+ /// Assign block-size for table flow.
+ ///
+ /// TODO(#2014, pcwalton): This probably doesn't handle margin collapse right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
+ }
+
+ pub fn build_display_list_table(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context);
+ }
+}
+
+impl Flow for TableFlow {
+ fn class(&self) -> FlowClass {
+ TableFlowClass
+ }
+
+ fn as_table<'a>(&'a mut self) -> &'a mut TableFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// The specified column inline-sizes are set from column group and the first row for the fixed
+ /// table layout calculation.
+ /// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
+ /// table layout calculation.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+ let mut did_first_row = false;
+
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_proper_table_child());
+
+ if kid.is_table_colgroup() {
+ self.col_inline_sizes.push_all(kid.as_table_colgroup().inline_sizes.as_slice());
+ self.col_min_inline_sizes = self.col_inline_sizes.clone();
+ self.col_pref_inline_sizes = self.col_inline_sizes.clone();
+ } else if kid.is_table_rowgroup() || kid.is_table_row() {
+ // read column inline-sizes from table-row-group/table-row, and assign
+ // inline-size=0 for the columns not defined in column-group
+ // FIXME: need to read inline-sizes from either table-header-group OR
+ // first table-row
+ match self.table_layout {
+ FixedLayout => {
+ let kid_col_inline_sizes = kid.col_inline_sizes();
+ if !did_first_row {
+ did_first_row = true;
+ let mut child_inline_sizes = kid_col_inline_sizes.iter();
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ match child_inline_sizes.next() {
+ Some(child_inline_size) => {
+ if *col_inline_size == Au::new(0) {
+ *col_inline_size = *child_inline_size;
+ }
+ },
+ None => break
+ }
+ }
+ }
+ let num_child_cols = kid_col_inline_sizes.len();
+ let num_cols = self.col_inline_sizes.len();
+ debug!("table until the previous row has {} column(s) and this row has {} column(s)",
+ num_cols, num_child_cols);
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push((*kid_col_inline_sizes)[i]);
+ }
+ },
+ AutoLayout => {
+ min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
+ pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
+
+ // update the number of column inline-sizes from table-rows.
+ let num_cols = self.col_min_inline_sizes.len();
+ let num_child_cols = kid.col_min_inline_sizes().len();
+ debug!("table until the previous row has {} column(s) and this row has {} column(s)",
+ num_cols, num_child_cols);
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push(Au::new(0));
+ let new_kid_min = kid.col_min_inline_sizes()[i];
+ self.col_min_inline_sizes.push( new_kid_min );
+ let new_kid_pref = kid.col_pref_inline_sizes()[i];
+ self.col_pref_inline_sizes.push( new_kid_pref );
+ min_inline_size = min_inline_size + new_kid_min;
+ pref_inline_size = pref_inline_size + new_kid_pref;
+ }
+ }
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
+ geometry::max(min_inline_size, pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let mut num_unspecified_inline_sizes = 0;
+ let mut total_column_inline_size = Au::new(0);
+ for col_inline_size in self.col_inline_sizes.iter() {
+ if *col_inline_size == Au::new(0) {
+ num_unspecified_inline_sizes += 1;
+ } else {
+ total_column_inline_size = total_column_inline_size.add(col_inline_size);
+ }
+ }
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ match self.table_layout {
+ FixedLayout => {
+ // In fixed table layout, we distribute extra space among the unspecified columns if there are
+ // any, or among all the columns if all are specified.
+ if (total_column_inline_size < content_inline_size) && (num_unspecified_inline_sizes == 0) {
+ let ratio = content_inline_size.to_f64().unwrap() / total_column_inline_size.to_f64().unwrap();
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ *col_inline_size = (*col_inline_size).scale_by(ratio);
+ }
+ } else if num_unspecified_inline_sizes != 0 {
+ let extra_column_inline_size = (content_inline_size - total_column_inline_size) / Au::new(num_unspecified_inline_sizes);
+ for col_inline_size in self.col_inline_sizes.mut_iter() {
+ if *col_inline_size == Au(0) {
+ *col_inline_size = extra_column_inline_size;
+ }
+ }
+ }
+ }
+ _ => {}
+ }
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table");
+ self.assign_block_size_table_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableFlow {
+ /// Outputs a debugging string describing this table flow.
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableFlow: {}", self.block_flow)
+ }
+}
+
+/// Table, TableRowGroup, TableRow, TableCell types.
+/// Their inline-sizes are calculated in the same way and do not have margins.
+pub struct InternalTable;
+
+impl ISizeAndMarginsComputer for InternalTable {
+ /// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
+ ///
+ /// CSS Section 10.4: Minimum and Maximum inline-sizes
+ fn compute_used_inline_size(&self,
+ block: &mut BlockFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, ctx);
+ let solution = self.solve_inline_size_constraints(block, &input);
+ self.set_inline_size_constraint_solutions(block, solution);
+ }
+
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ ISizeConstraintSolution::new(input.available_inline_size, Au::new(0), Au::new(0))
+ }
+}
diff --git a/components/layout/table_caption.rs b/components/layout/table_caption.rs
new file mode 100644
index 00000000000..8c1dba3e7ca
--- /dev/null
+++ b/components/layout/table_caption.rs
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableCaptionFlowClass, FlowClass, Flow};
+use wrapper::ThreadSafeLayoutNode;
+
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableCaptionFlow {
+ pub block_flow: BlockFlow,
+}
+
+impl TableCaptionFlow {
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableCaptionFlow {
+ TableCaptionFlow {
+ block_flow: BlockFlow::from_node(constructor, node)
+ }
+ }
+
+ pub fn build_display_list_table_caption(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_caption: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableCaptionFlow {
+ fn class(&self) -> FlowClass {
+ TableCaptionFlowClass
+ }
+
+ fn as_table_caption<'a>(&'a mut self) -> &'a mut TableCaptionFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ self.block_flow.bubble_inline_sizes(ctx);
+ }
+
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_caption");
+ self.block_flow.assign_inline_sizes(ctx);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_caption");
+ self.block_flow.assign_block_size(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableCaptionFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCaptionFlow: {}", self.block_flow)
+ }
+}
diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs
new file mode 100644
index 00000000000..0011cd29fbc
--- /dev/null
+++ b/components/layout/table_cell.rs
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use context::LayoutContext;
+use flow::{TableCellFlowClass, FlowClass, Flow};
+use fragment::Fragment;
+use model::{MaybeAuto};
+use table::InternalTable;
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableCellFlow {
+ /// Data common to all flows.
+ pub block_flow: BlockFlow,
+}
+
+impl TableCellFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableCellFlow {
+ TableCellFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment)
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ pub fn mut_fragment<'a>(&'a mut self) -> &'a mut Fragment {
+ &mut self.block_flow.fragment
+ }
+
+ /// Assign block-size for table-cell flow.
+ ///
+ /// TODO(#2015, pcwalton): This doesn't handle floats right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_cell_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse)
+ }
+
+ pub fn build_display_list_table_cell(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableCellFlow {
+ fn class(&self) -> FlowClass {
+ TableCellFlowClass
+ }
+
+ fn as_table_cell<'a>(&'a mut self) -> &'a mut TableCellFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ /// Minimum/preferred inline-sizes set by this function are used in automatic table layout calculation.
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ self.block_flow.bubble_inline_sizes(ctx);
+ let specified_inline_size = MaybeAuto::from_style(self.block_flow.fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+ if self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size < specified_inline_size {
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = specified_inline_size;
+ }
+ if self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size <
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size {
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size =
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size;
+ }
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent table row.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_cell");
+
+ // The position was set to the column inline-size by the parent flow, table row flow.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
+ self.block_flow.fragment.border_padding.inline_start;
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge,
+ content_inline_size,
+ None);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_cell");
+ self.assign_block_size_table_cell_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableCellFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableCellFlow: {}", self.block_flow)
+ }
+}
diff --git a/components/layout/table_colgroup.rs b/components/layout/table_colgroup.rs
new file mode 100644
index 00000000000..270f55970b2
--- /dev/null
+++ b/components/layout/table_colgroup.rs
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use context::LayoutContext;
+use flow::{BaseFlow, TableColGroupFlowClass, FlowClass, Flow};
+use fragment::{Fragment, TableColumnFragment};
+use model::{MaybeAuto};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableColGroupFlow {
+ /// Data common to all flows.
+ pub base: BaseFlow,
+
+ /// The associated fragment.
+ pub fragment: Option<Fragment>,
+
+ /// The table column fragments
+ pub cols: Vec<Fragment>,
+
+ /// The specified inline-sizes of table columns
+ pub inline_sizes: Vec<Au>,
+}
+
+impl TableColGroupFlow {
+ pub fn from_node_and_fragments(node: &ThreadSafeLayoutNode,
+ fragment: Fragment,
+ fragments: Vec<Fragment>) -> TableColGroupFlow {
+ TableColGroupFlow {
+ base: BaseFlow::new((*node).clone()),
+ fragment: Some(fragment),
+ cols: fragments,
+ inline_sizes: vec!(),
+ }
+ }
+}
+
+impl Flow for TableColGroupFlow {
+ fn class(&self) -> FlowClass {
+ TableColGroupFlowClass
+ }
+
+ fn as_table_colgroup<'a>(&'a mut self) -> &'a mut TableColGroupFlow {
+ self
+ }
+
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ for fragment in self.cols.iter() {
+ // get the specified value from inline-size property
+ let inline_size = MaybeAuto::from_style(fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+
+ let span: int = match fragment.specific {
+ TableColumnFragment(col_fragment) => col_fragment.span.unwrap_or(1),
+ _ => fail!("Other fragment come out in TableColGroupFlow. {:?}", fragment.specific)
+ };
+ for _ in range(0, span) {
+ self.inline_sizes.push(inline_size);
+ }
+ }
+ }
+
+ /// Table column inline-sizes are assigned in table flow and propagated to table row or rowgroup flow.
+ /// Therefore, table colgroup flow does not need to assign its inline-size.
+ fn assign_inline_sizes(&mut self, _ctx: &LayoutContext) {
+ }
+
+ /// Table column do not have block-size.
+ fn assign_block_size(&mut self, _ctx: &LayoutContext) {
+ }
+}
+
+impl fmt::Show for TableColGroupFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.fragment {
+ Some(ref rb) => write!(f, "TableColGroupFlow: {}", rb),
+ None => write!(f, "TableColGroupFlow"),
+ }
+ }
+}
diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs
new file mode 100644
index 00000000000..101f00eb5cc
--- /dev/null
+++ b/components/layout/table_row.rs
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use block::ISizeAndMarginsComputer;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableRowFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow;
+use fragment::Fragment;
+use table::InternalTable;
+use model::{MaybeAuto, Specified, Auto};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableRowFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes.
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+}
+
+impl TableRowFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableRowFlow {
+ TableRowFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableRowFlow {
+ TableRowFlow {
+ block_flow: BlockFlow::from_node(constructor, node),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ fn initialize_offsets(&mut self) -> (Au, Au, Au) {
+ // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
+ // should be updated. Currently, they are set as Au(0).
+ (Au(0), Au(0), Au(0))
+ }
+
+ /// Assign block-size for table-row flow.
+ ///
+ /// TODO(pcwalton): This doesn't handle floats and positioned elements right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ let (block_start_offset, _, _) = self.initialize_offsets();
+
+ let /* mut */ cur_y = block_start_offset;
+
+ // Per CSS 2.1 § 17.5.3, find max_y = max( computed `block-size`, minimum block-size of all cells )
+ let mut max_y = Au::new(0);
+ for kid in self.block_flow.base.child_iter() {
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ {
+ let child_fragment = kid.as_table_cell().fragment();
+ // TODO: Percentage block-size
+ let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(),
+ Au::new(0)).specified_or_zero();
+ max_y =
+ geometry::max(max_y,
+ child_specified_block_size + child_fragment.border_padding.block_start_end());
+ }
+ let child_node = flow::mut_base(kid);
+ child_node.position.start.b = cur_y;
+ max_y = geometry::max(max_y, child_node.position.size.block);
+ }
+
+ let mut block_size = max_y;
+ // TODO: Percentage block-size
+ block_size = match MaybeAuto::from_style(self.block_flow.fragment.style().content_block_size(), Au(0)) {
+ Auto => block_size,
+ Specified(value) => geometry::max(value, block_size)
+ };
+ // cur_y = cur_y + block-size;
+
+ // Assign the block-size of own fragment
+ //
+ // FIXME(pcwalton): Take `cur_y` into account.
+ let mut position = self.block_flow.fragment.border_box;
+ position.size.block = block_size;
+ self.block_flow.fragment.border_box = position;
+ self.block_flow.base.position.size.block = block_size;
+
+ // Assign the block-size of kid fragments, which is the same value as own block-size.
+ for kid in self.block_flow.base.child_iter() {
+ {
+ let kid_fragment = kid.as_table_cell().mut_fragment();
+ let mut position = kid_fragment.border_box;
+ position.size.block = block_size;
+ kid_fragment.border_box = position;
+ }
+ let child_node = flow::mut_base(kid);
+ child_node.position.size.block = block_size;
+ }
+ }
+
+ pub fn build_display_list_table_row(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_row: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableRowFlow {
+ fn class(&self) -> FlowClass {
+ TableRowFlowClass
+ }
+
+ fn as_table_row<'a>(&'a mut self) -> &'a mut TableRowFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
+ /// on this context, all child contexts have had their min/pref inline-sizes set. This function must
+ /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
+ /// responsible for flowing.
+ /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
+ /// The specified column inline-sizes of children cells are used in fixed table layout calculation.
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+ /* find the specified inline_sizes from child table-cell contexts */
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_cell());
+
+ // collect the specified column inline-sizes of cells. These are used in fixed table layout calculation.
+ {
+ let child_fragment = kid.as_table_cell().fragment();
+ let child_specified_inline_size = MaybeAuto::from_style(child_fragment.style().content_inline_size(),
+ Au::new(0)).specified_or_zero();
+ self.col_inline_sizes.push(child_specified_inline_size);
+ }
+
+ // collect min_inline-size & pref_inline-size of children cells for automatic table layout calculation.
+ let child_base = flow::mut_base(kid);
+ self.col_min_inline_sizes.push(child_base.intrinsic_inline_sizes.minimum_inline_size);
+ self.col_pref_inline_sizes.push(child_base.intrinsic_inline_sizes.preferred_inline_size);
+ min_inline_size = min_inline_size + child_base.intrinsic_inline_sizes.minimum_inline_size;
+ pref_inline_size = pref_inline_size + child_base.intrinsic_inline_sizes.preferred_inline_size;
+ }
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
+ pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When called
+ /// on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_row");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+ // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be border-inline-start
+ let inline_start_content_edge = Au::new(0);
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, Au(0), Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_row");
+ self.assign_block_size_table_row_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableRowFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowFlow: {}", self.block_flow.fragment)
+ }
+}
diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs
new file mode 100644
index 00000000000..48f9d376af3
--- /dev/null
+++ b/components/layout/table_rowgroup.rs
@@ -0,0 +1,208 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::BlockFlow;
+use block::ISizeAndMarginsComputer;
+use construct::FlowConstructor;
+use context::LayoutContext;
+use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use flow;
+use fragment::Fragment;
+use table::{InternalTable, TableFlow};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+
+/// A table formatting context.
+pub struct TableRowGroupFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Column min inline-sizes.
+ pub col_min_inline_sizes: Vec<Au>,
+
+ /// Column pref inline-sizes.
+ pub col_pref_inline_sizes: Vec<Au>,
+}
+
+impl TableRowGroupFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableRowGroupFlow {
+ TableRowGroupFlow {
+ block_flow: BlockFlow::from_node_and_fragment(node, fragment),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableRowGroupFlow {
+ TableRowGroupFlow {
+ block_flow: BlockFlow::from_node(constructor, node),
+ col_inline_sizes: vec!(),
+ col_min_inline_sizes: vec!(),
+ col_pref_inline_sizes: vec!(),
+ }
+ }
+
+ pub fn fragment<'a>(&'a mut self) -> &'a Fragment {
+ &self.block_flow.fragment
+ }
+
+ fn initialize_offsets(&mut self) -> (Au, Au, Au) {
+ // TODO: If border-collapse: collapse, block-start_offset, block-end_offset, and inline-start_offset
+ // should be updated. Currently, they are set as Au(0).
+ (Au(0), Au(0), Au(0))
+ }
+
+ /// Assign block-size for table-rowgroup flow.
+ ///
+ /// FIXME(pcwalton): This doesn't handle floats right.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_rowgroup_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ let (block_start_offset, _, _) = self.initialize_offsets();
+
+ let mut cur_y = block_start_offset;
+
+ for kid in self.block_flow.base.child_iter() {
+ kid.assign_block_size_for_inorder_child_if_necessary(layout_context);
+
+ let child_node = flow::mut_base(kid);
+ child_node.position.start.b = cur_y;
+ cur_y = cur_y + child_node.position.size.block;
+ }
+
+ let block_size = cur_y - block_start_offset;
+
+ let mut position = self.block_flow.fragment.border_box;
+ position.size.block = block_size;
+ self.block_flow.fragment.border_box = position;
+ self.block_flow.base.position.size.block = block_size;
+ }
+
+ pub fn build_display_list_table_rowgroup(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_rowgroup: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context)
+ }
+}
+
+impl Flow for TableRowGroupFlow {
+ fn class(&self) -> FlowClass {
+ TableRowGroupFlowClass
+ }
+
+ fn as_table_rowgroup<'a>(&'a mut self) -> &'a mut TableRowGroupFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn col_inline_sizes<'a>(&'a mut self) -> &'a mut Vec<Au> {
+ &mut self.col_inline_sizes
+ }
+
+ fn col_min_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_min_inline_sizes
+ }
+
+ fn col_pref_inline_sizes<'a>(&'a self) -> &'a Vec<Au> {
+ &self.col_pref_inline_sizes
+ }
+
+ /// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When called
+ /// on this context, all child contexts have had their min/pref inline-sizes set. This function must
+ /// decide min/pref inline-sizes based on child context inline-sizes and dimensions of any fragments it is
+ /// responsible for flowing.
+ /// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
+ /// Also, this function finds the specified column inline-sizes from the first row.
+ /// Those are used in fixed table layout calculation
+ fn bubble_inline_sizes(&mut self, _: &LayoutContext) {
+ let mut min_inline_size = Au(0);
+ let mut pref_inline_size = Au(0);
+
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_row());
+
+ // calculate min_inline-size & pref_inline-size for automatic table layout calculation
+ // 'self.col_min_inline-sizes' collects the maximum value of cells' min-inline-sizes for each column.
+ // 'self.col_pref_inline-sizes' collects the maximum value of cells' pref-inline-sizes for each column.
+ if self.col_inline_sizes.is_empty() { // First Row
+ assert!(self.col_min_inline_sizes.is_empty() && self.col_pref_inline_sizes.is_empty());
+ // 'self.col_inline-sizes' collects the specified column inline-sizes from the first table-row for fixed table layout calculation.
+ self.col_inline_sizes = kid.col_inline_sizes().clone();
+ self.col_min_inline_sizes = kid.col_min_inline_sizes().clone();
+ self.col_pref_inline_sizes = kid.col_pref_inline_sizes().clone();
+ } else {
+ min_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_min_inline_sizes, kid.col_min_inline_sizes());
+ pref_inline_size = TableFlow::update_col_inline_sizes(&mut self.col_pref_inline_sizes, kid.col_pref_inline_sizes());
+
+ // update the number of column inline-sizes from table-rows.
+ let num_cols = self.col_inline_sizes.len();
+ let num_child_cols = kid.col_min_inline_sizes().len();
+ for i in range(num_cols, num_child_cols) {
+ self.col_inline_sizes.push(Au::new(0));
+ let new_kid_min = kid.col_min_inline_sizes()[i];
+ self.col_min_inline_sizes.push(kid.col_min_inline_sizes()[i]);
+ let new_kid_pref = kid.col_pref_inline_sizes()[i];
+ self.col_pref_inline_sizes.push(kid.col_pref_inline_sizes()[i]);
+ min_inline_size = min_inline_size + new_kid_min;
+ pref_inline_size = pref_inline_size + new_kid_pref;
+ }
+ }
+ }
+
+ self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
+ self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = geometry::max(min_inline_size,
+ pref_inline_size);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow", "table_rowgroup");
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+ // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be
+ // the border width on the inline-start side.
+ let inline_start_content_edge = Au::new(0);
+ let content_inline_size = containing_block_inline_size;
+
+ let inline_size_computer = InternalTable;
+ inline_size_computer.compute_used_inline_size(&mut self.block_flow, ctx, containing_block_inline_size);
+
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, Some(self.col_inline_sizes.clone()));
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ debug!("assign_block_size: assigning block_size for table_rowgroup");
+ self.assign_block_size_table_rowgroup_base(ctx);
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableRowGroupFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "TableRowGroupFlow: {}", self.block_flow.fragment)
+ }
+}
diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs
new file mode 100644
index 00000000000..2084ef52bdc
--- /dev/null
+++ b/components/layout/table_wrapper.rs
@@ -0,0 +1,325 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! CSS table formatting contexts.
+
+#![deny(unsafe_block)]
+
+use block::{BlockFlow, MarginsMayNotCollapse, ISizeAndMarginsComputer};
+use block::{ISizeConstraintInput, ISizeConstraintSolution};
+use construct::FlowConstructor;
+use context::LayoutContext;
+use floats::FloatKind;
+use flow::{TableWrapperFlowClass, FlowClass, Flow, ImmutableFlowUtils};
+use fragment::Fragment;
+use model::{Specified, Auto, specified};
+use wrapper::ThreadSafeLayoutNode;
+
+use servo_util::geometry::Au;
+use servo_util::geometry;
+use std::fmt;
+use style::computed_values::table_layout;
+
+pub enum TableLayout {
+ FixedLayout,
+ AutoLayout
+}
+
+/// A table wrapper flow based on a block formatting context.
+pub struct TableWrapperFlow {
+ pub block_flow: BlockFlow,
+
+ /// Column inline-sizes
+ pub col_inline_sizes: Vec<Au>,
+
+ /// Table-layout property
+ pub table_layout: TableLayout,
+}
+
+impl TableWrapperFlow {
+ pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
+ fragment: Fragment)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::from_node_and_fragment(node, fragment);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::from_node(constructor, node);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn float_from_node(constructor: &mut FlowConstructor,
+ node: &ThreadSafeLayoutNode,
+ float_kind: FloatKind)
+ -> TableWrapperFlow {
+ let mut block_flow = BlockFlow::float_from_node(constructor, node, float_kind);
+ let table_layout = if block_flow.fragment().style().get_table().table_layout ==
+ table_layout::fixed {
+ FixedLayout
+ } else {
+ AutoLayout
+ };
+ TableWrapperFlow {
+ block_flow: block_flow,
+ col_inline_sizes: vec!(),
+ table_layout: table_layout
+ }
+ }
+
+ pub fn is_float(&self) -> bool {
+ self.block_flow.float.is_some()
+ }
+
+ /// Assign block-size for table-wrapper flow.
+ /// `Assign block-size` of table-wrapper flow follows a similar process to that of block flow.
+ ///
+ /// inline(always) because this is only ever called by in-order or non-in-order top-level
+ /// methods
+ #[inline(always)]
+ fn assign_block_size_table_wrapper_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
+ self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
+ }
+
+ pub fn build_display_list_table_wrapper(&mut self, layout_context: &LayoutContext) {
+ debug!("build_display_list_table_wrapper: same process as block flow");
+ self.block_flow.build_display_list_block(layout_context);
+ }
+}
+
+impl Flow for TableWrapperFlow {
+ fn class(&self) -> FlowClass {
+ TableWrapperFlowClass
+ }
+
+ fn as_table_wrapper<'a>(&'a mut self) -> &'a mut TableWrapperFlow {
+ self
+ }
+
+ fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ /* Recursively (bottom-up) determine the context's preferred and
+ minimum inline_sizes. When called on this context, all child contexts
+ have had their min/pref inline_sizes set. This function must decide
+ min/pref inline_sizes based on child context inline_sizes and dimensions of
+ any fragments it is responsible for flowing. */
+
+ fn bubble_inline_sizes(&mut self, ctx: &LayoutContext) {
+ // get column inline-sizes info from table flow
+ for kid in self.block_flow.base.child_iter() {
+ assert!(kid.is_table_caption() || kid.is_table());
+
+ if kid.is_table() {
+ self.col_inline_sizes.push_all(kid.as_table().col_inline_sizes.as_slice());
+ }
+ }
+
+ self.block_flow.bubble_inline_sizes(ctx);
+ }
+
+ /// Recursively (top-down) determines the actual inline-size of child contexts and fragments. When
+ /// called on this context, the context has had its inline-size set by the parent context.
+ ///
+ /// Dual fragments consume some inline-size first, and the remainder is assigned to all child (block)
+ /// contexts.
+ fn assign_inline_sizes(&mut self, ctx: &LayoutContext) {
+ debug!("assign_inline_sizes({}): assigning inline_size for flow",
+ if self.is_float() {
+ "floated table_wrapper"
+ } else {
+ "table_wrapper"
+ });
+
+ // The position was set to the containing block by the flow's parent.
+ let containing_block_inline_size = self.block_flow.base.position.size.inline;
+
+ let inline_size_computer = TableWrapper;
+ inline_size_computer.compute_used_inline_size_table_wrapper(self, ctx, containing_block_inline_size);
+
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
+ let content_inline_size = self.block_flow.fragment.border_box.size.inline;
+
+ match self.table_layout {
+ FixedLayout | _ if self.is_float() =>
+ self.block_flow.base.position.size.inline = content_inline_size,
+ _ => {}
+ }
+
+ // In case of fixed layout, column inline-sizes are calculated in table flow.
+ let assigned_col_inline_sizes = match self.table_layout {
+ FixedLayout => None,
+ AutoLayout => Some(self.col_inline_sizes.clone())
+ };
+ self.block_flow.propagate_assigned_inline_size_to_children(inline_start_content_edge, content_inline_size, assigned_col_inline_sizes);
+ }
+
+ fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {
+ if self.is_float() {
+ debug!("assign_block_size_float: assigning block_size for floated table_wrapper");
+ self.block_flow.assign_block_size_float(ctx);
+ } else {
+ debug!("assign_block_size: assigning block_size for table_wrapper");
+ self.assign_block_size_table_wrapper_base(ctx);
+ }
+ }
+
+ fn compute_absolute_position(&mut self) {
+ self.block_flow.compute_absolute_position()
+ }
+}
+
+impl fmt::Show for TableWrapperFlow {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.is_float() {
+ write!(f, "TableWrapperFlow(Float): {}", self.block_flow.fragment)
+ } else {
+ write!(f, "TableWrapperFlow: {}", self.block_flow.fragment)
+ }
+ }
+}
+
+struct TableWrapper;
+
+impl TableWrapper {
+ fn compute_used_inline_size_table_wrapper(&self,
+ table_wrapper: &mut TableWrapperFlow,
+ ctx: &LayoutContext,
+ parent_flow_inline_size: Au) {
+ let input = self.compute_inline_size_constraint_inputs_table_wrapper(table_wrapper,
+ parent_flow_inline_size,
+ ctx);
+
+ let solution = self.solve_inline_size_constraints(&mut table_wrapper.block_flow, &input);
+
+ self.set_inline_size_constraint_solutions(&mut table_wrapper.block_flow, solution);
+ self.set_flow_x_coord_if_necessary(&mut table_wrapper.block_flow, solution);
+ }
+
+ fn compute_inline_size_constraint_inputs_table_wrapper(&self,
+ table_wrapper: &mut TableWrapperFlow,
+ parent_flow_inline_size: Au,
+ ctx: &LayoutContext)
+ -> ISizeConstraintInput {
+ let mut input = self.compute_inline_size_constraint_inputs(&mut table_wrapper.block_flow,
+ parent_flow_inline_size,
+ ctx);
+ let computed_inline_size = match table_wrapper.table_layout {
+ FixedLayout => {
+ let fixed_cells_inline_size = table_wrapper.col_inline_sizes.iter().fold(Au(0),
+ |sum, inline_size| sum.add(inline_size));
+
+ let mut computed_inline_size = input.computed_inline_size.specified_or_zero();
+ let style = table_wrapper.block_flow.fragment.style();
+
+ // Get inline-start and inline-end paddings, borders for table.
+ // We get these values from the fragment's style since table_wrapper doesn't have it's own border or padding.
+ // input.available_inline-size is same as containing_block_inline-size in table_wrapper.
+ let padding = style.logical_padding();
+ let border = style.logical_border_width();
+ let padding_and_borders =
+ specified(padding.inline_start, input.available_inline_size) +
+ specified(padding.inline_end, input.available_inline_size) +
+ border.inline_start +
+ border.inline_end;
+ // Compare border-edge inline-sizes. Because fixed_cells_inline-size indicates content-inline-size,
+ // padding and border values are added to fixed_cells_inline-size.
+ computed_inline_size = geometry::max(
+ fixed_cells_inline_size + padding_and_borders, computed_inline_size);
+ computed_inline_size
+ },
+ AutoLayout => {
+ // Automatic table layout is calculated according to CSS 2.1 § 17.5.2.2.
+ let mut cap_min = Au(0);
+ let mut cols_min = Au(0);
+ let mut cols_max = Au(0);
+ let mut col_min_inline_sizes = &vec!();
+ let mut col_pref_inline_sizes = &vec!();
+ for kid in table_wrapper.block_flow.base.child_iter() {
+ if kid.is_table_caption() {
+ cap_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
+ } else {
+ assert!(kid.is_table());
+ cols_min = kid.as_block().base.intrinsic_inline_sizes.minimum_inline_size;
+ cols_max = kid.as_block().base.intrinsic_inline_sizes.preferred_inline_size;
+ col_min_inline_sizes = kid.col_min_inline_sizes();
+ col_pref_inline_sizes = kid.col_pref_inline_sizes();
+ }
+ }
+ // 'extra_inline-size': difference between the calculated table inline-size and minimum inline-size
+ // required by all columns. It will be distributed over the columns.
+ let (inline_size, extra_inline_size) = match input.computed_inline_size {
+ Auto => {
+ if input.available_inline_size > geometry::max(cols_max, cap_min) {
+ if cols_max > cap_min {
+ table_wrapper.col_inline_sizes = col_pref_inline_sizes.clone();
+ (cols_max, Au(0))
+ } else {
+ (cap_min, cap_min - cols_min)
+ }
+ } else {
+ let max = if cols_min >= input.available_inline_size && cols_min >= cap_min {
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
+ cols_min
+ } else {
+ geometry::max(input.available_inline_size, cap_min)
+ };
+ (max, max - cols_min)
+ }
+ },
+ Specified(inline_size) => {
+ let max = if cols_min >= inline_size && cols_min >= cap_min {
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.clone();
+ cols_min
+ } else {
+ geometry::max(inline_size, cap_min)
+ };
+ (max, max - cols_min)
+ }
+ };
+ // The extra inline-size is distributed over the columns
+ if extra_inline_size > Au(0) {
+ let cell_len = table_wrapper.col_inline_sizes.len() as f64;
+ table_wrapper.col_inline_sizes = col_min_inline_sizes.iter().map(|inline_size| {
+ inline_size + extra_inline_size.scale_by(1.0 / cell_len)
+ }).collect();
+ }
+ inline_size
+ }
+ };
+ input.computed_inline_size = Specified(computed_inline_size);
+ input
+ }
+}
+
+impl ISizeAndMarginsComputer for TableWrapper {
+ /// Solve the inline-size and margins constraints for this block flow.
+ fn solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput)
+ -> ISizeConstraintSolution {
+ self.solve_block_inline_size_constraints(block, input)
+ }
+}
diff --git a/components/layout/text.rs b/components/layout/text.rs
new file mode 100644
index 00000000000..e90272e218a
--- /dev/null
+++ b/components/layout/text.rs
@@ -0,0 +1,327 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Text layout.
+
+#![deny(unsafe_block)]
+
+use flow::Flow;
+use fragment::{Fragment, ScannedTextFragment, ScannedTextFragmentInfo, UnscannedTextFragment};
+
+use gfx::font::{FontMetrics, FontStyle, RunMetrics};
+use gfx::font_context::FontContext;
+use gfx::text::glyph::CharIndex;
+use gfx::text::text_run::TextRun;
+use gfx::text::util::{CompressWhitespaceNewline, transform_text, CompressNone};
+use servo_util::geometry::Au;
+use servo_util::logical_geometry::{LogicalSize, WritingMode};
+use servo_util::range::Range;
+use style::ComputedValues;
+use style::computed_values::{font_family, line_height, text_orientation, white_space};
+use sync::Arc;
+
+struct NewLinePositions {
+ new_line_pos: Vec<CharIndex>,
+}
+
+// A helper function.
+fn can_coalesce_text_nodes(fragments: &[Fragment], left_i: uint, right_i: uint) -> bool {
+ assert!(left_i != right_i);
+ fragments[left_i].can_merge_with_fragment(&fragments[right_i])
+}
+
+/// A stack-allocated object for scanning an inline flow into `TextRun`-containing `TextFragment`s.
+pub struct TextRunScanner {
+ pub clump: Range<CharIndex>,
+}
+
+impl TextRunScanner {
+ pub fn new() -> TextRunScanner {
+ TextRunScanner {
+ clump: Range::empty(),
+ }
+ }
+
+ pub fn scan_for_runs(&mut self, font_context: &mut FontContext, flow: &mut Flow) {
+ {
+ let inline = flow.as_immutable_inline();
+ debug!("TextRunScanner: scanning {:u} fragments for text runs...", inline.fragments.len());
+ }
+
+ let fragments = &mut flow.as_inline().fragments;
+
+ let mut last_whitespace = true;
+ let mut new_fragments = Vec::new();
+ for fragment_i in range(0, fragments.fragments.len()) {
+ debug!("TextRunScanner: considering fragment: {:u}", fragment_i);
+ if fragment_i > 0 && !can_coalesce_text_nodes(fragments.fragments.as_slice(), fragment_i - 1, fragment_i) {
+ last_whitespace = self.flush_clump_to_list(font_context,
+ fragments.fragments.as_slice(),
+ &mut new_fragments,
+ last_whitespace);
+ }
+
+ self.clump.extend_by(CharIndex(1));
+ }
+
+ // Handle remaining clumps.
+ if self.clump.length() > CharIndex(0) {
+ drop(self.flush_clump_to_list(font_context,
+ fragments.fragments.as_slice(),
+ &mut new_fragments,
+ last_whitespace))
+ }
+
+ debug!("TextRunScanner: swapping out fragments.");
+
+ fragments.fragments = new_fragments;
+ }
+
+ /// A "clump" is a range of inline flow leaves that can be merged together into a single
+ /// fragment. Adjacent text with the same style can be merged, and nothing else can.
+ ///
+ /// The flow keeps track of the fragments contained by all non-leaf DOM nodes. This is necessary
+ /// for correct painting order. Since we compress several leaf fragments here, the mapping must
+ /// be adjusted.
+ ///
+ /// FIXME(#2267, pcwalton): Stop cloning fragments. Instead we will need to replace each
+ /// `in_fragment` with some smaller stub.
+ fn flush_clump_to_list(&mut self,
+ font_context: &mut FontContext,
+ in_fragments: &[Fragment],
+ out_fragments: &mut Vec<Fragment>,
+ last_whitespace: bool)
+ -> bool {
+ assert!(self.clump.length() > CharIndex(0));
+
+ debug!("TextRunScanner: flushing fragments in range={}", self.clump);
+ let is_singleton = self.clump.length() == CharIndex(1);
+
+ let is_text_clump = match in_fragments[self.clump.begin().to_uint()].specific {
+ UnscannedTextFragment(_) => true,
+ _ => false,
+ };
+
+ let mut new_whitespace = last_whitespace;
+ match (is_singleton, is_text_clump) {
+ (false, false) => {
+ fail!("WAT: can't coalesce non-text nodes in flush_clump_to_list()!")
+ }
+ (true, false) => {
+ // FIXME(pcwalton): Stop cloning fragments, as above.
+ debug!("TextRunScanner: pushing single non-text fragment in range: {}", self.clump);
+ let new_fragment = in_fragments[self.clump.begin().to_uint()].clone();
+ out_fragments.push(new_fragment)
+ },
+ (true, true) => {
+ let old_fragment = &in_fragments[self.clump.begin().to_uint()];
+ let text = match old_fragment.specific {
+ UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
+ _ => fail!("Expected an unscanned text fragment!"),
+ };
+
+ let font_style = old_fragment.font_style();
+
+ let compression = match old_fragment.white_space() {
+ white_space::normal => CompressWhitespaceNewline,
+ white_space::pre => CompressNone,
+ };
+
+ let mut new_line_pos = vec![];
+
+ let (transformed_text, whitespace) = transform_text(text.as_slice(),
+ compression,
+ last_whitespace,
+ &mut new_line_pos);
+
+ new_whitespace = whitespace;
+
+ if transformed_text.len() > 0 {
+ // TODO(#177): Text run creation must account for the renderability of text by
+ // font group fonts. This is probably achieved by creating the font group above
+ // and then letting `FontGroup` decide which `Font` to stick into the text run.
+ let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
+ let run = box fontgroup.create_textrun(
+ transformed_text.clone());
+
+ debug!("TextRunScanner: pushing single text fragment in range: {} ({})",
+ self.clump,
+ *text);
+ let range = Range::new(CharIndex(0), run.char_len());
+ let new_metrics = run.metrics_for_range(&range);
+ let bounding_box_size = bounding_box_for_run_metrics(
+ &new_metrics, old_fragment.style.writing_mode);
+ let new_text_fragment_info = ScannedTextFragmentInfo::new(Arc::new(run), range);
+ let mut new_fragment = old_fragment.transform(
+ bounding_box_size, ScannedTextFragment(new_text_fragment_info));
+ new_fragment.new_line_pos = new_line_pos;
+ out_fragments.push(new_fragment)
+ }
+ },
+ (false, true) => {
+ // TODO(#177): Text run creation must account for the renderability of text by
+ // font group fonts. This is probably achieved by creating the font group above
+ // and then letting `FontGroup` decide which `Font` to stick into the text run.
+ let in_fragment = &in_fragments[self.clump.begin().to_uint()];
+ let font_style = in_fragment.font_style();
+ let fontgroup = font_context.get_layout_font_group_for_style(&font_style);
+
+ let compression = match in_fragment.white_space() {
+ white_space::normal => CompressWhitespaceNewline,
+ white_space::pre => CompressNone,
+ };
+
+ let mut new_line_positions: Vec<NewLinePositions> = vec![];
+
+ // First, transform/compress text of all the nodes.
+ let mut last_whitespace_in_clump = new_whitespace;
+ let transformed_strs: Vec<String> = Vec::from_fn(self.clump.length().to_uint(), |i| {
+ let idx = CharIndex(i as int) + self.clump.begin();
+ let in_fragment = match in_fragments[idx.to_uint()].specific {
+ UnscannedTextFragment(ref text_fragment_info) => &text_fragment_info.text,
+ _ => fail!("Expected an unscanned text fragment!"),
+ };
+
+ let mut new_line_pos = vec![];
+
+ let (new_str, new_whitespace) = transform_text(in_fragment.as_slice(),
+ compression,
+ last_whitespace_in_clump,
+ &mut new_line_pos);
+ new_line_positions.push(NewLinePositions { new_line_pos: new_line_pos });
+
+ last_whitespace_in_clump = new_whitespace;
+ new_str
+ });
+ new_whitespace = last_whitespace_in_clump;
+
+ // Next, concatenate all of the transformed strings together, saving the new
+ // character indices.
+ let mut run_str = String::new();
+ let mut new_ranges: Vec<Range<CharIndex>> = vec![];
+ let mut char_total = CharIndex(0);
+ for i in range(0, transformed_strs.len() as int) {
+ let added_chars = CharIndex(transformed_strs[i as uint].as_slice().char_len() as int);
+ new_ranges.push(Range::new(char_total, added_chars));
+ run_str.push_str(transformed_strs[i as uint].as_slice());
+ char_total = char_total + added_chars;
+ }
+
+ // Now create the run.
+ // TextRuns contain a cycle which is usually resolved by the teardown
+ // sequence. If no clump takes ownership, however, it will leak.
+ let clump = self.clump;
+ let run = if clump.length() != CharIndex(0) && run_str.len() > 0 {
+ Some(Arc::new(box TextRun::new(
+ &mut *fontgroup.fonts[0].borrow_mut(),
+ run_str.to_string())))
+ } else {
+ None
+ };
+
+ // Make new fragments with the run and adjusted text indices.
+ debug!("TextRunScanner: pushing fragment(s) in range: {}", self.clump);
+ for i in clump.each_index() {
+ let logical_offset = i - self.clump.begin();
+ let range = new_ranges[logical_offset.to_uint()];
+ if range.length() == CharIndex(0) {
+ debug!("Elided an `UnscannedTextFragment` because it was zero-length after \
+ compression; {}", in_fragments[i.to_uint()]);
+ continue
+ }
+
+ let new_text_fragment_info = ScannedTextFragmentInfo::new(run.get_ref().clone(), range);
+ let old_fragment = &in_fragments[i.to_uint()];
+ let new_metrics = new_text_fragment_info.run.metrics_for_range(&range);
+ let bounding_box_size = bounding_box_for_run_metrics(
+ &new_metrics, old_fragment.style.writing_mode);
+ let mut new_fragment = old_fragment.transform(
+ bounding_box_size, ScannedTextFragment(new_text_fragment_info));
+ new_fragment.new_line_pos = new_line_positions[logical_offset.to_uint()].new_line_pos.clone();
+ out_fragments.push(new_fragment)
+ }
+ }
+ } // End of match.
+
+ let end = self.clump.end(); // FIXME: borrow checker workaround
+ self.clump.reset(end, CharIndex(0));
+
+ new_whitespace
+ } // End of `flush_clump_to_list`.
+}
+
+
+#[inline]
+fn bounding_box_for_run_metrics(metrics: &RunMetrics, writing_mode: WritingMode)
+ -> LogicalSize<Au> {
+
+ // This does nothing, but it will fail to build
+ // when more values are added to the `text-orientation` CSS property.
+ // This will be a reminder to update the code below.
+ let dummy: Option<text_orientation::T> = None;
+ match dummy {
+ Some(text_orientation::sideways_right) |
+ Some(text_orientation::sideways_left) |
+ Some(text_orientation::sideways) |
+ None => {}
+ }
+
+ // In vertical sideways or horizontal upgright text,
+ // the "width" of text metrics is always inline
+ // This will need to be updated when other text orientations are supported.
+ LogicalSize::new(
+ writing_mode,
+ metrics.bounding_box.size.width,
+ metrics.bounding_box.size.height)
+
+}
+
+/// Returns the metrics of the font represented by the given `FontStyle`, respectively.
+///
+/// `#[inline]` because often the caller only needs a few fields from the font metrics.
+#[inline]
+pub fn font_metrics_for_style(font_context: &mut FontContext, font_style: &FontStyle)
+ -> FontMetrics {
+ let fontgroup = font_context.get_layout_font_group_for_style(font_style);
+ fontgroup.fonts[0].borrow().metrics.clone()
+}
+
+/// Converts a computed style to a font style used for rendering.
+///
+/// FIXME(pcwalton): This should not be necessary; just make the font part of the style sharable
+/// with the display list somehow. (Perhaps we should use an ARC.)
+pub fn computed_style_to_font_style(style: &ComputedValues) -> FontStyle {
+ debug!("(font style) start");
+
+ // FIXME: Too much allocation here.
+ let mut font_families = style.get_font().font_family.iter().map(|family| {
+ match *family {
+ font_family::FamilyName(ref name) => (*name).clone(),
+ }
+ });
+ debug!("(font style) font families: `{:?}`", font_families);
+
+ let font_size = style.get_font().font_size.to_f64().unwrap() / 60.0;
+ debug!("(font style) font size: `{:f}px`", font_size);
+
+ FontStyle {
+ pt_size: font_size,
+ weight: style.get_font().font_weight,
+ style: style.get_font().font_style,
+ families: font_families.collect(),
+ }
+}
+
+/// Returns the line block-size needed by the given computed style and font size.
+pub fn line_height_from_style(style: &ComputedValues, metrics: &FontMetrics) -> Au {
+ let font_size = style.get_font().font_size;
+ let from_inline = match style.get_inheritedbox().line_height {
+ line_height::Normal => metrics.line_gap,
+ line_height::Number(l) => font_size.scale_by(l),
+ line_height::Length(l) => l
+ };
+ let minimum = style.get_inheritedbox()._servo_minimum_line_height;
+ Au::max(from_inline, minimum)
+}
+
diff --git a/components/layout/util.rs b/components/layout/util.rs
new file mode 100644
index 00000000000..a0e466e8875
--- /dev/null
+++ b/components/layout/util.rs
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use construct::{ConstructionResult, NoConstructionResult};
+use incremental::RestyleDamage;
+use parallel::DomParallelInfo;
+use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode};
+
+use gfx::display_list::OpaqueNode;
+use gfx;
+use libc::uintptr_t;
+use script::dom::bindings::js::JS;
+use script::dom::bindings::utils::Reflectable;
+use script::dom::node::{Node, SharedLayoutData};
+use script::layout_interface::{LayoutChan, UntrustedNodeAddress, TrustedNodeAddress};
+use std::mem;
+use std::cell::{Ref, RefMut};
+use style::ComputedValues;
+use style;
+use sync::Arc;
+
+/// Data that layout associates with a node.
+pub struct PrivateLayoutData {
+ /// The results of CSS styling for this node's `before` pseudo-element, if any.
+ pub before_style: Option<Arc<ComputedValues>>,
+
+ /// The results of CSS styling for this node's `after` pseudo-element, if any.
+ pub after_style: Option<Arc<ComputedValues>>,
+
+ /// Description of how to account for recent style changes.
+ pub restyle_damage: Option<RestyleDamage>,
+
+ /// The current results of flow construction for this node. This is either a flow or a
+ /// `ConstructionItem`. See comments in `construct.rs` for more details.
+ pub flow_construction_result: ConstructionResult,
+
+ pub before_flow_construction_result: ConstructionResult,
+
+ pub after_flow_construction_result: ConstructionResult,
+
+ /// Information needed during parallel traversals.
+ pub parallel: DomParallelInfo,
+}
+
+impl PrivateLayoutData {
+ /// Creates new layout data.
+ pub fn new() -> PrivateLayoutData {
+ PrivateLayoutData {
+ before_style: None,
+ after_style: None,
+ restyle_damage: None,
+ flow_construction_result: NoConstructionResult,
+ before_flow_construction_result: NoConstructionResult,
+ after_flow_construction_result: NoConstructionResult,
+ parallel: DomParallelInfo::new(),
+ }
+ }
+}
+
+pub struct LayoutDataWrapper {
+ pub chan: Option<LayoutChan>,
+ pub shared_data: SharedLayoutData,
+ pub data: Box<PrivateLayoutData>,
+}
+
+/// A trait that allows access to the layout data of a DOM node.
+pub trait LayoutDataAccess {
+ /// Borrows the layout data without checks.
+ unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper>;
+ /// Borrows the layout data immutably. Fails on a conflicting borrow.
+ fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>>;
+ /// Borrows the layout data mutably. Fails on a conflicting borrow.
+ fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>>;
+}
+
+impl<'ln> LayoutDataAccess for LayoutNode<'ln> {
+ #[inline(always)]
+ unsafe fn borrow_layout_data_unchecked(&self) -> *const Option<LayoutDataWrapper> {
+ mem::transmute(self.get().layout_data.borrow_unchecked())
+ }
+
+ #[inline(always)]
+ fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow())
+ }
+ }
+
+ #[inline(always)]
+ fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow_mut())
+ }
+ }
+}
+
+pub trait OpaqueNodeMethods {
+ /// Converts a DOM node (layout view) to an `OpaqueNode`.
+ fn from_layout_node(node: &LayoutNode) -> Self;
+
+ /// Converts a thread-safe DOM node (layout view) to an `OpaqueNode`.
+ fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> Self;
+
+ /// Converts a DOM node (script view) to an `OpaqueNode`.
+ fn from_script_node(node: TrustedNodeAddress) -> Self;
+
+ /// Converts a DOM node to an `OpaqueNode'.
+ fn from_jsmanaged(node: &JS<Node>) -> Self;
+
+ /// Converts this node to an `UntrustedNodeAddress`. An `UntrustedNodeAddress` is just the type
+ /// of node that script expects to receive in a hit test.
+ fn to_untrusted_node_address(&self) -> UntrustedNodeAddress;
+}
+
+impl OpaqueNodeMethods for OpaqueNode {
+ fn from_layout_node(node: &LayoutNode) -> OpaqueNode {
+ unsafe {
+ OpaqueNodeMethods::from_jsmanaged(node.get_jsmanaged())
+ }
+ }
+
+ fn from_thread_safe_layout_node(node: &ThreadSafeLayoutNode) -> OpaqueNode {
+ unsafe {
+ let abstract_node = node.get_jsmanaged();
+ let ptr: uintptr_t = abstract_node.reflector().get_jsobject() as uint;
+ OpaqueNode(ptr)
+ }
+ }
+
+ fn from_script_node(node: TrustedNodeAddress) -> OpaqueNode {
+ unsafe {
+ OpaqueNodeMethods::from_jsmanaged(&JS::from_trusted_node_address(node))
+ }
+ }
+
+ fn from_jsmanaged(node: &JS<Node>) -> OpaqueNode {
+ unsafe {
+ let ptr: uintptr_t = mem::transmute(node.reflector().get_jsobject());
+ OpaqueNode(ptr)
+ }
+ }
+
+ fn to_untrusted_node_address(&self) -> UntrustedNodeAddress {
+ unsafe {
+ let OpaqueNode(addr) = *self;
+ let addr: UntrustedNodeAddress = mem::transmute(addr);
+ addr
+ }
+ }
+}
+
+/// Allows a CSS color to be converted into a graphics color.
+pub trait ToGfxColor {
+ /// Converts a CSS color to a graphics color.
+ fn to_gfx_color(&self) -> gfx::color::Color;
+}
+
+impl ToGfxColor for style::computed_values::RGBA {
+ fn to_gfx_color(&self) -> gfx::color::Color {
+ gfx::color::rgba(self.red, self.green, self.blue, self.alpha)
+ }
+}
+
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
new file mode 100644
index 00000000000..d052c263655
--- /dev/null
+++ b/components/layout/wrapper.rs
@@ -0,0 +1,783 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes
+//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via
+//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from
+//! escaping.
+//!
+//! As a security wrapper is only as good as its whitelist, be careful when adding operations to
+//! this list. The cardinal rules are:
+//!
+//! 1. Layout is not allowed to mutate the DOM.
+//!
+//! 2. Layout is not allowed to see anything with `JS` in the name, because it could hang
+//! onto these objects and cause use-after-free.
+//!
+//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you
+//! will race and cause spurious task failure. (Note that I do not believe these races are
+//! exploitable, but they'll result in brokenness nonetheless.)
+//!
+//! Rules of the road for this file:
+//!
+//! * In general, you must not use the `Cast` functions; use explicit checks and `transmute_copy`
+//! instead.
+//!
+//! * You must also not use `.get()`; instead, use `.unsafe_get()`.
+//!
+//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags.
+//!
+//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`.
+//!
+//! o Instead of `html_element_in_html_document()`, use
+//! `html_element_in_html_document_for_layout()`.
+
+use css::node_style::StyledNode;
+use util::LayoutDataWrapper;
+
+use script::dom::bindings::codegen::InheritTypes::{HTMLIFrameElementDerived};
+use script::dom::bindings::codegen::InheritTypes::{HTMLImageElementDerived, TextDerived};
+use script::dom::bindings::js::JS;
+use script::dom::element::{Element, HTMLAreaElementTypeId, HTMLAnchorElementTypeId};
+use script::dom::element::{HTMLLinkElementTypeId, LayoutElementHelpers, RawLayoutElementHelpers};
+use script::dom::htmliframeelement::HTMLIFrameElement;
+use script::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
+use script::dom::node::{DocumentNodeTypeId, ElementNodeTypeId, Node, NodeTypeId};
+use script::dom::node::{LayoutNodeHelpers, RawLayoutNodeHelpers, TextNodeTypeId};
+use script::dom::text::Text;
+use servo_msg::constellation_msg::{PipelineId, SubpageId};
+use servo_util::atom::Atom;
+use servo_util::namespace::Namespace;
+use servo_util::namespace;
+use servo_util::str::is_whitespace;
+use std::cell::{RefCell, Ref, RefMut};
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+use style::computed_values::{content, display, white_space};
+use style::{AnyNamespace, AttrSelector, PropertyDeclarationBlock, SpecificNamespace, TElement};
+use style::{TNode};
+use url::Url;
+
+/// Allows some convenience methods on generic layout nodes.
+pub trait TLayoutNode {
+ /// Creates a new layout node with the same lifetime as this layout node.
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> Self;
+
+ /// Returns the type ID of this node. Fails if this node is borrowed mutably. Returns `None`
+ /// if this is a pseudo-element; otherwise, returns `Some`.
+ fn type_id(&self) -> Option<NodeTypeId>;
+
+ /// Returns the interior of this node as a `JS`. This is highly unsafe for layout to
+ /// call and as such is marked `unsafe`.
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node>;
+
+ /// Returns the interior of this node as a `Node`. This is highly unsafe for layout to call
+ /// and as such is marked `unsafe`.
+ unsafe fn get<'a>(&'a self) -> &'a Node {
+ &*self.get_jsmanaged().unsafe_get()
+ }
+
+ fn node_is_element(&self) -> bool {
+ match self.type_id() {
+ Some(ElementNodeTypeId(..)) => true,
+ _ => false
+ }
+ }
+
+ fn node_is_document(&self) -> bool {
+ match self.type_id() {
+ Some(DocumentNodeTypeId(..)) => true,
+ _ => false
+ }
+ }
+
+ /// If this is an image element, returns its URL. If this is not an image element, fails.
+ ///
+ /// FIXME(pcwalton): Don't copy URLs.
+ fn image_url(&self) -> Option<Url> {
+ unsafe {
+ if !self.get().is_htmlimageelement() {
+ fail!("not an image!")
+ }
+ let image_element: JS<HTMLImageElement> = self.get_jsmanaged().transmute_copy();
+ image_element.image().as_ref().map(|url| (*url).clone())
+ }
+ }
+
+ /// If this node is an iframe element, returns its pipeline and subpage IDs. If this node is
+ /// not an iframe element, fails.
+ fn iframe_pipeline_and_subpage_ids(&self) -> (PipelineId, SubpageId) {
+ unsafe {
+ if !self.get().is_htmliframeelement() {
+ fail!("not an iframe element!")
+ }
+ let iframe_element: JS<HTMLIFrameElement> = self.get_jsmanaged().transmute_copy();
+ let size = (*iframe_element.unsafe_get()).size.deref().get().unwrap();
+ (size.pipeline_id, size.subpage_id)
+ }
+ }
+
+ /// If this is a text node, copies out the text. If this is not a text node, fails.
+ ///
+ /// FIXME(pcwalton): Don't copy text. Atomically reference count instead.
+ fn text(&self) -> String;
+
+ /// Returns the first child of this node.
+ fn first_child(&self) -> Option<Self>;
+
+ /// Dumps this node tree, for debugging.
+ fn dump(&self) {
+ // TODO(pcwalton): Reimplement this in a way that's safe for layout to call.
+ }
+}
+
+/// A wrapper so that layout can access only the methods that it should have access to. Layout must
+/// only ever see these and must never see instances of `JS`.
+pub struct LayoutNode<'a> {
+ /// The wrapped node.
+ node: JS<Node>,
+
+ /// Being chained to a ContravariantLifetime prevents `LayoutNode`s from escaping.
+ pub chain: ContravariantLifetime<'a>,
+}
+
+impl<'ln> Clone for LayoutNode<'ln> {
+ fn clone(&self) -> LayoutNode<'ln> {
+ LayoutNode {
+ node: self.node.clone(),
+ chain: self.chain,
+ }
+ }
+}
+
+impl<'a> PartialEq for LayoutNode<'a> {
+ #[inline]
+ fn eq(&self, other: &LayoutNode) -> bool {
+ self.node == other.node
+ }
+}
+
+
+impl<'ln> TLayoutNode for LayoutNode<'ln> {
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> LayoutNode<'ln> {
+ LayoutNode {
+ node: node.transmute_copy(),
+ chain: self.chain,
+ }
+ }
+
+ fn type_id(&self) -> Option<NodeTypeId> {
+ unsafe {
+ Some(self.node.type_id_for_layout())
+ }
+ }
+
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ &self.node
+ }
+
+ fn first_child(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn text(&self) -> String {
+ unsafe {
+ if !self.get().is_text() {
+ fail!("not text!")
+ }
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
+ }
+ }
+}
+
+impl<'ln> LayoutNode<'ln> {
+ /// Creates a new layout node, scoped to the given closure.
+ pub unsafe fn with_layout_node<R>(node: JS<Node>, f: <'a> |LayoutNode<'a>| -> R) -> R {
+ f(LayoutNode {
+ node: node,
+ chain: ContravariantLifetime,
+ })
+ }
+
+ /// Iterates over this node and all its descendants, in preorder.
+ ///
+ /// FIXME(pcwalton): Terribly inefficient. We should use parallelism.
+ pub fn traverse_preorder(&self) -> LayoutTreeIterator<'ln> {
+ let mut nodes = vec!();
+ gather_layout_nodes(self, &mut nodes, false);
+ LayoutTreeIterator::new(nodes)
+ }
+
+ /// Returns an iterator over this node's children.
+ pub fn children(&self) -> LayoutNodeChildrenIterator<'ln> {
+ LayoutNodeChildrenIterator {
+ current_node: self.first_child(),
+ }
+ }
+
+ pub unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ &self.node
+ }
+}
+
+impl<'ln> TNode<LayoutElement<'ln>> for LayoutNode<'ln> {
+ fn parent_node(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.parent_node_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn prev_sibling(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.prev_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn next_sibling(&self) -> Option<LayoutNode<'ln>> {
+ unsafe {
+ self.node.next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ /// If this is an element, accesses the element data. Fails if this is not an element node.
+ #[inline]
+ fn as_element(&self) -> LayoutElement<'ln> {
+ unsafe {
+ assert!(self.node.is_element_for_layout());
+ let elem: JS<Element> = self.node.transmute_copy();
+ let element = &*elem.unsafe_get();
+ LayoutElement {
+ element: mem::transmute(element),
+ }
+ }
+ }
+
+ fn is_element(&self) -> bool {
+ self.node_is_element()
+ }
+
+ fn is_document(&self) -> bool {
+ self.node_is_document()
+ }
+
+ fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool {
+ assert!(self.is_element())
+ let name = if self.is_html_element_in_html_document() {
+ attr.lower_name.as_slice()
+ } else {
+ attr.name.as_slice()
+ };
+ match attr.namespace {
+ SpecificNamespace(ref ns) => {
+ let element = self.as_element();
+ element.get_attr(ns, name)
+ .map_or(false, |attr| test(attr))
+ },
+ // FIXME: https://github.com/mozilla/servo/issues/1558
+ AnyNamespace => false,
+ }
+ }
+
+ fn is_html_element_in_html_document(&self) -> bool {
+ unsafe {
+ self.is_element() && {
+ let element: JS<Element> = self.node.transmute_copy();
+ element.html_element_in_html_document_for_layout()
+ }
+ }
+ }
+}
+
+pub struct LayoutNodeChildrenIterator<'a> {
+ current_node: Option<LayoutNode<'a>>,
+}
+
+impl<'a> Iterator<LayoutNode<'a>> for LayoutNodeChildrenIterator<'a> {
+ fn next(&mut self) -> Option<LayoutNode<'a>> {
+ let node = self.current_node.clone();
+ self.current_node = node.clone().and_then(|node| {
+ node.next_sibling()
+ });
+ node
+ }
+}
+
+// FIXME: Do this without precomputing a vector of refs.
+// Easy for preorder; harder for postorder.
+//
+// FIXME(pcwalton): Parallelism! Eventually this should just be nuked.
+pub struct LayoutTreeIterator<'a> {
+ nodes: Vec<LayoutNode<'a>>,
+ index: uint,
+}
+
+impl<'a> LayoutTreeIterator<'a> {
+ fn new(nodes: Vec<LayoutNode<'a>>) -> LayoutTreeIterator<'a> {
+ LayoutTreeIterator {
+ nodes: nodes,
+ index: 0,
+ }
+ }
+}
+
+impl<'a> Iterator<LayoutNode<'a>> for LayoutTreeIterator<'a> {
+ fn next(&mut self) -> Option<LayoutNode<'a>> {
+ if self.index >= self.nodes.len() {
+ None
+ } else {
+ let v = self.nodes[self.index].clone();
+ self.index += 1;
+ Some(v)
+ }
+ }
+}
+
+/// FIXME(pcwalton): This is super inefficient.
+fn gather_layout_nodes<'a>(cur: &LayoutNode<'a>, refs: &mut Vec<LayoutNode<'a>>, postorder: bool) {
+ if !postorder {
+ refs.push(cur.clone());
+ }
+ for kid in cur.children() {
+ gather_layout_nodes(&kid, refs, postorder)
+ }
+ if postorder {
+ refs.push(cur.clone());
+ }
+}
+
+/// A wrapper around elements that ensures layout can only ever access safe properties.
+pub struct LayoutElement<'le> {
+ element: &'le Element,
+}
+
+impl<'le> LayoutElement<'le> {
+ pub fn style_attribute(&self) -> &'le Option<PropertyDeclarationBlock> {
+ let style: &Option<PropertyDeclarationBlock> = unsafe {
+ let style: &RefCell<Option<PropertyDeclarationBlock>> = self.element.style_attribute.deref();
+ // cast to the direct reference to T placed on the head of RefCell<T>
+ mem::transmute(style)
+ };
+ style
+ }
+}
+
+impl<'le> TElement for LayoutElement<'le> {
+ #[inline]
+ fn get_local_name<'a>(&'a self) -> &'a Atom {
+ &self.element.local_name
+ }
+
+ #[inline]
+ fn get_namespace<'a>(&'a self) -> &'a Namespace {
+ &self.element.namespace
+ }
+
+ #[inline]
+ fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
+ unsafe { self.element.get_attr_val_for_layout(namespace, name) }
+ }
+
+ fn get_link(&self) -> Option<&'static str> {
+ // FIXME: This is HTML only.
+ match self.element.node.type_id_for_layout() {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
+ // selector-link
+ ElementNodeTypeId(HTMLAnchorElementTypeId) |
+ ElementNodeTypeId(HTMLAreaElementTypeId) |
+ ElementNodeTypeId(HTMLLinkElementTypeId) => {
+ unsafe { self.element.get_attr_val_for_layout(&namespace::Null, "href") }
+ }
+ _ => None,
+ }
+ }
+
+ fn get_hover_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_hover_state_for_layout()
+ }
+ }
+
+ #[inline]
+ fn get_id(&self) -> Option<Atom> {
+ unsafe { self.element.get_attr_atom_for_layout(&namespace::Null, "id") }
+ }
+
+ fn get_disabled_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_disabled_state_for_layout()
+ }
+ }
+
+ fn get_enabled_state(&self) -> bool {
+ unsafe {
+ self.element.node.get_enabled_state_for_layout()
+ }
+ }
+}
+
+fn get_content(content_list: &content::T) -> String {
+ match *content_list {
+ content::Content(ref value) => {
+ let iter = &mut value.clone().move_iter().peekable();
+ match iter.next() {
+ Some(content::StringContent(content)) => content,
+ _ => "".to_string(),
+ }
+ }
+ _ => "".to_string(),
+ }
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum PseudoElementType {
+ Normal,
+ Before,
+ After,
+ BeforeBlock,
+ AfterBlock,
+}
+
+/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout
+/// node does not allow any parents or siblings of nodes to be accessed, to avoid races.
+#[deriving(Clone)]
+pub struct ThreadSafeLayoutNode<'ln> {
+ /// The wrapped node.
+ node: LayoutNode<'ln>,
+
+ pseudo: PseudoElementType,
+}
+
+impl<'ln> TLayoutNode for ThreadSafeLayoutNode<'ln> {
+ /// Creates a new layout node with the same lifetime as this layout node.
+ unsafe fn new_with_this_lifetime(&self, node: &JS<Node>) -> ThreadSafeLayoutNode<'ln> {
+ ThreadSafeLayoutNode {
+ node: LayoutNode {
+ node: node.transmute_copy(),
+ chain: self.node.chain,
+ },
+ pseudo: Normal,
+ }
+ }
+
+ /// Returns `None` if this is a pseudo-element.
+ fn type_id(&self) -> Option<NodeTypeId> {
+ if self.pseudo == Before || self.pseudo == After {
+ return None
+ }
+
+ self.node.type_id()
+ }
+
+ unsafe fn get_jsmanaged<'a>(&'a self) -> &'a JS<Node> {
+ self.node.get_jsmanaged()
+ }
+
+ unsafe fn get<'a>(&'a self) -> &'a Node { // this change.
+ mem::transmute::<*mut Node,&'a Node>(self.get_jsmanaged().unsafe_get())
+ }
+
+ fn first_child(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
+ if self.pseudo == Before || self.pseudo == After {
+ return None
+ }
+
+ if self.has_before_pseudo() {
+ if self.is_block(Before) && self.pseudo == Normal {
+ let pseudo_before_node = self.with_pseudo(BeforeBlock);
+ return Some(pseudo_before_node)
+ } else if self.pseudo == Normal || self.pseudo == BeforeBlock {
+ let pseudo_before_node = self.with_pseudo(Before);
+ return Some(pseudo_before_node)
+ }
+ }
+
+ unsafe {
+ self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+ }
+
+ fn text(&self) -> String {
+ if self.pseudo == Before || self.pseudo == After {
+ let layout_data_ref = self.borrow_layout_data();
+ let node_layout_data_wrapper = layout_data_ref.get_ref();
+
+ if self.pseudo == Before {
+ let before_style = node_layout_data_wrapper.data.before_style.get_ref();
+ return get_content(&before_style.get_box().content)
+ } else {
+ let after_style = node_layout_data_wrapper.data.after_style.get_ref();
+ return get_content(&after_style.get_box().content)
+ }
+ }
+
+ unsafe {
+ if !self.get().is_text() {
+ fail!("not text!")
+ }
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ (*text.unsafe_get()).characterdata.data.deref().borrow().clone()
+ }
+ }
+}
+
+
+impl<'ln> ThreadSafeLayoutNode<'ln> {
+ /// Creates a new `ThreadSafeLayoutNode` from the given `LayoutNode`.
+ pub fn new<'a>(node: &LayoutNode<'a>) -> ThreadSafeLayoutNode<'a> {
+ ThreadSafeLayoutNode {
+ node: node.clone(),
+ pseudo: Normal,
+ }
+ }
+
+ /// Creates a new `ThreadSafeLayoutNode` for the same `LayoutNode`
+ /// with a different pseudo-element type.
+ fn with_pseudo(&self, pseudo: PseudoElementType) -> ThreadSafeLayoutNode<'ln> {
+ ThreadSafeLayoutNode {
+ node: self.node.clone(),
+ pseudo: pseudo,
+ }
+ }
+
+ /// Returns the next sibling of this node. Unsafe and private because this can lead to races.
+ unsafe fn next_sibling(&self) -> Option<ThreadSafeLayoutNode<'ln>> {
+ if self.pseudo == Before || self.pseudo == BeforeBlock {
+ return self.get_jsmanaged().first_child_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+
+ self.get_jsmanaged().next_sibling_ref().map(|node| self.new_with_this_lifetime(&node))
+ }
+
+ /// Returns an iterator over this node's children.
+ pub fn children(&self) -> ThreadSafeLayoutNodeChildrenIterator<'ln> {
+ ThreadSafeLayoutNodeChildrenIterator {
+ current_node: self.first_child(),
+ parent_node: Some(self.clone()),
+ }
+ }
+
+ /// If this is an element, accesses the element data. Fails if this is not an element node.
+ #[inline]
+ pub fn as_element(&self) -> ThreadSafeLayoutElement {
+ unsafe {
+ assert!(self.get_jsmanaged().is_element_for_layout());
+ let elem: JS<Element> = self.get_jsmanaged().transmute_copy();
+ let element = elem.unsafe_get();
+ // FIXME(pcwalton): Workaround until Rust gets multiple lifetime parameters on
+ // implementations.
+ ThreadSafeLayoutElement {
+ element: &mut *element,
+ }
+ }
+ }
+
+ pub fn get_pseudo_element_type(&self) -> PseudoElementType {
+ self.pseudo
+ }
+
+ pub fn is_block(&self, kind: PseudoElementType) -> bool {
+ let mut layout_data_ref = self.mutate_layout_data();
+ let node_layout_data_wrapper = layout_data_ref.get_mut_ref();
+
+ let display = match kind {
+ Before | BeforeBlock => {
+ let before_style = node_layout_data_wrapper.data.before_style.get_ref();
+ before_style.get_box().display
+ }
+ After | AfterBlock => {
+ let after_style = node_layout_data_wrapper.data.after_style.get_ref();
+ after_style.get_box().display
+ }
+ Normal => {
+ let after_style = node_layout_data_wrapper.shared_data.style.get_ref();
+ after_style.get_box().display
+ }
+ };
+
+ display == display::block
+ }
+
+ pub fn has_before_pseudo(&self) -> bool {
+ let layout_data_wrapper = self.borrow_layout_data();
+ let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
+ layout_data_wrapper_ref.data.before_style.is_some()
+ }
+
+ pub fn has_after_pseudo(&self) -> bool {
+ let layout_data_wrapper = self.borrow_layout_data();
+ let layout_data_wrapper_ref = layout_data_wrapper.get_ref();
+ layout_data_wrapper_ref.data.after_style.is_some()
+ }
+
+ /// Borrows the layout data immutably. Fails on a conflicting borrow.
+ #[inline(always)]
+ pub fn borrow_layout_data<'a>(&'a self) -> Ref<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow())
+ }
+ }
+
+ /// Borrows the layout data mutably. Fails on a conflicting borrow.
+ #[inline(always)]
+ pub fn mutate_layout_data<'a>(&'a self) -> RefMut<'a,Option<LayoutDataWrapper>> {
+ unsafe {
+ mem::transmute(self.get().layout_data.borrow_mut())
+ }
+ }
+
+ /// Traverses the tree in postorder.
+ ///
+ /// TODO(pcwalton): Offer a parallel version with a compatible API.
+ pub fn traverse_postorder_mut<T:PostorderNodeMutTraversal>(&mut self, traversal: &mut T)
+ -> bool {
+ if traversal.should_prune(self) {
+ return true
+ }
+
+ let mut opt_kid = self.first_child();
+ loop {
+ match opt_kid {
+ None => break,
+ Some(mut kid) => {
+ if !kid.traverse_postorder_mut(traversal) {
+ return false
+ }
+ unsafe {
+ opt_kid = kid.next_sibling()
+ }
+ }
+ }
+ }
+
+ traversal.process(self)
+ }
+
+ pub fn is_ignorable_whitespace(&self) -> bool {
+ match self.type_id() {
+ Some(TextNodeTypeId) => {
+ unsafe {
+ let text: JS<Text> = self.get_jsmanaged().transmute_copy();
+ if !is_whitespace((*text.unsafe_get()).characterdata.data.deref().borrow().as_slice()) {
+ return false
+ }
+
+ // NB: See the rules for `white-space` here:
+ //
+ // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
+ //
+ // If you implement other values for this property, you will almost certainly
+ // want to update this check.
+ match self.style().get_inheritedtext().white_space {
+ white_space::normal => true,
+ _ => false,
+ }
+ }
+ }
+ _ => false
+ }
+ }
+}
+
+pub struct ThreadSafeLayoutNodeChildrenIterator<'a> {
+ current_node: Option<ThreadSafeLayoutNode<'a>>,
+ parent_node: Option<ThreadSafeLayoutNode<'a>>,
+}
+
+impl<'a> Iterator<ThreadSafeLayoutNode<'a>> for ThreadSafeLayoutNodeChildrenIterator<'a> {
+ fn next(&mut self) -> Option<ThreadSafeLayoutNode<'a>> {
+ let node = self.current_node.clone();
+
+ match node {
+ Some(ref node) => {
+ if node.pseudo == After || node.pseudo == AfterBlock {
+ return None
+ }
+
+ match self.parent_node {
+ Some(ref parent_node) => {
+ if parent_node.pseudo == Normal {
+ self.current_node = self.current_node.clone().and_then(|node| {
+ unsafe {
+ node.next_sibling()
+ }
+ });
+ } else {
+ self.current_node = None;
+ }
+ }
+ None => {}
+ }
+ }
+ None => {
+ match self.parent_node {
+ Some(ref parent_node) => {
+ if parent_node.has_after_pseudo() {
+ let pseudo_after_node = if parent_node.is_block(After) && parent_node.pseudo == Normal {
+ let pseudo_after_node = parent_node.with_pseudo(AfterBlock);
+ Some(pseudo_after_node)
+ } else if parent_node.pseudo == Normal || parent_node.pseudo == AfterBlock {
+ let pseudo_after_node = parent_node.with_pseudo(After);
+ Some(pseudo_after_node)
+ } else {
+ None
+ };
+ self.current_node = pseudo_after_node;
+ return self.current_node.clone()
+ }
+ }
+ None => {}
+ }
+ }
+ }
+
+ node
+ }
+}
+
+/// A wrapper around elements that ensures layout can only ever access safe properties and cannot
+/// race on elements.
+pub struct ThreadSafeLayoutElement<'le> {
+ element: &'le Element,
+}
+
+impl<'le> ThreadSafeLayoutElement<'le> {
+ #[inline]
+ pub fn get_attr(&self, namespace: &Namespace, name: &str) -> Option<&'static str> {
+ unsafe { self.element.get_attr_val_for_layout(namespace, name) }
+ }
+}
+
+/// A bottom-up, parallelizable traversal.
+pub trait PostorderNodeMutTraversal {
+ /// The operation to perform. Return true to continue or false to stop.
+ fn process<'a>(&'a mut self, node: &ThreadSafeLayoutNode<'a>) -> bool;
+
+ /// Returns true if this node should be pruned. If this returns true, we skip the operation
+ /// entirely and do not process any descendant nodes. This is called *before* child nodes are
+ /// visited. The default implementation never prunes any nodes.
+ fn should_prune<'a>(&'a self, _node: &ThreadSafeLayoutNode<'a>) -> bool {
+ false
+ }
+}
+
+/// Opaque type stored in type-unsafe work queues for parallel layout.
+/// Must be transmutable to and from LayoutNode/ThreadSafeLayoutNode.
+pub type UnsafeLayoutNode = (uint, uint);
+
+pub fn layout_node_to_unsafe_layout_node(node: &LayoutNode) -> UnsafeLayoutNode {
+ unsafe {
+ let ptr: uint = mem::transmute_copy(node);
+ (ptr, 0)
+ }
+}
+
+// FIXME(#3044): This should be updated to use a real lifetime instead of
+// faking one.
+pub unsafe fn layout_node_from_unsafe_layout_node(node: &UnsafeLayoutNode) -> LayoutNode<'static> {
+ let (node, _) = *node;
+ mem::transmute(node)
+}
diff --git a/components/layout_traits/Cargo.toml b/components/layout_traits/Cargo.toml
new file mode 100644
index 00000000000..29556f9b975
--- /dev/null
+++ b/components/layout_traits/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "layout_traits"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "layout_traits"
+path = "lib.rs"
+
+[dependencies.gfx]
+path = "../gfx"
+
+[dependencies.script_traits]
+path = "../script_traits"
+
+[dependencies.msg]
+path = "../msg"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.util]
+path = "../util"
diff --git a/components/layout_traits/lib.rs b/components/layout_traits/lib.rs
new file mode 100644
index 00000000000..612ba246e76
--- /dev/null
+++ b/components/layout_traits/lib.rs
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+extern crate gfx;
+extern crate script_traits;
+extern crate servo_msg = "msg";
+extern crate servo_net = "net";
+extern crate servo_util = "util";
+
+// This module contains traits in layout used generically
+// in the rest of Servo.
+// The traits are here instead of in layout so
+// that these modules won't have to depend on layout.
+
+use gfx::font_cache_task::FontCacheTask;
+use gfx::render_task::RenderChan;
+use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
+use servo_msg::constellation_msg::Failure;
+use servo_net::image_cache_task::ImageCacheTask;
+use servo_util::opts::Opts;
+use servo_util::time::TimeProfilerChan;
+use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
+use std::comm::Sender;
+
+/// Messages sent to the layout task from the constellation
+pub enum LayoutControlMsg {
+ ExitNowMsg,
+}
+
+/// A channel wrapper for constellation messages
+pub struct LayoutControlChan(pub Sender<LayoutControlMsg>);
+
+// A static method creating a layout task
+// Here to remove the compositor -> layout dependency
+pub trait LayoutTaskFactory {
+ // FIXME: use a proper static method
+ fn create(_phantom: Option<&mut Self>,
+ id: PipelineId,
+ chan: OpaqueScriptLayoutChannel,
+ pipeline_port: Receiver<LayoutControlMsg>,
+ constellation_chan: ConstellationChan,
+ failure_msg: Failure,
+ script_chan: ScriptControlChan,
+ render_chan: RenderChan,
+ img_cache_task: ImageCacheTask,
+ font_cache_task: FontCacheTask,
+ opts: Opts,
+ time_profiler_chan: TimeProfilerChan,
+ shutdown_chan: Sender<()>);
+}
diff --git a/components/macros/Cargo.toml b/components/macros/Cargo.toml
new file mode 100644
index 00000000000..c187d554829
--- /dev/null
+++ b/components/macros/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "macros"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "macros"
+path = "lib.rs"
+plugin = true
diff --git a/components/macros/lib.rs b/components/macros/lib.rs
new file mode 100644
index 00000000000..20304ffe806
--- /dev/null
+++ b/components/macros/lib.rs
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![feature(macro_rules, plugin_registrar, quote, phase)]
+
+//! Exports macros for use in other Servo crates.
+
+#[cfg(test)]
+extern crate sync;
+
+
+#[macro_export]
+macro_rules! bitfield(
+ ($bitfieldname:ident, $getter:ident, $setter:ident, $value:expr) => (
+ impl $bitfieldname {
+ #[inline]
+ pub fn $getter(self) -> bool {
+ let $bitfieldname(this) = self;
+ (this & $value) != 0
+ }
+
+ #[inline]
+ pub fn $setter(&mut self, value: bool) {
+ let $bitfieldname(this) = *self;
+ *self = $bitfieldname((this & !$value) | (if value { $value } else { 0 }))
+ }
+ }
+ )
+)
+
+
+#[macro_export]
+macro_rules! lazy_init(
+ ($(static ref $N:ident : $T:ty = $e:expr;)*) => (
+ $(
+ #[allow(non_camel_case_types)]
+ struct $N {__unit__: ()}
+ static $N: $N = $N {__unit__: ()};
+ impl Deref<$T> for $N {
+ fn deref<'a>(&'a self) -> &'a $T {
+ unsafe {
+ static mut s: *const $T = 0 as *const $T;
+ static mut ONCE: ::sync::one::Once = ::sync::one::ONCE_INIT;
+ ONCE.doit(|| {
+ s = ::std::mem::transmute::<Box<$T>, *const $T>(box () ($e));
+ });
+ &*s
+ }
+ }
+ }
+
+ )*
+ )
+)
+
+
+#[cfg(test)]
+mod tests {
+ use std::collections::hashmap::HashMap;
+ lazy_init! {
+ static ref NUMBER: uint = times_two(3);
+ static ref VEC: [Box<uint>, ..3] = [box 1, box 2, box 3];
+ static ref OWNED_STRING: String = "hello".to_string();
+ static ref HASHMAP: HashMap<uint, &'static str> = {
+ let mut m = HashMap::new();
+ m.insert(0u, "abc");
+ m.insert(1, "def");
+ m.insert(2, "ghi");
+ m
+ };
+ }
+
+ fn times_two(n: uint) -> uint {
+ n * 2
+ }
+
+ #[test]
+ fn test_basic() {
+ assert_eq!(*OWNED_STRING, "hello".to_string());
+ assert_eq!(*NUMBER, 6);
+ assert!(HASHMAP.find(&1).is_some());
+ assert!(HASHMAP.find(&3).is_none());
+ assert_eq!(VEC.as_slice(), &[box 1, box 2, box 3]);
+ }
+
+ #[test]
+ fn test_repeat() {
+ assert_eq!(*NUMBER, 6);
+ assert_eq!(*NUMBER, 6);
+ assert_eq!(*NUMBER, 6);
+ }
+}
diff --git a/components/msg/Cargo.toml b/components/msg/Cargo.toml
new file mode 100644
index 00000000000..8fc29ca2039
--- /dev/null
+++ b/components/msg/Cargo.toml
@@ -0,0 +1,30 @@
+[package]
+
+name = "msg"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "msg"
+path = "lib.rs"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.azure]
+git = "http://github.com/servo/rust-azure"
+
+[dependencies.geom]
+git = "http://github.com/servo/rust-geom"
+
+[dependencies.layers]
+git = "http://github.com/servo/rust-layers"
+
+[dependencies.core_foundation]
+git = "http://github.com/servo/rust-core-foundation"
+
+[dependencies.io_surface]
+git = "http://github.com/servo/rust-io-surface"
+
+[dependencies.url]
+git = "http://github.com/servo/rust-url"
diff --git a/components/msg/compositor_msg.rs b/components/msg/compositor_msg.rs
new file mode 100644
index 00000000000..6008e4fca23
--- /dev/null
+++ b/components/msg/compositor_msg.rs
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use azure::azure_hl::Color;
+use geom::point::Point2D;
+use geom::rect::Rect;
+use layers::platform::surface::NativeGraphicsMetadata;
+use layers::layers::LayerBufferSet;
+use serialize::{Encoder, Encodable};
+use std::fmt::{Formatter, Show};
+use std::fmt;
+
+use constellation_msg::PipelineId;
+
+/// The status of the renderer.
+#[deriving(PartialEq, Clone)]
+pub enum RenderState {
+ IdleRenderState,
+ RenderingRenderState,
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum ReadyState {
+ /// Informs the compositor that nothing has been done yet. Used for setting status
+ Blank,
+ /// Informs the compositor that a page is loading. Used for setting status
+ Loading,
+ /// Informs the compositor that a page is performing layout. Used for setting status
+ PerformingLayout,
+ /// Informs the compositor that a page is finished loading. Used for setting status
+ FinishedLoading,
+}
+
+/// A newtype struct for denoting the age of messages; prevents race conditions.
+#[deriving(PartialEq)]
+pub struct Epoch(pub uint);
+
+impl Epoch {
+ pub fn next(&mut self) {
+ let Epoch(ref mut u) = *self;
+ *u += 1;
+ }
+}
+
+#[deriving(Clone, PartialEq)]
+pub struct LayerId(pub uint, pub uint);
+
+impl Show for LayerId {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ let LayerId(a, b) = *self;
+ write!(f, "Layer({}, {})", a, b)
+ }
+}
+
+impl LayerId {
+ /// FIXME(#2011, pcwalton): This is unfortunate. Maybe remove this in the future.
+ pub fn null() -> LayerId {
+ LayerId(0, 0)
+ }
+}
+
+/// The scrolling policy of a layer.
+#[deriving(PartialEq)]
+pub enum ScrollPolicy {
+ /// These layers scroll when the parent receives a scrolling message.
+ Scrollable,
+ /// These layers do not scroll when the parent receives a scrolling message.
+ FixedPosition,
+}
+
+/// All layer-specific information that the painting task sends to the compositor other than the
+/// buffer contents of the layer itself.
+pub struct LayerMetadata {
+ /// An opaque ID. This is usually the address of the flow and index of the box within it.
+ pub id: LayerId,
+ /// The position and size of the layer in pixels.
+ pub position: Rect<uint>,
+ /// The background color of the layer.
+ pub background_color: Color,
+ /// The scrolling policy of this layer.
+ pub scroll_policy: ScrollPolicy,
+}
+
+/// The interface used by the renderer to acquire draw targets for each render frame and
+/// submit them to be drawn to the display.
+pub trait RenderListener {
+ fn get_graphics_metadata(&self) -> Option<NativeGraphicsMetadata>;
+
+ /// Informs the compositor of the layers for the given pipeline. The compositor responds by
+ /// creating and/or destroying render layers as necessary.
+ fn initialize_layers_for_pipeline(&self,
+ pipeline_id: PipelineId,
+ metadata: Vec<LayerMetadata>,
+ epoch: Epoch);
+
+ /// Sends new tiles for the given layer to the compositor.
+ fn paint(&self,
+ pipeline_id: PipelineId,
+ epoch: Epoch,
+ replies: Vec<(LayerId, Box<LayerBufferSet>)>);
+
+ fn render_msg_discarded(&self);
+ fn set_render_state(&self, render_state: RenderState);
+}
+
+/// The interface used by the script task to tell the compositor to update its ready state,
+/// which is used in displaying the appropriate message in the window's title.
+pub trait ScriptListener : Clone {
+ fn set_ready_state(&self, ReadyState);
+ fn scroll_fragment_point(&self,
+ pipeline_id: PipelineId,
+ layer_id: LayerId,
+ point: Point2D<f32>);
+ fn close(&self);
+ fn dup(&self) -> Box<ScriptListener>;
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Box<ScriptListener> {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs
new file mode 100644
index 00000000000..35b07024acd
--- /dev/null
+++ b/components/msg/constellation_msg.rs
@@ -0,0 +1,84 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The high-level interface from script to constellation. Using this abstract interface helps reduce
+//! coupling between these two components
+
+use geom::rect::Rect;
+use geom::size::TypedSize2D;
+use geom::scale_factor::ScaleFactor;
+use layers::geometry::DevicePixel;
+use serialize::Encodable;
+use servo_util::geometry::{PagePx, ViewportPx};
+use std::comm::{channel, Sender, Receiver};
+use url::Url;
+
+#[deriving(Clone)]
+pub struct ConstellationChan(pub Sender<Msg>);
+
+impl ConstellationChan {
+ pub fn new() -> (Receiver<Msg>, ConstellationChan) {
+ let (chan, port) = channel();
+ (port, ConstellationChan(chan))
+ }
+}
+
+#[deriving(PartialEq)]
+pub enum IFrameSandboxState {
+ IFrameSandboxed,
+ IFrameUnsandboxed
+}
+
+// We pass this info to various tasks, so it lives in a separate, cloneable struct.
+#[deriving(Clone)]
+pub struct Failure {
+ pub pipeline_id: PipelineId,
+ pub subpage_id: Option<SubpageId>,
+}
+
+#[deriving(Encodable)]
+pub struct WindowSizeData {
+ /// The size of the initial layout viewport, before parsing an
+ /// http://www.w3.org/TR/css-device-adapt/#initial-viewport
+ pub initial_viewport: TypedSize2D<ViewportPx, f32>,
+
+ /// The "viewing area" in page px. See `PagePx` documentation for details.
+ pub visible_viewport: TypedSize2D<PagePx, f32>,
+
+ /// The resolution of the window in dppx, not including any "pinch zoom" factor.
+ pub device_pixel_ratio: ScaleFactor<ViewportPx, DevicePixel, f32>,
+}
+
+/// Messages from the compositor and script to the constellation.
+pub enum Msg {
+ ExitMsg,
+ FailureMsg(Failure),
+ InitLoadUrlMsg(Url),
+ LoadCompleteMsg(PipelineId, Url),
+ FrameRectMsg(PipelineId, SubpageId, Rect<f32>),
+ LoadUrlMsg(PipelineId, Url),
+ LoadIframeUrlMsg(Url, PipelineId, SubpageId, IFrameSandboxState),
+ NavigateMsg(NavigationDirection),
+ RendererReadyMsg(PipelineId),
+ ResizedWindowMsg(WindowSizeData),
+}
+
+/// Represents the two different ways to which a page can be navigated
+#[deriving(Clone, PartialEq, Hash)]
+pub enum NavigationType {
+ Load, // entered or clicked on a url
+ Navigate, // browser forward/back buttons
+}
+
+#[deriving(Clone, PartialEq, Hash)]
+pub enum NavigationDirection {
+ Forward,
+ Back,
+}
+
+#[deriving(Clone, PartialEq, Eq, Hash, Encodable)]
+pub struct PipelineId(pub uint);
+
+#[deriving(Clone, PartialEq, Eq, Hash, Encodable)]
+pub struct SubpageId(pub uint);
diff --git a/components/msg/lib.rs b/components/msg/lib.rs
new file mode 100644
index 00000000000..129ce20ca5a
--- /dev/null
+++ b/components/msg/lib.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate azure;
+extern crate geom;
+extern crate layers;
+extern crate serialize;
+extern crate servo_util = "util";
+extern crate std;
+extern crate url;
+
+#[cfg(target_os="macos")]
+extern crate core_foundation;
+#[cfg(target_os="macos")]
+extern crate io_surface;
+
+pub mod compositor_msg;
+pub mod constellation_msg;
+
+pub mod platform {
+ #[cfg(target_os="macos")]
+ pub mod macos {
+ #[cfg(target_os="macos")]
+ pub mod surface;
+ }
+
+ #[cfg(target_os="linux")]
+ pub mod linux {
+ #[cfg(target_os="linux")]
+ pub mod surface;
+ }
+
+ #[cfg(target_os="android")]
+ pub mod android {
+ #[cfg(target_os="android")]
+ pub mod surface;
+ }
+
+
+ pub mod surface;
+}
+
diff --git a/components/msg/platform/android/surface.rs b/components/msg/platform/android/surface.rs
new file mode 100644
index 00000000000..6f2e962d804
--- /dev/null
+++ b/components/msg/platform/android/surface.rs
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! EGL-specific implementation of cross-process surfaces. This uses EGL surfaces.
+
+use platform::surface::NativeSurfaceAzureMethods;
+
+use azure::AzSkiaGrGLSharedSurfaceRef;
+use layers::platform::surface::NativeSurface;
+use std::mem;
+
+impl NativeSurfaceAzureMethods for NativeSurface {
+ fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
+ unsafe {
+ NativeSurface::from_image_khr(mem::transmute(surface))
+ }
+ }
+}
+
diff --git a/components/msg/platform/linux/surface.rs b/components/msg/platform/linux/surface.rs
new file mode 100644
index 00000000000..60cc84bc965
--- /dev/null
+++ b/components/msg/platform/linux/surface.rs
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! X11-specific implementation of cross-process surfaces. This uses X pixmaps.
+
+use platform::surface::NativeSurfaceAzureMethods;
+
+use azure::AzSkiaGrGLSharedSurfaceRef;
+use layers::platform::surface::NativeSurface;
+use std::mem;
+
+impl NativeSurfaceAzureMethods for NativeSurface {
+ fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
+ unsafe {
+ NativeSurface::from_pixmap(mem::transmute(surface))
+ }
+ }
+}
+
diff --git a/components/msg/platform/macos/surface.rs b/components/msg/platform/macos/surface.rs
new file mode 100644
index 00000000000..30b5e405500
--- /dev/null
+++ b/components/msg/platform/macos/surface.rs
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Mac OS-specific implementation of cross-process surfaces. This uses `IOSurface`, introduced
+//! in Mac OS X 10.6 Snow Leopard.
+
+use platform::surface::NativeSurfaceAzureMethods;
+
+use azure::AzSkiaGrGLSharedSurfaceRef;
+use io_surface::IOSurface;
+use layers::platform::surface::NativeSurface;
+use std::mem;
+
+impl NativeSurfaceAzureMethods for NativeSurface {
+ fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> NativeSurface {
+ unsafe {
+ let io_surface = IOSurface {
+ obj: mem::transmute(surface),
+ };
+ NativeSurface::from_io_surface(io_surface)
+ }
+ }
+}
+
diff --git a/components/msg/platform/surface.rs b/components/msg/platform/surface.rs
new file mode 100644
index 00000000000..eee8dfa5598
--- /dev/null
+++ b/components/msg/platform/surface.rs
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Declarations of types for cross-process surfaces.
+
+use azure::AzSkiaGrGLSharedSurfaceRef;
+
+pub trait NativeSurfaceAzureMethods {
+ fn from_azure_surface(surface: AzSkiaGrGLSharedSurfaceRef) -> Self;
+}
+
diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml
new file mode 100644
index 00000000000..17560f4aa5c
--- /dev/null
+++ b/components/net/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "net"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "net"
+path = "lib.rs"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.http]
+git = "https://github.com/servo/rust-http"
+branch = "servo"
+
+[dependencies.png]
+git = "https://github.com/servo/rust-png"
+
+[dependencies.stb_image]
+git = "https://github.com/servo/rust-stb-image"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
diff --git a/components/net/data_loader.rs b/components/net/data_loader.rs
new file mode 100644
index 00000000000..5d9fb776674
--- /dev/null
+++ b/components/net/data_loader.rs
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::str;
+
+use resource_task::{Done, Payload, Metadata, LoadData, LoadResponse, LoaderTask, start_sending};
+
+use serialize::base64::FromBase64;
+
+use http::headers::test_utils::from_stream_with_str;
+use http::headers::content_type::MediaType;
+use url::{percent_decode, NonRelativeSchemeData};
+
+
+pub fn factory() -> LoaderTask {
+ proc(url, start_chan) {
+ // NB: we don't spawn a new task.
+ // Hypothesis: data URLs are too small for parallel base64 etc. to be worth it.
+ // Should be tested at some point.
+ load(url, start_chan)
+ }
+}
+
+fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
+ let url = load_data.url;
+ assert!("data" == url.scheme.as_slice());
+
+ let mut metadata = Metadata::default(url.clone());
+
+ // Split out content type and data.
+ let mut scheme_data = match url.scheme_data {
+ NonRelativeSchemeData(scheme_data) => scheme_data,
+ _ => fail!("Expected a non-relative scheme URL.")
+ };
+ match url.query {
+ Some(query) => {
+ scheme_data.push_str("?");
+ scheme_data.push_str(query.as_slice());
+ },
+ None => ()
+ }
+ let parts: Vec<&str> = scheme_data.as_slice().splitn(',', 1).collect();
+ if parts.len() != 2 {
+ start_sending(start_chan, metadata).send(Done(Err("invalid data uri".to_string())));
+ return;
+ }
+
+ // ";base64" must come at the end of the content type, per RFC 2397.
+ // rust-http will fail to parse it because there's no =value part.
+ let mut is_base64 = false;
+ let mut ct_str = parts[0];
+ if ct_str.ends_with(";base64") {
+ is_base64 = true;
+ ct_str = ct_str.slice_to(ct_str.as_bytes().len() - 7);
+ }
+
+ // Parse the content type using rust-http.
+ // FIXME: this can go into an infinite loop! (rust-http #25)
+ let content_type: Option<MediaType> = from_stream_with_str(ct_str);
+ metadata.set_content_type(&content_type);
+
+ let progress_chan = start_sending(start_chan, metadata);
+ let bytes = percent_decode(parts[1].as_bytes());
+
+ if is_base64 {
+ // FIXME(#2909): It’s unclear what to do with non-alphabet characters,
+ // but Acid 3 apparently depends on spaces being ignored.
+ let bytes = bytes.move_iter().filter(|&b| b != ' ' as u8).collect::<Vec<u8>>();
+ // FIXME(#2877): use bytes.as_slice().from_base64() when we upgrade to a Rust version
+ // that includes https://github.com/rust-lang/rust/pull/15810
+ let fake_utf8 = unsafe { str::raw::from_utf8(bytes.as_slice()) };
+ match fake_utf8.from_base64() {
+ Err(..) => {
+ progress_chan.send(Done(Err("non-base64 data uri".to_string())));
+ }
+ Ok(data) => {
+ progress_chan.send(Payload(data));
+ progress_chan.send(Done(Ok(())));
+ }
+ }
+ } else {
+ progress_chan.send(Payload(bytes));
+ progress_chan.send(Done(Ok(())));
+ }
+}
+
+#[cfg(test)]
+fn assert_parse(url: &'static str,
+ content_type: Option<(String, String)>,
+ charset: Option<String>,
+ data: Option<Vec<u8>>) {
+ use std::comm;
+ use url::Url;
+
+ let (start_chan, start_port) = comm::channel();
+ load(LoadData::new(Url::parse(url).unwrap()), start_chan);
+
+ let response = start_port.recv();
+ assert_eq!(&response.metadata.content_type, &content_type);
+ assert_eq!(&response.metadata.charset, &charset);
+
+ let progress = response.progress_port.recv();
+
+ match data {
+ None => {
+ assert_eq!(progress, Done(Err("invalid data uri".to_string())));
+ }
+ Some(dat) => {
+ assert_eq!(progress, Payload(dat));
+ assert_eq!(response.progress_port.recv(), Done(Ok(())));
+ }
+ }
+}
+
+#[test]
+fn empty_invalid() {
+ assert_parse("data:", None, None, None);
+}
+
+#[test]
+fn plain() {
+ assert_parse("data:,hello%20world", None, None, Some(b"hello world".iter().map(|&x| x).collect()));
+}
+
+#[test]
+fn plain_ct() {
+ assert_parse("data:text/plain,hello",
+ Some(("text".to_string(), "plain".to_string())), None, Some(b"hello".iter().map(|&x| x).collect()));
+}
+
+#[test]
+fn plain_charset() {
+ assert_parse("data:text/plain;charset=latin1,hello",
+ Some(("text".to_string(), "plain".to_string())), Some("latin1".to_string()), Some(b"hello".iter().map(|&x| x).collect()));
+}
+
+#[test]
+fn base64() {
+ assert_parse("data:;base64,C62+7w==", None, None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
+}
+
+#[test]
+fn base64_ct() {
+ assert_parse("data:application/octet-stream;base64,C62+7w==",
+ Some(("application".to_string(), "octet-stream".to_string())), None, Some(vec!(0x0B, 0xAD, 0xBE, 0xEF)));
+}
+
+#[test]
+fn base64_charset() {
+ assert_parse("data:text/plain;charset=koi8-r;base64,8PLl9+XkIO3l5Pfl5A==",
+ Some(("text".to_string(), "plain".to_string())), Some("koi8-r".to_string()),
+ Some(vec!(0xF0, 0xF2, 0xE5, 0xF7, 0xE5, 0xE4, 0x20, 0xED, 0xE5, 0xE4, 0xF7, 0xE5, 0xE4)));
+}
diff --git a/components/net/fetch/cors_cache.rs b/components/net/fetch/cors_cache.rs
new file mode 100644
index 00000000000..fb6676e8064
--- /dev/null
+++ b/components/net/fetch/cors_cache.rs
@@ -0,0 +1,316 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! An implementation of the [CORS preflight cache](http://fetch.spec.whatwg.org/#cors-preflight-cache)
+//! For now this library is XHR-specific.
+//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
+//! the request mode should be and compare with the fetch spec
+//! This library will eventually become the core of the Fetch crate
+//! with CORSRequest being expanded into FetchRequest (etc)
+
+use http::method::Method;
+use std::ascii::StrAsciiExt;
+use std::comm::{Sender, Receiver, channel};
+use time;
+use time::{now, Timespec};
+use url::Url;
+
+/// Union type for CORS cache entries
+///
+/// Each entry might pertain to a header or method
+#[deriving(Clone)]
+pub enum HeaderOrMethod {
+ HeaderData(String),
+ MethodData(Method)
+}
+
+impl HeaderOrMethod {
+ fn match_header(&self, header_name: &str) -> bool {
+ match *self {
+ HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name),
+ _ => false
+ }
+ }
+
+ fn match_method(&self, method: &Method) -> bool {
+ match *self {
+ MethodData(ref m) => m == method,
+ _ => false
+ }
+ }
+}
+
+/// An entry in the CORS cache
+#[deriving(Clone)]
+pub struct CORSCacheEntry {
+ pub origin: Url,
+ pub url: Url,
+ pub max_age: uint,
+ pub credentials: bool,
+ pub header_or_method: HeaderOrMethod,
+ created: Timespec
+}
+
+impl CORSCacheEntry {
+ fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
+ CORSCacheEntry {
+ origin: origin,
+ url: url,
+ max_age: max_age,
+ credentials: credentials,
+ header_or_method: header_or_method,
+ created: time::now().to_timespec()
+ }
+ }
+}
+
+/// Properties of Request required to cache match.
+pub struct CacheRequestDetails {
+ pub origin: Url,
+ pub destination: Url,
+ pub credentials: bool
+}
+
+/// Trait for a generic CORS Cache
+pub trait CORSCache {
+ /// [Clear the cache](http://fetch.spec.whatwg.org/#concept-cache-clear)
+ fn clear (&mut self, request: CacheRequestDetails);
+
+ /// Remove old entries
+ fn cleanup(&mut self);
+
+ /// Returns true if an entry with a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool;
+
+ /// Updates max age if an entry for a [matching header](http://fetch.spec.whatwg.org/#concept-cache-match-header) is found.
+ ///
+ /// If not, it will insert an equivalent entry
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool;
+
+ /// Returns true if an entry with a [matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool;
+
+ /// Updates max age if an entry for [a matching method](http://fetch.spec.whatwg.org/#concept-cache-match-method) is found.
+ ///
+ /// If not, it will insert an equivalent entry
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool;
+ /// Insert an entry
+ fn insert(&mut self, entry: CORSCacheEntry);
+}
+
+/// A simple, vector-based CORS Cache
+#[deriving(Clone)]
+#[unstable = "This might later be replaced with a HashMap-like entity, though that requires a separate Origin struct"]
+pub struct BasicCORSCache(Vec<CORSCacheEntry>);
+
+impl BasicCORSCache {
+ fn find_entry_by_header<'a>(&'a mut self, request: &CacheRequestDetails, header_name: &str) -> Option<&'a mut CORSCacheEntry> {
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.credentials == request.credentials &&
+ e.header_or_method.match_header(header_name));
+ entry
+ }
+
+ fn find_entry_by_method<'a>(&'a mut self, request: &CacheRequestDetails, method: Method) -> Option<&'a mut CORSCacheEntry> {
+ // we can take the method from CORSRequest itself
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.credentials == request.credentials &&
+ e.header_or_method.match_method(&method));
+ entry
+ }
+}
+
+impl CORSCache for BasicCORSCache {
+ /// http://fetch.spec.whatwg.org/#concept-cache-clear
+ #[allow(dead_code)]
+ fn clear (&mut self, request: CacheRequestDetails) {
+ let BasicCORSCache(buf) = self.clone();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
+ *self = BasicCORSCache(new_buf);
+ }
+
+ // Remove old entries
+ fn cleanup(&mut self) {
+ let BasicCORSCache(buf) = self.clone();
+ let now = time::now().to_timespec();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect();
+ *self = BasicCORSCache(new_buf);
+ }
+
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
+ self.find_entry_by_header(&request, header_name).is_some()
+ }
+
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
+ match self.find_entry_by_header(&request, header_name).map(|e| e.max_age = new_max_age) {
+ Some(_) => true,
+ None => {
+ self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
+ request.credentials, HeaderData(header_name.to_string())));
+ false
+ }
+ }
+ }
+
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
+ self.find_entry_by_method(&request, method).is_some()
+ }
+
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
+ match self.find_entry_by_method(&request, method.clone()).map(|e| e.max_age = new_max_age) {
+ Some(_) => true,
+ None => {
+ self.insert(CORSCacheEntry::new(request.origin, request.destination, new_max_age,
+ request.credentials, MethodData(method)));
+ false
+ }
+ }
+ }
+
+ fn insert(&mut self, entry: CORSCacheEntry) {
+ self.cleanup();
+ let BasicCORSCache(ref mut buf) = *self;
+ buf.push(entry);
+ }
+}
+
+/// Various messages that can be sent to a CORSCacheTask
+pub enum CORSCacheTaskMsg {
+ Clear(CacheRequestDetails, Sender<()>),
+ Cleanup(Sender<()>),
+ MatchHeader(CacheRequestDetails, String, Sender<bool>),
+ MatchHeaderUpdate(CacheRequestDetails, String, uint, Sender<bool>),
+ MatchMethod(CacheRequestDetails, Method, Sender<bool>),
+ MatchMethodUpdate(CacheRequestDetails, Method, uint, Sender<bool>),
+ Insert(CORSCacheEntry, Sender<()>),
+ ExitMsg
+}
+
+/// A Sender to a CORSCacheTask
+///
+/// This can be used as a CORS Cache.
+/// The methods on this type block until they can run, and it behaves similar to a mutex
+pub type CORSCacheSender = Sender<CORSCacheTaskMsg>;
+
+impl CORSCache for CORSCacheSender {
+ fn clear (&mut self, request: CacheRequestDetails) {
+ let (tx, rx) = channel();
+ self.send(Clear(request, tx));
+ let _ = rx.recv_opt();
+ }
+
+ fn cleanup(&mut self) {
+ let (tx, rx) = channel();
+ self.send(Cleanup(tx));
+ let _ = rx.recv_opt();
+ }
+
+ fn match_header(&mut self, request: CacheRequestDetails, header_name: &str) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchHeader(request, header_name.to_string(), tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_header_and_update(&mut self, request: CacheRequestDetails, header_name: &str, new_max_age: uint) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchHeaderUpdate(request, header_name.to_string(), new_max_age, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_method(&mut self, request: CacheRequestDetails, method: Method) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchMethod(request, method, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn match_method_and_update(&mut self, request: CacheRequestDetails, method: Method, new_max_age: uint) -> bool {
+ let (tx, rx) = channel();
+ self.send(MatchMethodUpdate(request, method, new_max_age, tx));
+ rx.recv_opt().unwrap_or(false)
+ }
+
+ fn insert(&mut self, entry: CORSCacheEntry) {
+ let (tx, rx) = channel();
+ self.send(Insert(entry, tx));
+ let _ = rx.recv_opt();
+ }
+}
+
+/// A simple task-based CORS Cache that can be sent messages
+///
+/// #Example
+/// ```
+/// let task = CORSCacheTask::new();
+/// let builder = TaskBuilder::new().named("XHRTask");
+/// let mut sender = task.get_sender();
+/// builder.spawn(proc() { task.run() });
+/// sender.insert(CORSCacheEntry::new(/* parameters here */));
+/// ```
+pub struct CORSCacheTask {
+ receiver: Receiver<CORSCacheTaskMsg>,
+ cache: BasicCORSCache,
+ sender: CORSCacheSender
+}
+
+impl CORSCacheTask {
+ pub fn new() -> CORSCacheTask {
+ let (tx, rx) = channel();
+ CORSCacheTask {
+ receiver: rx,
+ cache: BasicCORSCache(vec![]),
+ sender: tx
+ }
+ }
+
+ /// Provides a sender to the cache task
+ pub fn get_sender(&self) -> CORSCacheSender {
+ self.sender.clone()
+ }
+
+ /// Runs the cache task
+ /// This blocks the current task, so it is advised
+ /// to spawn a new task for this
+ /// Send ExitMsg to the associated Sender to exit
+ pub fn run(&mut self) {
+ loop {
+ match self.receiver.recv() {
+ Clear(request, tx) => {
+ self.cache.clear(request);
+ tx.send(());
+ },
+ Cleanup(tx) => {
+ self.cache.cleanup();
+ tx.send(());
+ },
+ MatchHeader(request, header, tx) => {
+ tx.send(self.cache.match_header(request, header.as_slice()));
+ },
+ MatchHeaderUpdate(request, header, new_max_age, tx) => {
+ tx.send(self.cache.match_header_and_update(request, header.as_slice(), new_max_age));
+ },
+ MatchMethod(request, method, tx) => {
+ tx.send(self.cache.match_method(request, method));
+ },
+ MatchMethodUpdate(request, method, new_max_age, tx) => {
+ tx.send(self.cache.match_method_and_update(request, method, new_max_age));
+ },
+ Insert(entry, tx) => {
+ self.cache.insert(entry);
+ tx.send(());
+ },
+ ExitMsg => break
+ }
+ }
+ }
+}
diff --git a/components/net/fetch/request.rs b/components/net/fetch/request.rs
new file mode 100644
index 00000000000..c14efe9c59e
--- /dev/null
+++ b/components/net/fetch/request.rs
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use url::Url;
+use http::method::{Get, Method};
+use http::headers::request::HeaderCollection;
+use fetch::cors_cache::CORSCache;
+use fetch::response::Response;
+
+/// A [request context](http://fetch.spec.whatwg.org/#concept-request-context)
+pub enum Context {
+ Audio, Beacon, CSPreport, Download, Embed, Eventsource,
+ Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
+ ImageSet, Import, Internal, Location, Manifest, Object, Ping,
+ Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
+ Style, Track, Video, Worker, XMLHttpRequest, XSLT
+}
+
+/// A [request context frame type](http://fetch.spec.whatwg.org/#concept-request-context-frame-type)
+pub enum ContextFrameType {
+ Auxiliary,
+ TopLevel,
+ Nested,
+ ContextNone
+}
+
+/// A [referer](http://fetch.spec.whatwg.org/#concept-request-referrer)
+pub enum Referer {
+ RefererNone,
+ Client,
+ RefererUrl(Url)
+}
+
+/// A [request mode](http://fetch.spec.whatwg.org/#concept-request-mode)
+pub enum RequestMode {
+ SameOrigin,
+ NoCORS,
+ CORSMode,
+ ForcedPreflightMode
+}
+
+/// Request [credentials mode](http://fetch.spec.whatwg.org/#concept-request-credentials-mode)
+pub enum CredentialsMode {
+ Omit,
+ CredentialsSameOrigin,
+ Include
+}
+
+/// [Response tainting](http://fetch.spec.whatwg.org/#concept-request-response-tainting)
+pub enum ResponseTainting {
+ Basic,
+ CORSTainting,
+ Opaque
+}
+
+/// A [Request](http://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
+pub struct Request {
+ pub method: Method,
+ pub url: Url,
+ pub headers: HeaderCollection,
+ pub unsafe_request: bool,
+ pub body: Option<Vec<u8>>,
+ pub preserve_content_codings: bool,
+ // pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
+ // not the entire scope to avoid the libscript dependency
+ pub skip_service_worker: bool,
+ pub context: Context,
+ pub context_frame_type: ContextFrameType,
+ pub origin: Option<Url>,
+ pub force_origin_header: bool,
+ pub same_origin_data: bool,
+ pub referer: Referer,
+ pub authentication: bool,
+ pub sync: bool,
+ pub mode: RequestMode,
+ pub credentials_mode: CredentialsMode,
+ pub use_url_credentials: bool,
+ pub manual_redirect: bool,
+ pub redirect_count: uint,
+ pub response_tainting: ResponseTainting,
+ pub cache: Option<Box<CORSCache>>
+}
+
+impl Request {
+ pub fn new(url: Url, context: Context) -> Request {
+ Request {
+ method: Get,
+ url: url,
+ headers: HeaderCollection::new(),
+ unsafe_request: false,
+ body: None,
+ preserve_content_codings: false,
+ skip_service_worker: false,
+ context: context,
+ context_frame_type: ContextNone,
+ origin: None,
+ force_origin_header: false,
+ same_origin_data: false,
+ referer: Client,
+ authentication: false,
+ sync: false,
+ mode: NoCORS,
+ credentials_mode: Omit,
+ use_url_credentials: false,
+ manual_redirect: false,
+ redirect_count: 0,
+ response_tainting: Basic,
+ cache: None
+ }
+ }
+
+ /// [Basic fetch](http://fetch.spec.whatwg.org#basic-fetch)
+ pub fn basic_fetch(&mut self) -> Response {
+ match self.url.scheme.as_slice() {
+ "about" => match self.url.non_relative_scheme_data() {
+ Some(s) if s.as_slice() == "blank" => {
+ let mut response = Response::new();
+ let _ = response.headers.insert_raw("Content-Type".to_string(), b"text/html;charset=utf-8");
+ response
+ },
+ _ => Response::network_error()
+ },
+ "http" | "https" => {
+ self.http_fetch(false, false, false)
+ },
+ "blob" | "data" | "file" | "ftp" => {
+ // XXXManishearth handle these
+ fail!("Unimplemented scheme for Fetch")
+ },
+
+ _ => Response::network_error()
+ }
+ }
+
+ // [HTTP fetch](http://fetch.spec.whatwg.org#http-fetch)
+ pub fn http_fetch(&mut self, _cors_flag: bool, cors_preflight_flag: bool, _authentication_fetch_flag: bool) -> Response {
+ let response = Response::new();
+ // TODO: Service worker fetch
+ // Step 3
+ // Substep 1
+ self.skip_service_worker = true;
+ // Substep 2
+ if cors_preflight_flag {
+ // XXXManishearth stuff goes here
+ }
+ response
+ }
+}
diff --git a/components/net/fetch/response.rs b/components/net/fetch/response.rs
new file mode 100644
index 00000000000..359ec6aa394
--- /dev/null
+++ b/components/net/fetch/response.rs
@@ -0,0 +1,144 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use url::Url;
+use http::status::{Status, UnregisteredStatus};
+use StatusOk = http::status::Ok;
+use http::headers::HeaderEnum;
+use http::headers::response::HeaderCollection;
+use std::ascii::OwnedStrAsciiExt;
+use std::comm::Receiver;
+
+/// [Response type](http://fetch.spec.whatwg.org/#concept-response-type)
+#[deriving(Clone, PartialEq)]
+pub enum ResponseType {
+ Basic,
+ CORS,
+ Default,
+ Error,
+ Opaque
+}
+
+/// [Response termination reason](http://fetch.spec.whatwg.org/#concept-response-termination-reason)
+#[deriving(Clone)]
+pub enum TerminationReason {
+ EndUserAbort,
+ Fatal,
+ Timeout
+}
+
+/// The response body can still be pushed to after fetch
+/// This provides a way to store unfinished response bodies
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+#[deriving(Clone)]
+pub enum ResponseBody {
+ Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
+ Receiving(Vec<u8>),
+ Done(Vec<u8>),
+}
+
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+pub enum ResponseMsg {
+ Chunk(Vec<u8>),
+ Finished,
+ Errored
+}
+
+#[unstable = "I haven't yet decided exactly how the interface for this will be"]
+pub struct ResponseLoader {
+ response: Response,
+ chan: Receiver<ResponseMsg>
+}
+
+/// A [Response](http://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
+#[deriving(Clone)]
+pub struct Response {
+ pub response_type: ResponseType,
+ pub termination_reason: Option<TerminationReason>,
+ pub url: Option<Url>,
+ pub status: Status,
+ pub headers: HeaderCollection,
+ pub body: ResponseBody,
+ /// [Internal response](http://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response is a filtered response
+ pub internal_response: Option<Box<Response>>,
+}
+
+impl Response {
+ pub fn new() -> Response {
+ Response {
+ response_type: Default,
+ termination_reason: None,
+ url: None,
+ status: StatusOk,
+ headers: HeaderCollection::new(),
+ body: Empty,
+ internal_response: None
+ }
+ }
+
+ pub fn network_error() -> Response {
+ Response {
+ response_type: Error,
+ termination_reason: None,
+ url: None,
+ status: UnregisteredStatus(0, "".to_string()),
+ headers: HeaderCollection::new(),
+ body: Empty,
+ internal_response: None
+ }
+ }
+
+ pub fn is_network_error(&self) -> bool {
+ match self.response_type {
+ Error => true,
+ _ => false
+ }
+ }
+
+ /// Convert to a filtered response, of type `filter_type`.
+ /// Do not use with type Error or Default
+ pub fn to_filtered(self, filter_type: ResponseType) -> Response {
+ assert!(filter_type != Error);
+ assert!(filter_type != Default);
+ if self.is_network_error() {
+ return self;
+ }
+ let old_headers = self.headers.clone();
+ let mut response = self.clone();
+ response.internal_response = Some(box self);
+ match filter_type {
+ Default | Error => unreachable!(),
+ Basic => {
+ let mut headers = HeaderCollection::new();
+ for h in old_headers.iter() {
+ match h.header_name().into_ascii_lower().as_slice() {
+ "set-cookie" | "set-cookie2" => {},
+ _ => headers.insert(h)
+ }
+ }
+ response.headers = headers;
+ response.response_type = filter_type;
+ },
+ CORS => {
+ let mut headers = HeaderCollection::new();
+ for h in old_headers.iter() {
+ match h.header_name().into_ascii_lower().as_slice() {
+ "cache-control" | "content-language" |
+ "content-type" | "expires" | "last-modified" | "Pragma" => {},
+ // XXXManishearth handle Access-Control-Expose-Headers
+ _ => headers.insert(h)
+ }
+ }
+ response.headers = headers;
+ response.response_type = filter_type;
+ },
+ Opaque => {
+ response.headers = HeaderCollection::new();
+ response.status = UnregisteredStatus(0, "".to_string());
+ response.body = Empty;
+ }
+ }
+ response
+ }
+}
diff --git a/components/net/file_loader.rs b/components/net/file_loader.rs
new file mode 100644
index 00000000000..43c3191c600
--- /dev/null
+++ b/components/net/file_loader.rs
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use resource_task::{ProgressMsg, Metadata, Payload, Done, LoaderTask, start_sending};
+
+use std::io;
+use std::io::File;
+use servo_util::task::spawn_named;
+
+static READ_SIZE: uint = 8192;
+
+fn read_all(reader: &mut io::Stream, progress_chan: &Sender<ProgressMsg>)
+ -> Result<(), String> {
+ loop {
+ let mut buf = vec!();
+ match reader.push_at_least(READ_SIZE, READ_SIZE, &mut buf) {
+ Ok(_) => progress_chan.send(Payload(buf)),
+ Err(e) => match e.kind {
+ io::EndOfFile => {
+ if buf.len() > 0 {
+ progress_chan.send(Payload(buf));
+ }
+ return Ok(());
+ }
+ _ => return Err(e.desc.to_string()),
+ }
+ }
+ }
+}
+
+pub fn factory() -> LoaderTask {
+ let f: LoaderTask = proc(load_data, start_chan) {
+ let url = load_data.url;
+ assert!("file" == url.scheme.as_slice());
+ let progress_chan = start_sending(start_chan, Metadata::default(url.clone()));
+ spawn_named("file_loader", proc() {
+ match File::open_mode(&Path::new(url.serialize_path().unwrap()), io::Open, io::Read) {
+ Ok(ref mut reader) => {
+ let res = read_all(reader as &mut io::Stream, &progress_chan);
+ progress_chan.send(Done(res));
+ }
+ Err(e) => {
+ progress_chan.send(Done(Err(e.desc.to_string())));
+ }
+ };
+ });
+ };
+ f
+}
diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs
new file mode 100644
index 00000000000..c7cb56d4231
--- /dev/null
+++ b/components/net/http_loader.rs
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use resource_task::{Metadata, Payload, Done, LoadResponse, LoadData, LoaderTask, start_sending_opt};
+
+use std::collections::hashmap::HashSet;
+use http::client::{RequestWriter, NetworkStream};
+use http::headers::HeaderEnum;
+use std::io::Reader;
+use servo_util::task::spawn_named;
+use url::Url;
+
+pub fn factory() -> LoaderTask {
+ let f: LoaderTask = proc(url, start_chan) {
+ spawn_named("http_loader", proc() load(url, start_chan))
+ };
+ f
+}
+
+fn send_error(url: Url, err: String, start_chan: Sender<LoadResponse>) {
+ match start_sending_opt(start_chan, Metadata::default(url)) {
+ Ok(p) => p.send(Done(Err(err))),
+ _ => {}
+ };
+}
+
+fn load(load_data: LoadData, start_chan: Sender<LoadResponse>) {
+ // FIXME: At the time of writing this FIXME, servo didn't have any central
+ // location for configuration. If you're reading this and such a
+ // repository DOES exist, please update this constant to use it.
+ let max_redirects = 50u;
+ let mut iters = 0u;
+ let mut url = load_data.url.clone();
+ let mut redirected_to = HashSet::new();
+
+ // Loop to handle redirects.
+ loop {
+ iters = iters + 1;
+
+ if iters > max_redirects {
+ send_error(url, "too many redirects".to_string(), start_chan);
+ return;
+ }
+
+ if redirected_to.contains(&url) {
+ send_error(url, "redirect loop".to_string(), start_chan);
+ return;
+ }
+
+ redirected_to.insert(url.clone());
+
+ match url.scheme.as_slice() {
+ "http" | "https" => {}
+ _ => {
+ let s = format!("{:s} request, but we don't support that scheme", url.scheme);
+ send_error(url, s, start_chan);
+ return;
+ }
+ }
+
+ info!("requesting {:s}", url.serialize());
+
+ let request = RequestWriter::<NetworkStream>::new(load_data.method.clone(), url.clone());
+ let mut writer = match request {
+ Ok(w) => box w,
+ Err(e) => {
+ send_error(url, e.desc.to_string(), start_chan);
+ return;
+ }
+ };
+
+ // Preserve the `host` header set automatically by RequestWriter.
+ let host = writer.headers.host.clone();
+ writer.headers = box load_data.headers.clone();
+ writer.headers.host = host;
+ if writer.headers.accept_encoding.is_none() {
+ // We currently don't support HTTP Compression (FIXME #2587)
+ writer.headers.accept_encoding = Some(String::from_str("identity".as_slice()))
+ }
+ match load_data.data {
+ Some(ref data) => {
+ writer.headers.content_length = Some(data.len());
+ match writer.write(data.as_slice()) {
+ Err(e) => {
+ send_error(url, e.desc.to_string(), start_chan);
+ return;
+ }
+ _ => {}
+ }
+ },
+ _ => {}
+ }
+ let mut response = match writer.read_response() {
+ Ok(r) => r,
+ Err((_, e)) => {
+ send_error(url, e.desc.to_string(), start_chan);
+ return;
+ }
+ };
+
+ // Dump headers, but only do the iteration if info!() is enabled.
+ info!("got HTTP response {:s}, headers:", response.status.to_string());
+ info!("{:?}",
+ for header in response.headers.iter() {
+ info!(" - {:s}: {:s}", header.header_name(), header.header_value());
+ });
+
+ if 3 == (response.status.code() / 100) {
+ match response.headers.location {
+ Some(new_url) => {
+ // CORS (http://fetch.spec.whatwg.org/#http-fetch, status section, point 9, 10)
+ match load_data.cors {
+ Some(ref c) => {
+ if c.preflight {
+ // The preflight lied
+ send_error(url, "Preflight fetch inconsistent with main fetch".to_string(), start_chan);
+ return;
+ } else {
+ // XXXManishearth There are some CORS-related steps here,
+ // but they don't seem necessary until credentials are implemented
+ }
+ }
+ _ => {}
+ }
+ info!("redirecting to {:s}", new_url.serialize());
+ url = new_url;
+ continue;
+ }
+ None => ()
+ }
+ }
+
+ let mut metadata = Metadata::default(url);
+ metadata.set_content_type(&response.headers.content_type);
+ metadata.headers = Some(*response.headers.clone());
+ metadata.status = response.status.clone();
+
+ let progress_chan = match start_sending_opt(start_chan, metadata) {
+ Ok(p) => p,
+ _ => return
+ };
+ loop {
+ let mut buf = Vec::with_capacity(1024);
+
+ unsafe { buf.set_len(1024); }
+ match response.read(buf.as_mut_slice()) {
+ Ok(len) => {
+ unsafe { buf.set_len(len); }
+ if progress_chan.send_opt(Payload(buf)).is_err() {
+ // The send errors when the receiver is out of scope,
+ // which will happen if the fetch has timed out (or has been aborted)
+ // so we don't need to continue with the loading of the file here.
+ return;
+ }
+ }
+ Err(_) => {
+ let _ = progress_chan.send_opt(Done(Ok(())));
+ break;
+ }
+ }
+ }
+
+ // We didn't get redirected.
+ break;
+ }
+}
diff --git a/components/net/image/base.rs b/components/net/image/base.rs
new file mode 100644
index 00000000000..deda4ee8556
--- /dev/null
+++ b/components/net/image/base.rs
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::iter::range_step;
+use stb_image = stb_image::image;
+use png;
+
+// FIXME: Images must not be copied every frame. Instead we should atomically
+// reference count them.
+pub type Image = png::Image;
+
+
+static TEST_IMAGE: &'static [u8] = include_bin!("test.jpeg");
+
+pub fn test_image_bin() -> Vec<u8> {
+ TEST_IMAGE.iter().map(|&x| x).collect()
+}
+
+// TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
+fn byte_swap(data: &mut [u8]) {
+ let length = data.len();
+ for i in range_step(0, length, 4) {
+ let r = data[i + 2];
+ data[i + 2] = data[i + 0];
+ data[i + 0] = r;
+ }
+}
+
+pub fn load_from_memory(buffer: &[u8]) -> Option<Image> {
+ if buffer.len() == 0 {
+ return None;
+ }
+
+ if png::is_png(buffer) {
+ match png::load_png_from_memory(buffer) {
+ Ok(mut png_image) => {
+ match png_image.pixels {
+ png::RGB8(ref mut data) | png::RGBA8(ref mut data) => {
+ byte_swap(data.as_mut_slice());
+ }
+ _ => {}
+ }
+ Some(png_image)
+ }
+ Err(_err) => None,
+ }
+ } else {
+ // For non-png images, we use stb_image
+ // Can't remember why we do this. Maybe it's what cairo wants
+ static FORCE_DEPTH: uint = 4;
+
+ match stb_image::load_from_memory_with_depth(buffer, FORCE_DEPTH, true) {
+ stb_image::ImageU8(mut image) => {
+ assert!(image.depth == 4);
+ byte_swap(image.data.as_mut_slice());
+ Some(png::Image {
+ width: image.width as u32,
+ height: image.height as u32,
+ pixels: png::RGBA8(image.data)
+ })
+ }
+ stb_image::ImageF32(_image) => fail!("HDR images not implemented"),
+ stb_image::Error(_) => None
+ }
+ }
+}
diff --git a/components/net/image/holder.rs b/components/net/image/holder.rs
new file mode 100644
index 00000000000..11f055aad9d
--- /dev/null
+++ b/components/net/image/holder.rs
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use image::base::Image;
+use image_cache_task::{ImageReady, ImageNotReady, ImageFailed};
+use local_image_cache::LocalImageCache;
+
+use geom::size::Size2D;
+use std::mem;
+use sync::{Arc, Mutex};
+use url::Url;
+
+// FIXME: Nasty coupling here This will be a problem if we want to factor out image handling from
+// the network stack. This should probably be factored out into an interface and use dependency
+// injection.
+
+/// A struct to store image data. The image will be loaded once the first time it is requested,
+/// and an Arc will be stored. Clones of this Arc are given out on demand.
+#[deriving(Clone)]
+pub struct ImageHolder {
+ url: Url,
+ image: Option<Arc<Box<Image>>>,
+ cached_size: Size2D<int>,
+ local_image_cache: Arc<Mutex<LocalImageCache>>,
+}
+
+impl ImageHolder {
+ pub fn new(url: Url, local_image_cache: Arc<Mutex<LocalImageCache>>) -> ImageHolder {
+ debug!("ImageHolder::new() {}", url.serialize());
+ let holder = ImageHolder {
+ url: url,
+ image: None,
+ cached_size: Size2D(0,0),
+ local_image_cache: local_image_cache.clone(),
+ };
+
+ // Tell the image cache we're going to be interested in this url
+ // FIXME: These two messages must be sent to prep an image for use
+ // but they are intended to be spread out in time. Ideally prefetch
+ // should be done as early as possible and decode only once we
+ // are sure that the image will be used.
+ {
+ let val = holder.local_image_cache.lock();
+ let mut local_image_cache = val;
+ local_image_cache.prefetch(&holder.url);
+ local_image_cache.decode(&holder.url);
+ }
+
+ holder
+ }
+
+ /// This version doesn't perform any computation, but may be stale w.r.t. newly-available image
+ /// data that determines size.
+ ///
+ /// The intent is that the impure version is used during layout when dimensions are used for
+ /// computing layout.
+ pub fn size(&self) -> Size2D<int> {
+ self.cached_size
+ }
+
+ /// Query and update the current image size.
+ pub fn get_size(&mut self) -> Option<Size2D<int>> {
+ debug!("get_size() {}", self.url.serialize());
+ self.get_image().map(|img| {
+ self.cached_size = Size2D(img.width as int,
+ img.height as int);
+ self.cached_size.clone()
+ })
+ }
+
+ pub fn get_image_if_present(&self) -> Option<Arc<Box<Image>>> {
+ debug!("get_image_if_present() {}", self.url.serialize());
+ self.image.clone()
+ }
+
+ pub fn get_image(&mut self) -> Option<Arc<Box<Image>>> {
+ debug!("get_image() {}", self.url.serialize());
+
+ // If this is the first time we've called this function, load
+ // the image and store it for the future
+ if self.image.is_none() {
+ let port = {
+ let val = self.local_image_cache.lock();
+ let mut local_image_cache = val;
+ local_image_cache.get_image(&self.url)
+ };
+ match port.recv() {
+ ImageReady(image) => {
+ self.image = Some(image);
+ }
+ ImageNotReady => {
+ debug!("image not ready for {:s}", self.url.serialize());
+ }
+ ImageFailed => {
+ debug!("image decoding failed for {:s}", self.url.serialize());
+ }
+ }
+ }
+
+ // Clone isn't pure so we have to swap out the mutable image option
+ let image = mem::replace(&mut self.image, None);
+ let result = image.clone();
+ mem::replace(&mut self.image, image);
+
+ return result;
+ }
+}
+
diff --git a/components/net/image/test.jpeg b/components/net/image/test.jpeg
new file mode 100644
index 00000000000..1a0bdb7acd1
--- /dev/null
+++ b/components/net/image/test.jpeg
Binary files differ
diff --git a/components/net/image_cache_task.rs b/components/net/image_cache_task.rs
new file mode 100644
index 00000000000..de0c978c3cf
--- /dev/null
+++ b/components/net/image_cache_task.rs
@@ -0,0 +1,993 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use image::base::{Image, load_from_memory};
+use resource_task;
+use resource_task::{LoadData, ResourceTask};
+
+use std::comm::{channel, Receiver, Sender};
+use std::collections::hashmap::HashMap;
+use std::mem::replace;
+use std::task::spawn;
+use std::result;
+use sync::{Arc, Mutex};
+use serialize::{Encoder, Encodable};
+use url::Url;
+
+pub enum Msg {
+ /// Tell the cache that we may need a particular image soon. Must be posted
+ /// before Decode
+ Prefetch(Url),
+
+ /// Tell the cache to decode an image. Must be posted before GetImage/WaitForImage
+ Decode(Url),
+
+ /// Request an Image object for a URL. If the image is not is not immediately
+ /// available then ImageNotReady is returned.
+ GetImage(Url, Sender<ImageResponseMsg>),
+
+ /// Wait for an image to become available (or fail to load).
+ WaitForImage(Url, Sender<ImageResponseMsg>),
+
+ /// Clients must wait for a response before shutting down the ResourceTask
+ Exit(Sender<()>),
+
+ /// Used by the prefetch tasks to post back image binaries
+ StorePrefetchedImageData(Url, Result<Vec<u8>, ()>),
+
+ /// Used by the decoder tasks to post decoded images back to the cache
+ StoreImage(Url, Option<Arc<Box<Image>>>),
+
+ /// For testing
+ WaitForStore(Sender<()>),
+
+ /// For testing
+ WaitForStorePrefetched(Sender<()>),
+}
+
+#[deriving(Clone)]
+pub enum ImageResponseMsg {
+ ImageReady(Arc<Box<Image>>),
+ ImageNotReady,
+ ImageFailed
+}
+
+impl PartialEq for ImageResponseMsg {
+ fn eq(&self, other: &ImageResponseMsg) -> bool {
+ match (self, other) {
+ (&ImageReady(..), &ImageReady(..)) => fail!("unimplemented comparison"),
+ (&ImageNotReady, &ImageNotReady) => true,
+ (&ImageFailed, &ImageFailed) => true,
+
+ (&ImageReady(..), _) | (&ImageNotReady, _) | (&ImageFailed, _) => false
+ }
+ }
+}
+
+#[deriving(Clone)]
+pub struct ImageCacheTask {
+ chan: Sender<Msg>,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for ImageCacheTask {
+ fn encode(&self, _: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+type DecoderFactory = fn() -> proc(&[u8]) -> Option<Image>;
+
+impl ImageCacheTask {
+ pub fn new(resource_task: ResourceTask) -> ImageCacheTask {
+ let (chan, port) = channel();
+ let chan_clone = chan.clone();
+
+ spawn(proc() {
+ let mut cache = ImageCache {
+ resource_task: resource_task,
+ port: port,
+ chan: chan_clone,
+ state_map: HashMap::new(),
+ wait_map: HashMap::new(),
+ need_exit: None
+ };
+ cache.run();
+ });
+
+ ImageCacheTask {
+ chan: chan,
+ }
+ }
+
+ pub fn new_sync(resource_task: ResourceTask) -> ImageCacheTask {
+ let (chan, port) = channel();
+
+ spawn(proc() {
+ let inner_cache = ImageCacheTask::new(resource_task);
+
+ loop {
+ let msg: Msg = port.recv();
+
+ match msg {
+ GetImage(url, response) => {
+ inner_cache.send(WaitForImage(url, response));
+ }
+ Exit(response) => {
+ inner_cache.send(Exit(response));
+ break;
+ }
+ msg => inner_cache.send(msg)
+ }
+ }
+ });
+
+ ImageCacheTask {
+ chan: chan,
+ }
+ }
+}
+
+struct ImageCache {
+ /// A handle to the resource task for fetching the image binaries
+ resource_task: ResourceTask,
+ /// The port on which we'll receive client requests
+ port: Receiver<Msg>,
+ /// A copy of the shared chan to give to child tasks
+ chan: Sender<Msg>,
+ /// The state of processsing an image for a URL
+ state_map: HashMap<Url, ImageState>,
+ /// List of clients waiting on a WaitForImage response
+ wait_map: HashMap<Url, Arc<Mutex<Vec<Sender<ImageResponseMsg>>>>>,
+ need_exit: Option<Sender<()>>,
+}
+
+#[deriving(Clone)]
+enum ImageState {
+ Init,
+ Prefetching(AfterPrefetch),
+ Prefetched(Vec<u8>),
+ Decoding,
+ Decoded(Arc<Box<Image>>),
+ Failed
+}
+
+#[deriving(Clone)]
+enum AfterPrefetch {
+ DoDecode,
+ DoNotDecode
+}
+
+impl ImageCache {
+ pub fn run(&mut self) {
+ let mut store_chan: Option<Sender<()>> = None;
+ let mut store_prefetched_chan: Option<Sender<()>> = None;
+
+ loop {
+ let msg = self.port.recv();
+
+ debug!("image_cache_task: received: {:?}", msg);
+
+ match msg {
+ Prefetch(url) => self.prefetch(url),
+ StorePrefetchedImageData(url, data) => {
+ store_prefetched_chan.map(|chan| {
+ chan.send(());
+ });
+ store_prefetched_chan = None;
+
+ self.store_prefetched_image_data(url, data);
+ }
+ Decode(url) => self.decode(url),
+ StoreImage(url, image) => {
+ store_chan.map(|chan| {
+ chan.send(());
+ });
+ store_chan = None;
+
+ self.store_image(url, image)
+ }
+ GetImage(url, response) => self.get_image(url, response),
+ WaitForImage(url, response) => {
+ self.wait_for_image(url, response)
+ }
+ WaitForStore(chan) => store_chan = Some(chan),
+ WaitForStorePrefetched(chan) => store_prefetched_chan = Some(chan),
+ Exit(response) => {
+ assert!(self.need_exit.is_none());
+ self.need_exit = Some(response);
+ }
+ }
+
+ let need_exit = replace(&mut self.need_exit, None);
+
+ match need_exit {
+ Some(response) => {
+ // Wait until we have no outstanding requests and subtasks
+ // before exiting
+ let mut can_exit = true;
+ for (_, state) in self.state_map.iter() {
+ match *state {
+ Prefetching(..) => can_exit = false,
+ Decoding => can_exit = false,
+
+ Init | Prefetched(..) | Decoded(..) | Failed => ()
+ }
+ }
+
+ if can_exit {
+ response.send(());
+ break;
+ } else {
+ self.need_exit = Some(response);
+ }
+ }
+ None => ()
+ }
+ }
+ }
+
+ fn get_state(&self, url: Url) -> ImageState {
+ match self.state_map.find(&url) {
+ Some(state) => state.clone(),
+ None => Init
+ }
+ }
+
+ fn set_state(&mut self, url: Url, state: ImageState) {
+ self.state_map.insert(url, state);
+ }
+
+ fn prefetch(&mut self, url: Url) {
+ match self.get_state(url.clone()) {
+ Init => {
+ let to_cache = self.chan.clone();
+ let resource_task = self.resource_task.clone();
+ let url_clone = url.clone();
+
+ spawn(proc() {
+ let url = url_clone;
+ debug!("image_cache_task: started fetch for {:s}", url.serialize());
+
+ let image = load_image_data(url.clone(), resource_task.clone());
+
+ let result = if image.is_ok() {
+ Ok(image.unwrap())
+ } else {
+ Err(())
+ };
+ to_cache.send(StorePrefetchedImageData(url.clone(), result));
+ debug!("image_cache_task: ended fetch for {:s}", url.serialize());
+ });
+
+ self.set_state(url, Prefetching(DoNotDecode));
+ }
+
+ Prefetching(..) | Prefetched(..) | Decoding | Decoded(..) | Failed => {
+ // We've already begun working on this image
+ }
+ }
+ }
+
+ fn store_prefetched_image_data(&mut self, url: Url, data: Result<Vec<u8>, ()>) {
+ match self.get_state(url.clone()) {
+ Prefetching(next_step) => {
+ match data {
+ Ok(data) => {
+ self.set_state(url.clone(), Prefetched(data));
+ match next_step {
+ DoDecode => self.decode(url),
+ _ => ()
+ }
+ }
+ Err(..) => {
+ self.set_state(url.clone(), Failed);
+ self.purge_waiters(url, || ImageFailed);
+ }
+ }
+ }
+
+ Init
+ | Prefetched(..)
+ | Decoding
+ | Decoded(..)
+ | Failed => {
+ fail!("wrong state for storing prefetched image")
+ }
+ }
+ }
+
+ fn decode(&mut self, url: Url) {
+ match self.get_state(url.clone()) {
+ Init => fail!("decoding image before prefetch"),
+
+ Prefetching(DoNotDecode) => {
+ // We don't have the data yet, queue up the decode
+ self.set_state(url, Prefetching(DoDecode))
+ }
+
+ Prefetching(DoDecode) => {
+ // We don't have the data yet, but the decode request is queued up
+ }
+
+ Prefetched(data) => {
+ let to_cache = self.chan.clone();
+ let url_clone = url.clone();
+
+ spawn(proc() {
+ let url = url_clone;
+ debug!("image_cache_task: started image decode for {:s}", url.serialize());
+ let image = load_from_memory(data.as_slice());
+ let image = if image.is_some() {
+ Some(Arc::new(box image.unwrap()))
+ } else {
+ None
+ };
+ to_cache.send(StoreImage(url.clone(), image));
+ debug!("image_cache_task: ended image decode for {:s}", url.serialize());
+ });
+
+ self.set_state(url, Decoding);
+ }
+
+ Decoding | Decoded(..) | Failed => {
+ // We've already begun decoding
+ }
+ }
+ }
+
+ fn store_image(&mut self, url: Url, image: Option<Arc<Box<Image>>>) {
+
+ match self.get_state(url.clone()) {
+ Decoding => {
+ match image {
+ Some(image) => {
+ self.set_state(url.clone(), Decoded(image.clone()));
+ self.purge_waiters(url, || ImageReady(image.clone()) );
+ }
+ None => {
+ self.set_state(url.clone(), Failed);
+ self.purge_waiters(url, || ImageFailed );
+ }
+ }
+ }
+
+ Init
+ | Prefetching(..)
+ | Prefetched(..)
+ | Decoded(..)
+ | Failed => {
+ fail!("incorrect state in store_image")
+ }
+ }
+
+ }
+
+ fn purge_waiters(&mut self, url: Url, f: || -> ImageResponseMsg) {
+ match self.wait_map.pop(&url) {
+ Some(waiters) => {
+ let mut items = waiters.lock();
+ for response in items.iter() {
+ response.send(f());
+ }
+ }
+ None => ()
+ }
+ }
+
+ fn get_image(&self, url: Url, response: Sender<ImageResponseMsg>) {
+ match self.get_state(url.clone()) {
+ Init => fail!("request for image before prefetch"),
+ Prefetching(DoDecode) => response.send(ImageNotReady),
+ Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
+ Decoding => response.send(ImageNotReady),
+ Decoded(image) => response.send(ImageReady(image.clone())),
+ Failed => response.send(ImageFailed),
+ }
+ }
+
+ fn wait_for_image(&mut self, url: Url, response: Sender<ImageResponseMsg>) {
+ match self.get_state(url.clone()) {
+ Init => fail!("request for image before prefetch"),
+
+ Prefetching(DoNotDecode) | Prefetched(..) => fail!("request for image before decode"),
+
+ Prefetching(DoDecode) | Decoding => {
+ // We don't have this image yet
+ if self.wait_map.contains_key(&url) {
+ let waiters = self.wait_map.find_mut(&url).unwrap();
+ let mut response = Some(response);
+ let mut items = waiters.lock();
+ items.push(response.take().unwrap());
+ } else {
+ let response = vec!(response);
+ let wrapped = Arc::new(Mutex::new(response));
+ self.wait_map.insert(url, wrapped);
+ }
+ }
+
+ Decoded(image) => {
+ response.send(ImageReady(image.clone()));
+ }
+
+ Failed => {
+ response.send(ImageFailed);
+ }
+ }
+ }
+
+}
+
+
+pub trait ImageCacheTaskClient {
+ fn exit(&self);
+}
+
+impl ImageCacheTaskClient for ImageCacheTask {
+ fn exit(&self) {
+ let (response_chan, response_port) = channel();
+ self.send(Exit(response_chan));
+ response_port.recv();
+ }
+}
+
+impl ImageCacheTask {
+ pub fn send(&self, msg: Msg) {
+ self.chan.send(msg);
+ }
+
+ #[cfg(test)]
+ fn wait_for_store(&self) -> Receiver<()> {
+ let (chan, port) = channel();
+ self.send(WaitForStore(chan));
+ port
+ }
+
+ #[cfg(test)]
+ fn wait_for_store_prefetched(&self) -> Receiver<()> {
+ let (chan, port) = channel();
+ self.send(WaitForStorePrefetched(chan));
+ port
+ }
+}
+
+fn load_image_data(url: Url, resource_task: ResourceTask) -> Result<Vec<u8>, ()> {
+ let (response_chan, response_port) = channel();
+ resource_task.send(resource_task::Load(LoadData::new(url), response_chan));
+
+ let mut image_data = vec!();
+
+ let progress_port = response_port.recv().progress_port;
+ loop {
+ match progress_port.recv() {
+ resource_task::Payload(data) => {
+ image_data.push_all(data.as_slice());
+ }
+ resource_task::Done(result::Ok(..)) => {
+ return Ok(image_data.move_iter().collect());
+ }
+ resource_task::Done(result::Err(..)) => {
+ return Err(());
+ }
+ }
+ }
+}
+
+
+pub fn spawn_listener<A: Send>(f: proc(Receiver<A>):Send) -> Sender<A> {
+ let (setup_chan, setup_port) = channel();
+
+ spawn(proc() {
+ let (chan, port) = channel();
+ setup_chan.send(chan);
+ f(port);
+ });
+ setup_port.recv()
+}
+
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use resource_task;
+ use resource_task::{ResourceTask, Metadata, start_sending};
+ use image::base::test_image_bin;
+ use std::comm;
+ use url::Url;
+
+ trait Closure {
+ fn invoke(&self, _response: Sender<resource_task::ProgressMsg>) { }
+ }
+ struct DoesNothing;
+ impl Closure for DoesNothing { }
+
+ struct JustSendOK {
+ url_requested_chan: Sender<()>,
+ }
+ impl Closure for JustSendOK {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ self.url_requested_chan.send(());
+ response.send(resource_task::Done(Ok(())));
+ }
+ }
+
+ struct SendTestImage;
+ impl Closure for SendTestImage {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ response.send(resource_task::Payload(test_image_bin()));
+ response.send(resource_task::Done(Ok(())));
+ }
+ }
+
+ struct SendBogusImage;
+ impl Closure for SendBogusImage {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ response.send(resource_task::Payload(vec!()));
+ response.send(resource_task::Done(Ok(())));
+ }
+ }
+
+ struct SendTestImageErr;
+ impl Closure for SendTestImageErr {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ response.send(resource_task::Payload(test_image_bin()));
+ response.send(resource_task::Done(Err("".to_string())));
+ }
+ }
+
+ struct WaitSendTestImage {
+ wait_port: Receiver<()>,
+ }
+ impl Closure for WaitSendTestImage {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ // Don't send the data until after the client requests
+ // the image
+ self.wait_port.recv();
+ response.send(resource_task::Payload(test_image_bin()));
+ response.send(resource_task::Done(Ok(())));
+ }
+ }
+
+ struct WaitSendTestImageErr {
+ wait_port: Receiver<()>,
+ }
+ impl Closure for WaitSendTestImageErr {
+ fn invoke(&self, response: Sender<resource_task::ProgressMsg>) {
+ // Don't send the data until after the client requests
+ // the image
+ self.wait_port.recv();
+ response.send(resource_task::Payload(test_image_bin()));
+ response.send(resource_task::Done(Err("".to_string())));
+ }
+ }
+
+ fn mock_resource_task<T: Closure+Send>(on_load: Box<T>) -> ResourceTask {
+ spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
+ loop {
+ match port.recv() {
+ resource_task::Load(_, response) => {
+ let chan = start_sending(response, Metadata::default(
+ Url::parse("file:///fake").unwrap()));
+ on_load.invoke(chan);
+ }
+ resource_task::Exit => break
+ }
+ }
+ })
+ }
+
+ #[test]
+ fn should_exit_on_request() {
+ let mock_resource_task = mock_resource_task(box DoesNothing);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ #[should_fail]
+ fn should_fail_if_unprefetched_image_is_requested() {
+ let mock_resource_task = mock_resource_task(box DoesNothing);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let (chan, port) = channel();
+ image_cache_task.send(GetImage(url, chan));
+ port.recv();
+ }
+
+ #[test]
+ fn should_request_url_from_resource_task_on_prefetch() {
+ let (url_requested_chan, url_requested) = channel();
+
+ let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url));
+ url_requested.recv();
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_not_request_url_from_resource_task_on_multiple_prefetches() {
+ let (url_requested_chan, url_requested) = comm::channel();
+
+ let mock_resource_task = mock_resource_task(box JustSendOK { url_requested_chan: url_requested_chan});
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Prefetch(url));
+ url_requested.recv();
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ match url_requested.try_recv() {
+ Err(_) => (),
+ Ok(_) => fail!(),
+ };
+ }
+
+ #[test]
+ fn should_return_image_not_ready_if_data_has_not_arrived() {
+ let (wait_chan, wait_port) = comm::channel();
+
+ let mock_resource_task = mock_resource_task(box WaitSendTestImage{wait_port: wait_port});
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+ assert!(response_port.recv() == ImageNotReady);
+ wait_chan.send(());
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_decoded_image_data_if_data_has_arrived() {
+ let mock_resource_task = mock_resource_task(box SendTestImage);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+ match response_port.recv() {
+ ImageReady(_) => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_decoded_image_data_for_multiple_requests() {
+ let mock_resource_task = mock_resource_task(box SendTestImage);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ for _ in range(0u32, 2u32) {
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url.clone(), response_chan));
+ match response_port.recv() {
+ ImageReady(_) => (),
+ _ => fail!("bleh")
+ }
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_not_request_image_from_resource_task_if_image_is_already_available() {
+ let (image_bin_sent_chan, image_bin_sent) = comm::channel();
+
+ let (resource_task_exited_chan, resource_task_exited) = comm::channel();
+
+ let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
+ loop {
+ match port.recv() {
+ resource_task::Load(_, response) => {
+ let chan = start_sending(response, Metadata::default(
+ Url::parse("file:///fake").unwrap()));
+ chan.send(resource_task::Payload(test_image_bin()));
+ chan.send(resource_task::Done(Ok(())));
+ image_bin_sent_chan.send(());
+ }
+ resource_task::Exit => {
+ resource_task_exited_chan.send(());
+ break
+ }
+ }
+ }
+ });
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ image_bin_sent.recv();
+
+ image_cache_task.send(Prefetch(url.clone()));
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+
+ resource_task_exited.recv();
+
+ // Our resource task should not have received another request for the image
+ // because it's already cached
+ match image_bin_sent.try_recv() {
+ Err(_) => (),
+ Ok(_) => fail!(),
+ }
+ }
+
+ #[test]
+ fn should_not_request_image_from_resource_task_if_image_fetch_already_failed() {
+ let (image_bin_sent_chan, image_bin_sent) = comm::channel();
+
+ let (resource_task_exited_chan, resource_task_exited) = comm::channel();
+
+ let mock_resource_task = spawn_listener(proc(port: Receiver<resource_task::ControlMsg>) {
+ loop {
+ match port.recv() {
+ resource_task::Load(_, response) => {
+ let chan = start_sending(response, Metadata::default(
+ Url::parse("file:///fake").unwrap()));
+ chan.send(resource_task::Payload(test_image_bin()));
+ chan.send(resource_task::Done(Err("".to_string())));
+ image_bin_sent_chan.send(());
+ }
+ resource_task::Exit => {
+ resource_task_exited_chan.send(());
+ break
+ }
+ }
+ }
+ });
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ image_bin_sent.recv();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+
+ resource_task_exited.recv();
+
+ // Our resource task should not have received another request for the image
+ // because it's already cached
+ match image_bin_sent.try_recv() {
+ Err(_) => (),
+ Ok(_) => fail!(),
+ }
+ }
+
+ #[test]
+ fn should_return_failed_if_image_bin_cannot_be_fetched() {
+ let mock_resource_task = mock_resource_task(box SendTestImageErr);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store_prefetched();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+ match response_port.recv() {
+ ImageFailed => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_failed_for_multiple_get_image_requests_if_image_bin_cannot_be_fetched() {
+ let mock_resource_task = mock_resource_task(box SendTestImageErr);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store_prefetched();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url.clone(), response_chan));
+ match response_port.recv() {
+ ImageFailed => (),
+ _ => fail!("bleh")
+ }
+
+ // And ask again, we should get the same response
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+ match response_port.recv() {
+ ImageFailed => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_failed_if_image_decode_fails() {
+ let mock_resource_task = mock_resource_task(box SendBogusImage);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ // Make the request
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+
+ match response_port.recv() {
+ ImageFailed => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_image_on_wait_if_image_is_already_loaded() {
+ let mock_resource_task = mock_resource_task(box SendTestImage);
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ let join_port = image_cache_task.wait_for_store();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ // Wait until our mock resource task has sent the image to the image cache
+ join_port.recv();
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(WaitForImage(url, response_chan));
+ match response_port.recv() {
+ ImageReady(..) => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_image_on_wait_if_image_is_not_yet_loaded() {
+ let (wait_chan, wait_port) = comm::channel();
+
+ let mock_resource_task = mock_resource_task(box WaitSendTestImage {wait_port: wait_port});
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(WaitForImage(url, response_chan));
+
+ wait_chan.send(());
+
+ match response_port.recv() {
+ ImageReady(..) => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn should_return_image_failed_on_wait_if_image_fails_to_load() {
+ let (wait_chan, wait_port) = comm::channel();
+
+ let mock_resource_task = mock_resource_task(box WaitSendTestImageErr{wait_port: wait_port});
+
+ let image_cache_task = ImageCacheTask::new(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(WaitForImage(url, response_chan));
+
+ wait_chan.send(());
+
+ match response_port.recv() {
+ ImageFailed => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+
+ #[test]
+ fn sync_cache_should_wait_for_images() {
+ let mock_resource_task = mock_resource_task(box SendTestImage);
+
+ let image_cache_task = ImageCacheTask::new_sync(mock_resource_task.clone());
+ let url = Url::parse("file:///").unwrap();
+
+ image_cache_task.send(Prefetch(url.clone()));
+ image_cache_task.send(Decode(url.clone()));
+
+ let (response_chan, response_port) = comm::channel();
+ image_cache_task.send(GetImage(url, response_chan));
+ match response_port.recv() {
+ ImageReady(_) => (),
+ _ => fail!("bleh")
+ }
+
+ image_cache_task.exit();
+ mock_resource_task.send(resource_task::Exit);
+ }
+}
diff --git a/components/net/lib.rs b/components/net/lib.rs
new file mode 100644
index 00000000000..bb1c5d47b1a
--- /dev/null
+++ b/components/net/lib.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![feature(default_type_params, globs, managed_boxes, phase)]
+
+extern crate debug;
+extern crate collections;
+extern crate geom;
+extern crate http;
+extern crate png;
+#[phase(plugin, link)]
+extern crate log;
+extern crate serialize;
+extern crate servo_util = "util";
+extern crate stb_image;
+extern crate sync;
+extern crate time;
+extern crate url;
+
+/// Image handling.
+///
+/// It may be surprising that this goes in the network crate as opposed to the graphics crate.
+/// However, image handling is generally very integrated with the network stack (especially where
+/// caching is involved) and as a result it must live in here.
+pub mod image {
+ pub mod base;
+ pub mod holder;
+}
+
+pub mod file_loader;
+pub mod http_loader;
+pub mod data_loader;
+pub mod image_cache_task;
+pub mod local_image_cache;
+pub mod resource_task;
+
+/// An implementation of the [Fetch spec](http://fetch.spec.whatwg.org/)
+pub mod fetch {
+ #![allow(dead_code)] // XXXManishearth this is only temporary until the Fetch mod starts being used
+ pub mod request;
+ pub mod response;
+ pub mod cors_cache;
+}
diff --git a/components/net/local_image_cache.rs b/components/net/local_image_cache.rs
new file mode 100644
index 00000000000..1427c831654
--- /dev/null
+++ b/components/net/local_image_cache.rs
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*!
+An adapter for ImageCacheTask that does local caching to avoid
+extra message traffic, it also avoids waiting on the same image
+multiple times and thus triggering reflows multiple times.
+*/
+
+use image_cache_task::{Decode, GetImage, ImageCacheTask, ImageFailed, ImageNotReady, ImageReady};
+use image_cache_task::{ImageResponseMsg, Prefetch, WaitForImage};
+
+use std::comm::{Receiver, channel};
+use std::collections::hashmap::HashMap;
+use servo_util::task::spawn_named;
+use url::Url;
+
+pub trait ImageResponder {
+ fn respond(&self) -> proc(ImageResponseMsg):Send;
+}
+
+pub struct LocalImageCache {
+ image_cache_task: ImageCacheTask,
+ round_number: uint,
+ on_image_available: Option<Box<ImageResponder+Send>>,
+ state_map: HashMap<Url, ImageState>
+}
+
+impl LocalImageCache {
+ pub fn new(image_cache_task: ImageCacheTask) -> LocalImageCache {
+ LocalImageCache {
+ image_cache_task: image_cache_task,
+ round_number: 1,
+ on_image_available: None,
+ state_map: HashMap::new()
+ }
+ }
+}
+
+#[deriving(Clone)]
+struct ImageState {
+ prefetched: bool,
+ decoded: bool,
+ last_request_round: uint,
+ last_response: ImageResponseMsg
+}
+
+impl LocalImageCache {
+ /// The local cache will only do a single remote request for a given
+ /// URL in each 'round'. Layout should call this each time it begins
+ pub fn next_round(&mut self, on_image_available: Box<ImageResponder+Send>) {
+ self.round_number += 1;
+ self.on_image_available = Some(on_image_available);
+ }
+
+ pub fn prefetch(&mut self, url: &Url) {
+ {
+ let state = self.get_state(url);
+ if state.prefetched {
+ return
+ }
+
+ state.prefetched = true;
+ }
+
+ self.image_cache_task.send(Prefetch((*url).clone()));
+ }
+
+ pub fn decode(&mut self, url: &Url) {
+ {
+ let state = self.get_state(url);
+ if state.decoded {
+ return
+ }
+ state.decoded = true;
+ }
+
+ self.image_cache_task.send(Decode((*url).clone()));
+ }
+
+ // FIXME: Should return a Future
+ pub fn get_image(&mut self, url: &Url) -> Receiver<ImageResponseMsg> {
+ {
+ let round_number = self.round_number;
+ let state = self.get_state(url);
+
+ // Save the previous round number for comparison
+ let last_round = state.last_request_round;
+ // Set the current round number for this image
+ state.last_request_round = round_number;
+
+ match state.last_response {
+ ImageReady(ref image) => {
+ let (chan, port) = channel();
+ chan.send(ImageReady(image.clone()));
+ return port;
+ }
+ ImageNotReady => {
+ if last_round == round_number {
+ let (chan, port) = channel();
+ chan.send(ImageNotReady);
+ return port;
+ } else {
+ // We haven't requested the image from the
+ // remote cache this round
+ }
+ }
+ ImageFailed => {
+ let (chan, port) = channel();
+ chan.send(ImageFailed);
+ return port;
+ }
+ }
+ }
+
+ let (response_chan, response_port) = channel();
+ self.image_cache_task.send(GetImage((*url).clone(), response_chan));
+
+ let response = response_port.recv();
+ match response {
+ ImageNotReady => {
+ // Need to reflow when the image is available
+ // FIXME: Instead we should be just passing a Future
+ // to the caller, then to the display list. Finally,
+ // the compositor should be resonsible for waiting
+ // on the image to load and triggering layout
+ let image_cache_task = self.image_cache_task.clone();
+ assert!(self.on_image_available.is_some());
+ let on_image_available: proc(ImageResponseMsg):Send = self.on_image_available.as_ref().unwrap().respond();
+ let url = (*url).clone();
+ spawn_named("LocalImageCache", proc() {
+ let (response_chan, response_port) = channel();
+ image_cache_task.send(WaitForImage(url.clone(), response_chan));
+ on_image_available(response_port.recv());
+ });
+ }
+ _ => ()
+ }
+
+ // Put a copy of the response in the cache
+ let response_copy = match response {
+ ImageReady(ref image) => ImageReady(image.clone()),
+ ImageNotReady => ImageNotReady,
+ ImageFailed => ImageFailed
+ };
+ self.get_state(url).last_response = response_copy;
+
+ let (chan, port) = channel();
+ chan.send(response);
+ return port;
+ }
+
+ fn get_state<'a>(&'a mut self, url: &Url) -> &'a mut ImageState {
+ let state = self.state_map.find_or_insert_with(url.clone(), |_| {
+ let new_state = ImageState {
+ prefetched: false,
+ decoded: false,
+ last_request_round: 0,
+ last_response: ImageNotReady
+ };
+ new_state
+ });
+ state
+ }
+}
diff --git a/components/net/resource_task.rs b/components/net/resource_task.rs
new file mode 100644
index 00000000000..bdc1c3f2339
--- /dev/null
+++ b/components/net/resource_task.rs
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A task that takes a URL and streams back the binary data.
+
+use file_loader;
+use http_loader;
+use data_loader;
+
+use std::comm::{channel, Receiver, Sender};
+use std::task::TaskBuilder;
+use std::os;
+use http::headers::content_type::MediaType;
+use ResponseHeaderCollection = http::headers::response::HeaderCollection;
+use RequestHeaderCollection = http::headers::request::HeaderCollection;
+use http::method::{Method, Get};
+use url::Url;
+
+use StatusOk = http::status::Ok;
+use http::status::Status;
+
+
+pub enum ControlMsg {
+ /// Request the data associated with a particular URL
+ Load(LoadData, Sender<LoadResponse>),
+ Exit
+}
+
+#[deriving(Clone)]
+pub struct LoadData {
+ pub url: Url,
+ pub method: Method,
+ pub headers: RequestHeaderCollection,
+ pub data: Option<Vec<u8>>,
+ pub cors: Option<ResourceCORSData>
+}
+
+impl LoadData {
+ pub fn new(url: Url) -> LoadData {
+ LoadData {
+ url: url,
+ method: Get,
+ headers: RequestHeaderCollection::new(),
+ data: None,
+ cors: None
+ }
+ }
+}
+
+#[deriving(Clone)]
+pub struct ResourceCORSData {
+ /// CORS Preflight flag
+ pub preflight: bool,
+ /// Origin of CORS Request
+ pub origin: Url
+}
+
+/// Metadata about a loaded resource, such as is obtained from HTTP headers.
+pub struct Metadata {
+ /// Final URL after redirects.
+ pub final_url: Url,
+
+ /// MIME type / subtype.
+ pub content_type: Option<(String, String)>,
+
+ /// Character set.
+ pub charset: Option<String>,
+
+ /// Headers
+ pub headers: Option<ResponseHeaderCollection>,
+
+ /// HTTP Status
+ pub status: Status
+}
+
+impl Metadata {
+ /// Metadata with defaults for everything optional.
+ pub fn default(url: Url) -> Metadata {
+ Metadata {
+ final_url: url,
+ content_type: None,
+ charset: None,
+ headers: None,
+ status: StatusOk // http://fetch.spec.whatwg.org/#concept-response-status-message
+ }
+ }
+
+ /// Extract the parts of a MediaType that we care about.
+ pub fn set_content_type(&mut self, content_type: &Option<MediaType>) {
+ match *content_type {
+ None => (),
+ Some(MediaType { type_: ref type_,
+ subtype: ref subtype,
+ parameters: ref parameters }) => {
+ self.content_type = Some((type_.clone(), subtype.clone()));
+ for &(ref k, ref v) in parameters.iter() {
+ if "charset" == k.as_slice() {
+ self.charset = Some(v.clone());
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Message sent in response to `Load`. Contains metadata, and a port
+/// for receiving the data.
+///
+/// Even if loading fails immediately, we send one of these and the
+/// progress_port will provide the error.
+pub struct LoadResponse {
+ /// Metadata, such as from HTTP headers.
+ pub metadata: Metadata,
+ /// Port for reading data.
+ pub progress_port: Receiver<ProgressMsg>,
+}
+
+/// Messages sent in response to a `Load` message
+#[deriving(PartialEq,Show)]
+pub enum ProgressMsg {
+ /// Binary data - there may be multiple of these
+ Payload(Vec<u8>),
+ /// Indicates loading is complete, either successfully or not
+ Done(Result<(), String>)
+}
+
+/// For use by loaders in responding to a Load message.
+pub fn start_sending(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Sender<ProgressMsg> {
+ start_sending_opt(start_chan, metadata).ok().unwrap()
+}
+
+/// For use by loaders in responding to a Load message.
+pub fn start_sending_opt(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Result<Sender<ProgressMsg>, ()> {
+ let (progress_chan, progress_port) = channel();
+ let result = start_chan.send_opt(LoadResponse {
+ metadata: metadata,
+ progress_port: progress_port,
+ });
+ match result {
+ Ok(_) => Ok(progress_chan),
+ Err(_) => Err(())
+ }
+}
+
+/// Convenience function for synchronously loading a whole resource.
+pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
+ -> Result<(Metadata, Vec<u8>), String> {
+ let (start_chan, start_port) = channel();
+ resource_task.send(Load(LoadData::new(url), start_chan));
+ let response = start_port.recv();
+
+ let mut buf = vec!();
+ loop {
+ match response.progress_port.recv() {
+ Payload(data) => buf.push_all(data.as_slice()),
+ Done(Ok(())) => return Ok((response.metadata, buf)),
+ Done(Err(e)) => return Err(e)
+ }
+ }
+}
+
+/// Handle to a resource task
+pub type ResourceTask = Sender<ControlMsg>;
+
+pub type LoaderTask = proc(load_data: LoadData, Sender<LoadResponse>);
+
+/**
+Creates a task to load a specific resource
+
+The ResourceManager delegates loading to a different type of loader task for
+each URL scheme
+*/
+type LoaderTaskFactory = extern "Rust" fn() -> LoaderTask;
+
+/// Create a ResourceTask
+pub fn new_resource_task() -> ResourceTask {
+ let (setup_chan, setup_port) = channel();
+ let builder = TaskBuilder::new().named("ResourceManager");
+ builder.spawn(proc() {
+ ResourceManager::new(setup_port).start();
+ });
+ setup_chan
+}
+
+struct ResourceManager {
+ from_client: Receiver<ControlMsg>,
+}
+
+
+impl ResourceManager {
+ fn new(from_client: Receiver<ControlMsg>) -> ResourceManager {
+ ResourceManager {
+ from_client : from_client,
+ }
+ }
+}
+
+
+impl ResourceManager {
+ fn start(&self) {
+ loop {
+ match self.from_client.recv() {
+ Load(load_data, start_chan) => {
+ self.load(load_data, start_chan)
+ }
+ Exit => {
+ break
+ }
+ }
+ }
+ }
+
+ fn load(&self, mut load_data: LoadData, start_chan: Sender<LoadResponse>) {
+ let loader = match load_data.url.scheme.as_slice() {
+ "file" => file_loader::factory(),
+ "http" | "https" => http_loader::factory(),
+ "data" => data_loader::factory(),
+ "about" => {
+ match load_data.url.non_relative_scheme_data().unwrap() {
+ "crash" => fail!("Loading the about:crash URL."),
+ "failure" => {
+ // FIXME: Find a way to load this without relying on the `../src` directory.
+ let mut path = os::self_exe_path().expect("can't get exe path");
+ path.pop();
+ path.push_many(["src", "test", "html", "failure.html"]);
+ load_data.url = Url::from_file_path(&path).unwrap();
+ file_loader::factory()
+ }
+ _ => {
+ start_sending(start_chan, Metadata::default(load_data.url))
+ .send(Done(Err("Unknown about: URL.".to_string())));
+ return
+ }
+ }
+ },
+ _ => {
+ debug!("resource_task: no loader for scheme {:s}", load_data.url.scheme);
+ start_sending(start_chan, Metadata::default(load_data.url))
+ .send(Done(Err("no loader for scheme".to_string())));
+ return
+ }
+ };
+ debug!("resource_task: loading url: {:s}", load_data.url.serialize());
+ loader(load_data, start_chan);
+ }
+}
+
+#[test]
+fn test_exit() {
+ let resource_task = new_resource_task();
+ resource_task.send(Exit);
+}
+
+#[test]
+fn test_bad_scheme() {
+ let resource_task = new_resource_task();
+ let (start_chan, start) = channel();
+ let url = Url::parse("bogus://whatever").unwrap();
+ resource_task.send(Load(LoadData::new(url), start_chan));
+ let response = start.recv();
+ match response.progress_port.recv() {
+ Done(result) => { assert!(result.is_err()) }
+ _ => fail!("bleh")
+ }
+ resource_task.send(Exit);
+}
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
new file mode 100644
index 00000000000..1748a57956d
--- /dev/null
+++ b/components/script/Cargo.toml
@@ -0,0 +1,56 @@
+[package]
+name = "script"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+build = "make -f makefile.cargo"
+
+[lib]
+name = "script"
+path = "lib.rs"
+
+[dependencies.macros]
+path = "../macros"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.msg]
+path = "../msg"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.script_traits]
+path = "../script_traits"
+
+[dependencies.style]
+path = "../style"
+
+[dependencies.gfx]
+path = "../gfx"
+
+[dependencies.canvas]
+path = "../canvas"
+
+[dependencies.cssparser]
+git = "https://github.com/servo/rust-cssparser"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.hubbub]
+git = "https://github.com/servo/rust-hubbub"
+
+[dependencies.encoding]
+git = "https://github.com/lifthrasiir/rust-encoding"
+
+[dependencies.http]
+git = "https://github.com/servo/rust-http"
+branch = "servo"
+
+[dependencies.js]
+git = "https://github.com/servo/rust-mozjs"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
diff --git a/components/script/cors.rs b/components/script/cors.rs
new file mode 100644
index 00000000000..3a3fd98ee90
--- /dev/null
+++ b/components/script/cors.rs
@@ -0,0 +1,419 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A partial implementation of CORS
+//! For now this library is XHR-specific.
+//! For stuff involving `<img>`, `<iframe>`, `<form>`, etc please check what
+//! the request mode should be and compare with the fetch spec
+//! This library will eventually become the core of the Fetch crate
+//! with CORSRequest being expanded into FetchRequest (etc)
+
+use std::ascii::{StrAsciiExt, OwnedStrAsciiExt};
+use std::from_str::FromStr;
+use std::io::BufReader;
+use std::str::StrSlice;
+use time;
+use time::{now, Timespec};
+
+use ResponseHeaderCollection = http::headers::response::HeaderCollection;
+use RequestHeaderCollection = http::headers::request::HeaderCollection;
+use RequestHeader = http::headers::request::Header;
+
+use http::client::{RequestWriter, NetworkStream};
+use http::headers::{HeaderConvertible, HeaderEnum, HeaderValueByteIterator};
+use http::headers::content_type::MediaType;
+use http::headers::request::{Accept, AcceptLanguage, ContentLanguage, ContentType};
+use http::method::{Method, Get, Head, Post, Options};
+
+use url::{RelativeSchemeData, Url, UrlParser};
+
+#[deriving(Clone)]
+pub struct CORSRequest {
+ pub origin: Url,
+ pub destination: Url,
+ pub mode: RequestMode,
+ pub method: Method,
+ pub headers: RequestHeaderCollection,
+ /// CORS preflight flag (http://fetch.spec.whatwg.org/#concept-http-fetch)
+ /// Indicates that a CORS preflight request and/or cache check is to be performed
+ pub preflight_flag: bool
+}
+
+/// http://fetch.spec.whatwg.org/#concept-request-mode
+/// This only covers some of the request modes. The
+/// `same-origin` and `no CORS` modes are unnecessary for XHR.
+#[deriving(PartialEq, Clone)]
+pub enum RequestMode {
+ CORSMode, // CORS
+ ForcedPreflightMode // CORS-with-forced-preflight
+}
+
+impl CORSRequest {
+ /// Creates a CORS request if necessary. Will return an error when fetching is forbidden
+ pub fn maybe_new(referer: Url, destination: Url, mode: RequestMode,
+ method: Method, headers: RequestHeaderCollection) -> Result<Option<CORSRequest>, ()> {
+ if referer.scheme == destination.scheme &&
+ referer.host() == destination.host() &&
+ referer.port() == destination.port() {
+ return Ok(None); // Not cross-origin, proceed with a normal fetch
+ }
+ match destination.scheme.as_slice() {
+ // Todo: If the request's same origin data url flag is set (which isn't the case for XHR)
+ // we can fetch a data URL normally. about:blank can also be fetched by XHR
+ "http" | "https" => {
+ let mut req = CORSRequest::new(referer, destination, mode, method, headers);
+ req.preflight_flag = !is_simple_method(&req.method) || mode == ForcedPreflightMode;
+ if req.headers.iter().all(|h| is_simple_header(&h)) {
+ req.preflight_flag = true;
+ }
+ Ok(Some(req))
+ },
+ _ => Err(()),
+ }
+ }
+
+ fn new(mut referer: Url, destination: Url, mode: RequestMode, method: Method,
+ headers: RequestHeaderCollection) -> CORSRequest {
+ match referer.scheme_data {
+ RelativeSchemeData(ref mut data) => data.path = vec!(),
+ _ => {}
+ };
+ referer.fragment = None;
+ referer.query = None;
+ CORSRequest {
+ origin: referer,
+ destination: destination,
+ mode: mode,
+ method: method,
+ headers: headers,
+ preflight_flag: false
+ }
+ }
+
+ /// http://fetch.spec.whatwg.org/#concept-http-fetch
+ /// This method assumes that the CORS flag is set
+ /// This does not perform the full HTTP fetch, rather it handles part of the CORS filtering
+ /// if self.mode is ForcedPreflightMode, then the CORS-with-forced-preflight
+ /// fetch flag is set as well
+ pub fn http_fetch(&self) -> CORSResponse {
+ let response = CORSResponse::new();
+ // Step 2: Handle service workers (unimplemented)
+ // Step 3
+ // Substep 1: Service workers (unimplemented )
+ // Substep 2
+ let cache = &mut CORSCache(vec!()); // XXXManishearth Should come from user agent
+ if self.preflight_flag &&
+ !cache.match_method(self, &self.method) &&
+ !self.headers.iter().all(|h| is_simple_header(&h) && cache.match_header(self, h.header_name().as_slice())) {
+ if !is_simple_method(&self.method) || self.mode == ForcedPreflightMode {
+ return self.preflight_fetch();
+ // Everything after this is part of XHR::fetch()
+ // Expect the organization of code to improve once we have a fetch crate
+ }
+ }
+ response
+ }
+
+ /// http://fetch.spec.whatwg.org/#cors-preflight-fetch
+ fn preflight_fetch(&self) -> CORSResponse {
+ let error = CORSResponse::new_error();
+ let mut cors_response = CORSResponse::new();
+
+ let mut preflight = self.clone(); // Step 1
+ preflight.method = Options; // Step 2
+ preflight.headers = RequestHeaderCollection::new(); // Step 3
+ // Step 4
+ preflight.insert_string_header("Access-Control-Request-Method".to_string(), self.method.http_value());
+
+ // Step 5 - 7
+ let mut header_names = vec!();
+ for header in self.headers.iter() {
+ header_names.push(header.header_name().into_ascii_lower());
+ }
+ header_names.sort();
+ let header_list = header_names.connect(", "); // 0x2C 0x20
+ preflight.insert_string_header("Access-Control-Request-Headers".to_string(), header_list);
+
+ // Step 8 unnecessary, we don't use the request body
+ // Step 9, 10 unnecessary, we're writing our own fetch code
+
+ // Step 11
+ let preflight_request = RequestWriter::<NetworkStream>::new(preflight.method, preflight.destination);
+ let mut writer = match preflight_request {
+ Ok(w) => box w,
+ Err(_) => return error
+ };
+
+ let host = writer.headers.host.clone();
+ writer.headers = box preflight.headers.clone();
+ writer.headers.host = host;
+ let response = match writer.read_response() {
+ Ok(r) => r,
+ Err(_) => return error
+ };
+
+ // Step 12
+ match response.status.code() {
+ 200 .. 299 => {}
+ _ => return error
+ }
+ cors_response.headers = *response.headers.clone();
+ // Substeps 1-3 (parsing rules: http://fetch.spec.whatwg.org/#http-new-header-syntax)
+ fn find_header(headers: &ResponseHeaderCollection, name: &str) -> Option<String> {
+ headers.iter().find(|h| h.header_name().as_slice()
+ .eq_ignore_ascii_case(name))
+ .map(|h| h.header_value())
+ }
+ let methods_string = match find_header(&*response.headers, "Access-Control-Allow-Methods") {
+ Some(s) => s,
+ _ => return error
+ };
+ let methods = methods_string.as_slice().split(',');
+ let headers_string = match find_header(&*response.headers, "Access-Control-Allow-Headers") {
+ Some(s) => s,
+ _ => return error
+ };
+ let headers = headers_string.as_slice().split(0x2Cu8 as char);
+ // The ABNF # rule will consider consecutive delimeters as a single delimeter
+ let mut methods: Vec<String> = methods.filter(|s| s.len() > 0).map(|s| s.to_string()).collect();
+ let headers: Vec<String> = headers.filter(|s| s.len() > 0).map(|s| s.to_string()).collect();
+ // Substep 4
+ if methods.len() == 0 || preflight.mode == ForcedPreflightMode {
+ methods = vec!(self.method.http_value());
+ }
+ // Substep 5
+ if !is_simple_method(&self.method) &&
+ !methods.iter().any(|ref m| self.method.http_value().as_slice().eq_ignore_ascii_case(m.as_slice())) {
+ return error;
+ }
+ // Substep 6
+ for h in self.headers.iter() {
+ if is_simple_header(&h) {
+ continue;
+ }
+ if !headers.iter().any(|ref h2| h.header_name().as_slice().eq_ignore_ascii_case(h2.as_slice())) {
+ return error;
+ }
+ }
+ // Substep 7, 8
+ let max_age: uint = find_header(&*response.headers, "Access-Control-Max-Age")
+ .and_then(|h| FromStr::from_str(h.as_slice())).unwrap_or(0);
+ // Substep 9: Impose restrictions on max-age, if any (unimplemented)
+ // Substeps 10-12: Add a cache (partially implemented, XXXManishearth)
+ // This cache should come from the user agent, creating a new one here to check
+ // for compile time errors
+ let cache = &mut CORSCache(vec!());
+ for m in methods.iter() {
+ let maybe_method: Option<Method> = FromStr::from_str(m.as_slice());
+ maybe_method.map(|ref m| {
+ let cache_match = cache.match_method_and_update(self, m, max_age);
+ if !cache_match {
+ cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(),
+ max_age, false, MethodData(m.clone())));
+ }
+ });
+ }
+ for h in headers.iter() {
+ let cache_match = cache.match_header_and_update(self, h.as_slice(), max_age);
+ if !cache_match {
+ cache.insert(CORSCacheEntry::new(self.origin.clone(), self.destination.clone(),
+ max_age, false, HeaderData(h.to_string())));
+ }
+ }
+ cors_response
+ }
+
+ fn insert_string_header(&mut self, name: String, value: String) {
+ let value_bytes = value.into_bytes();
+ let mut reader = BufReader::new(value_bytes.as_slice());
+ let maybe_header: Option<RequestHeader> = HeaderEnum::value_from_stream(
+ String::from_str(name.as_slice()),
+ &mut HeaderValueByteIterator::new(&mut reader));
+ self.headers.insert(maybe_header.unwrap());
+ }
+}
+
+
+pub struct CORSResponse {
+ pub network_error: bool,
+ pub headers: ResponseHeaderCollection
+}
+
+impl CORSResponse {
+ fn new() -> CORSResponse {
+ CORSResponse {
+ network_error: false,
+ headers: ResponseHeaderCollection::new()
+ }
+ }
+
+ fn new_error() -> CORSResponse {
+ CORSResponse {
+ network_error: true,
+ headers: ResponseHeaderCollection::new()
+ }
+ }
+}
+
+// CORS Cache stuff
+
+/// A CORS cache object. Anchor it somewhere to the user agent.
+#[deriving(Clone)]
+pub struct CORSCache(Vec<CORSCacheEntry>);
+
+/// Union type for CORS cache entries
+/// Each entry might pertain to a header or method
+#[deriving(Clone)]
+pub enum HeaderOrMethod {
+ HeaderData(String),
+ MethodData(Method)
+}
+
+impl HeaderOrMethod {
+ fn match_header(&self, header_name: &str) -> bool {
+ match *self {
+ HeaderData(ref s) => s.as_slice().eq_ignore_ascii_case(header_name),
+ _ => false
+ }
+ }
+
+ fn match_method(&self, method: &Method) -> bool {
+ match *self {
+ MethodData(ref m) => m == method,
+ _ => false
+ }
+ }
+}
+
+// An entry in the CORS cache
+#[deriving(Clone)]
+pub struct CORSCacheEntry {
+ pub origin: Url,
+ pub url: Url,
+ pub max_age: uint,
+ pub credentials: bool,
+ pub header_or_method: HeaderOrMethod,
+ created: Timespec
+}
+
+impl CORSCacheEntry {
+ fn new (origin:Url, url: Url, max_age: uint, credentials: bool, header_or_method: HeaderOrMethod) -> CORSCacheEntry {
+ CORSCacheEntry {
+ origin: origin,
+ url: url,
+ max_age: max_age,
+ credentials: credentials,
+ header_or_method: header_or_method,
+ created: time::now().to_timespec()
+ }
+ }
+}
+
+impl CORSCache {
+ /// http://fetch.spec.whatwg.org/#concept-cache-clear
+ #[allow(dead_code)]
+ fn clear (&mut self, request: &CORSRequest) {
+ let CORSCache(buf) = self.clone();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| e.origin == request.origin && request.destination == e.url).collect();
+ *self = CORSCache(new_buf);
+ }
+
+ // Remove old entries
+ fn cleanup(&mut self) {
+ let CORSCache(buf) = self.clone();
+ let now = time::now().to_timespec();
+ let new_buf: Vec<CORSCacheEntry> = buf.move_iter().filter(|e| now.sec > e.created.sec + e.max_age as i64).collect();
+ *self = CORSCache(new_buf);
+ }
+
+ /// http://fetch.spec.whatwg.org/#concept-cache-match-header
+ fn find_entry_by_header<'a>(&'a mut self, request: &CORSRequest, header_name: &str) -> Option<&'a mut CORSCacheEntry> {
+ self.cleanup();
+ let CORSCache(ref mut buf) = *self;
+ // Credentials are not yet implemented here
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.header_or_method.match_header(header_name));
+ entry
+ }
+
+ fn match_header(&mut self, request: &CORSRequest, header_name: &str) -> bool {
+ self.find_entry_by_header(request, header_name).is_some()
+ }
+
+ fn match_header_and_update(&mut self, request: &CORSRequest, header_name: &str, new_max_age: uint) -> bool {
+ self.find_entry_by_header(request, header_name).map(|e| e.max_age = new_max_age).is_some()
+ }
+
+ fn find_entry_by_method<'a>(&'a mut self, request: &CORSRequest, method: &Method) -> Option<&'a mut CORSCacheEntry> {
+ // we can take the method from CORSRequest itself
+ self.cleanup();
+ let CORSCache(ref mut buf) = *self;
+ // Credentials are not yet implemented here
+ let entry = buf.mut_iter().find(|e| e.origin.scheme == request.origin.scheme &&
+ e.origin.host() == request.origin.host() &&
+ e.origin.port() == request.origin.port() &&
+ e.url == request.destination &&
+ e.header_or_method.match_method(method));
+ entry
+ }
+
+ /// http://fetch.spec.whatwg.org/#concept-cache-match-method
+ fn match_method(&mut self, request: &CORSRequest, method: &Method) -> bool {
+ self.find_entry_by_method(request, method).is_some()
+ }
+
+ fn match_method_and_update(&mut self, request: &CORSRequest, method: &Method, new_max_age: uint) -> bool {
+ self.find_entry_by_method(request, method).map(|e| e.max_age = new_max_age).is_some()
+ }
+
+ fn insert(&mut self, entry: CORSCacheEntry) {
+ self.cleanup();
+ let CORSCache(ref mut buf) = *self;
+ buf.push(entry);
+ }
+}
+
+fn is_simple_header(h: &RequestHeader) -> bool {
+ match *h {
+ Accept(_) | AcceptLanguage(_) | ContentLanguage(_) => true,
+ ContentType(MediaType {type_: ref t, subtype: ref s, ..}) => match (t.as_slice(), s.as_slice()) {
+ ("text", "plain") | ("application", "x-www-form-urlencoded") | ("multipart", "form-data") => true,
+ _ => false
+ },
+ _ => false
+ }
+}
+
+fn is_simple_method(m: &Method) -> bool {
+ match *m {
+ Get | Head | Post => true,
+ _ => false
+ }
+}
+
+/// Perform a CORS check on a header list and CORS request
+/// http://fetch.spec.whatwg.org/#cors-check
+pub fn allow_cross_origin_request(req: &CORSRequest, headers: &ResponseHeaderCollection) -> bool {
+ let allow_cross_origin_request = headers.iter().find(|h| h.header_name()
+ .as_slice()
+ .eq_ignore_ascii_case("Access-Control-Allow-Origin"));
+ match allow_cross_origin_request {
+ Some(h) => {
+ let origin_str = h.header_value();
+ if origin_str == "*".to_string() {
+ return true; // Not always true, depends on credentials mode
+ }
+ match UrlParser::new().parse(origin_str.as_slice()) {
+ Ok(parsed) => parsed.scheme == req.origin.scheme &&
+ parsed.host() == req.origin.host() &&
+ parsed.port() == req.origin.port(),
+ _ => false
+ }
+ },
+ None => false
+ }
+}
diff --git a/components/script/dom/attr.rs b/components/script/dom/attr.rs
new file mode 100644
index 00000000000..9f3fc9dc96e
--- /dev/null
+++ b/components/script/dom/attr.rs
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::AttrBinding;
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::InheritTypes::NodeCast;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::element::{Element, AttributeHandlers};
+use dom::node::Node;
+use dom::window::Window;
+use dom::virtualmethods::vtable_for;
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::namespace::Namespace;
+use servo_util::str::{DOMString, split_html_space_chars};
+use std::cell::{Ref, RefCell};
+use std::mem;
+use std::slice::Items;
+
+pub enum AttrSettingType {
+ FirstSetAttr,
+ ReplacedAttr,
+}
+
+#[deriving(PartialEq, Clone, Encodable)]
+pub enum AttrValue {
+ StringAttrValue(DOMString),
+ TokenListAttrValue(DOMString, Vec<Atom>),
+ UIntAttrValue(DOMString, u32),
+ AtomAttrValue(Atom),
+}
+
+impl AttrValue {
+ pub fn from_tokenlist(tokens: DOMString) -> AttrValue {
+ let atoms = split_html_space_chars(tokens.as_slice())
+ .map(|token| Atom::from_slice(token)).collect();
+ TokenListAttrValue(tokens, atoms)
+ }
+
+ pub fn from_u32(string: DOMString, default: u32) -> AttrValue {
+ let result: u32 = from_str(string.as_slice()).unwrap_or(default);
+ UIntAttrValue(string, result)
+ }
+
+ pub fn from_atomic(string: DOMString) -> AttrValue {
+ let value = Atom::from_slice(string.as_slice());
+ AtomAttrValue(value)
+ }
+
+ pub fn tokens<'a>(&'a self) -> Option<Items<'a, Atom>> {
+ match *self {
+ TokenListAttrValue(_, ref tokens) => Some(tokens.iter()),
+ _ => None
+ }
+ }
+}
+
+impl Str for AttrValue {
+ fn as_slice<'a>(&'a self) -> &'a str {
+ match *self {
+ StringAttrValue(ref value) |
+ TokenListAttrValue(ref value, _) |
+ UIntAttrValue(ref value, _) => value.as_slice(),
+ AtomAttrValue(ref value) => value.as_slice(),
+ }
+ }
+}
+
+#[deriving(Encodable)]
+pub struct Attr {
+ reflector_: Reflector,
+ local_name: Atom,
+ value: Traceable<RefCell<AttrValue>>,
+ pub name: Atom,
+ pub namespace: Namespace,
+ pub prefix: Option<DOMString>,
+
+ /// the element that owns this attribute.
+ owner: JS<Element>,
+}
+
+impl Reflectable for Attr {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+impl Attr {
+ fn new_inherited(local_name: Atom, value: AttrValue,
+ name: Atom, namespace: Namespace,
+ prefix: Option<DOMString>, owner: &JSRef<Element>) -> Attr {
+ Attr {
+ reflector_: Reflector::new(),
+ local_name: local_name,
+ value: Traceable::new(RefCell::new(value)),
+ name: name,
+ namespace: namespace,
+ prefix: prefix,
+ owner: JS::from_rooted(owner),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>, local_name: Atom, value: AttrValue,
+ name: Atom, namespace: Namespace,
+ prefix: Option<DOMString>, owner: &JSRef<Element>) -> Temporary<Attr> {
+ let attr = Attr::new_inherited(local_name, value, name, namespace, prefix, owner);
+ reflect_dom_object(box attr, &Window(*window), AttrBinding::Wrap)
+ }
+
+ pub fn set_value(&self, set_type: AttrSettingType, value: AttrValue) {
+ let owner = self.owner.root();
+ let node: &JSRef<Node> = NodeCast::from_ref(&*owner);
+ let namespace_is_null = self.namespace == namespace::Null;
+
+ match set_type {
+ ReplacedAttr => {
+ if namespace_is_null {
+ vtable_for(node).before_remove_attr(
+ self.local_name(),
+ self.value().as_slice().to_string())
+ }
+ }
+ FirstSetAttr => {}
+ }
+
+ *self.value.deref().borrow_mut() = value;
+
+ if namespace_is_null {
+ vtable_for(node).after_set_attr(
+ self.local_name(),
+ self.value().as_slice().to_string())
+ }
+ }
+
+ pub fn value<'a>(&'a self) -> Ref<'a, AttrValue> {
+ self.value.deref().borrow()
+ }
+
+ pub fn local_name<'a>(&'a self) -> &'a Atom {
+ &self.local_name
+ }
+}
+
+impl<'a> AttrMethods for JSRef<'a, Attr> {
+ fn LocalName(&self) -> DOMString {
+ self.local_name().as_slice().to_string()
+ }
+
+ fn Value(&self) -> DOMString {
+ self.value().as_slice().to_string()
+ }
+
+ fn SetValue(&self, value: DOMString) {
+ let owner = self.owner.root();
+ let value = owner.deref().parse_attribute(
+ &self.namespace, self.local_name(), value);
+ self.set_value(ReplacedAttr, value);
+ }
+
+ fn Name(&self) -> DOMString {
+ self.name.as_slice().to_string()
+ }
+
+ fn GetNamespaceURI(&self) -> Option<DOMString> {
+ match self.namespace.to_str() {
+ "" => None,
+ url => Some(url.to_string()),
+ }
+ }
+
+ fn GetPrefix(&self) -> Option<DOMString> {
+ self.prefix.clone()
+ }
+}
+
+pub trait AttrHelpersForLayout {
+ unsafe fn value_ref_forever(&self) -> &'static str;
+ unsafe fn value_atom_forever(&self) -> Option<Atom>;
+}
+
+impl AttrHelpersForLayout for Attr {
+ unsafe fn value_ref_forever(&self) -> &'static str {
+ // cast to point to T in RefCell<T> directly
+ let value = mem::transmute::<&RefCell<AttrValue>, &AttrValue>(self.value.deref());
+ value.as_slice()
+ }
+
+ unsafe fn value_atom_forever(&self) -> Option<Atom> {
+ // cast to point to T in RefCell<T> directly
+ let value = mem::transmute::<&RefCell<AttrValue>, &AttrValue>(self.value.deref());
+ match *value {
+ AtomAttrValue(ref val) => Some(val.clone()),
+ _ => None,
+ }
+ }
+}
diff --git a/components/script/dom/bindings/DESIGN.md b/components/script/dom/bindings/DESIGN.md
new file mode 100644
index 00000000000..0b8f6b01dd4
--- /dev/null
+++ b/components/script/dom/bindings/DESIGN.md
@@ -0,0 +1,38 @@
+# The design of Garbage collected DOM
+
+These are how Servo provides an object graph to SpiderMonkey's Garbage Collection.
+
+## Construct
+When Servo creates a Rusty DOM object, the binding code creates a wrapper `JSObject` with SpiderMonkey, is correspond to each Rusty DOM Object. It’s produced and set to the Rusty object in `FooBinding::Wrap`.
+
+In `FooBinding::Wrap`, the wrapper JSObject gets the pointer for Rusty Object to itself. And the same time, the wrapper `JSObject` are set to the Rusty Object’s `Reflector` field (All Rusty DOM objects have `dom::bindings::utils::Reflector` in their most basis field). These step are the “binding” work to create the relationship of both objects.
+
+
+## Trace object graph from SpiderMonkey GC.
+This is very tricky and magically mechanism helped by Rust Compiler.
+The outline is:
+
+1. SpiderMonkey's GC calls `JSClass.trace` defined in `FooBinding` when marking phase. This JSClass is basis of each wrapper JSObject.
+2. `JSClass.trace` calls `Foo::trace()` defined in InhertTypes.rs.
+3. `Foo::trace()` calls `Foo::encode()`. This `encode()` method is derived by the annotation of `#[deriving(Encodable)]` for a Rust DOM Element struct.
+4. `Foo::encode()` calls `JS<T>::encode()` method of `JS<T>` which is contained to `Foo`’s member. So this is the compiler magic! Rust compiler generates [codes like this](https://github.com/mozilla/rust/blob/db5206c32a879d5058d6a5cdce39c13c763fbdd5/src/libsyntax/ext/deriving/encodable.rs) for all structs annotated `#[deriving(Encodable)]`. This is based on [the assumption](https://github.com/mozilla/servo/blob/54da52fa774ce2ee59fcf811af595bf292169ad8/src/components/script/dom/bindings/trace.rs#L16).
+5. `JS<T>::encode()` calls `dom::bindings::trace::trace_reflector()`.
+6. `trace_reflector()` fetches the reflector that is reachable from a Rust object, and notifies it to the GC with using JSTracer.
+7. This operation continues to the end of the graph.
+8. Finally, GC gets whether Rust object lives or not from JSObjects which is hold by Rust object.
+
+
+## Destruct
+When destructing DOM objects (wrapper JSObjects) by SpiderMonkey, SpiderMonkey calls the `JSClass.finalize()` which is basis of each wrapper `JSObject`s. This method refers each `FooBinding::_finalize()`.
+
+In this function, the pointer of Rusty DOM Object that is contained in the wrapper JSObject is unwrapped, it cast to Rust owned pointer, and we assign its owned pointer to the empty local variable of `FooBinding::_finalize()`. Thus we can destruct the Rusty Object after we left from it.
+
+
+## Interact with Exact GC’s rooting
+For supporting SpiderMonkey’s exact GC rooting, we introduce [some types](https://github.com/mozilla/servo/wiki/Using-DOM-types):
+
+- `JS<T>` is used for the DOM typed field in a DOM type structure. GC can trace them recursively while enclosing DOM object (maybe root) is alive.
+- `Temporary<T>` is used as a return value of functions returning DOM type. They are rooted while they are alive. But a retun value gets moved around. It’s breakable for the LIFO ordering constraint. Thus we need introduce `Root<T>`.
+- `Root<T>` contains the pointer to `JSObject` which the represented DOM type has. SpiderMonkey's conservative stack scanner scans its pointer and mark a pointed `JSObject` as GC root.
+- `JSRef` is just a reference to the value rooted by `Root<T>`.
+- `RootCollection` is used for dynamic checking about rooting satisfies LIFO ordering, because SpiderMonkey GC requres LIFO order (See also: [Exact Stack Rooting - Storing a GCPointer on the CStack](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Internals/GC/Exact_Stack_Rooting)).
diff --git a/components/script/dom/bindings/callback.rs b/components/script/dom/bindings/callback.rs
new file mode 100644
index 00000000000..266abc3ab10
--- /dev/null
+++ b/components/script/dom/bindings/callback.rs
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Base classes to work with IDL callbacks.
+
+use dom::bindings::js::JSRef;
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, global_object_for_js_object};
+use js::jsapi::{JSContext, JSObject, JS_WrapObject, JS_ObjectIsCallable};
+use js::jsapi::JS_GetProperty;
+use js::jsval::{JSVal, UndefinedValue};
+
+use std::ptr;
+
+use serialize::{Encodable, Encoder};
+
+/// The exception handling used for a call.
+pub enum ExceptionHandling {
+ /// Report any exception and don't throw it to the caller code.
+ ReportExceptions,
+ /// Throw an exception to the caller code if the thrown exception is a
+ /// binding object for a DOMError from the caller's scope, otherwise report
+ /// it.
+ RethrowContentExceptions,
+ /// Throw any exception to the caller code.
+ RethrowExceptions
+}
+
+/// A common base class for representing IDL callback function types.
+#[deriving(Clone,PartialEq,Encodable)]
+pub struct CallbackFunction {
+ object: CallbackObject
+}
+
+impl CallbackFunction {
+ pub fn new(callback: *mut JSObject) -> CallbackFunction {
+ CallbackFunction {
+ object: CallbackObject {
+ callback: Traceable::new(callback)
+ }
+ }
+ }
+}
+
+/// A common base class for representing IDL callback interface types.
+#[deriving(Clone,PartialEq,Encodable)]
+pub struct CallbackInterface {
+ object: CallbackObject
+}
+
+/// A common base class for representing IDL callback function and
+/// callback interface types.
+#[allow(raw_pointer_deriving)]
+#[deriving(Clone,PartialEq,Encodable)]
+struct CallbackObject {
+ /// The underlying `JSObject`.
+ callback: Traceable<*mut JSObject>,
+}
+
+/// A trait to be implemented by concrete IDL callback function and
+/// callback interface types.
+pub trait CallbackContainer {
+ /// Create a new CallbackContainer object for the given `JSObject`.
+ fn new(callback: *mut JSObject) -> Self;
+ /// Returns the underlying `JSObject`.
+ fn callback(&self) -> *mut JSObject;
+}
+
+impl CallbackInterface {
+ /// Returns the underlying `JSObject`.
+ pub fn callback(&self) -> *mut JSObject {
+ *self.object.callback
+ }
+}
+
+impl CallbackFunction {
+ /// Returns the underlying `JSObject`.
+ pub fn callback(&self) -> *mut JSObject {
+ *self.object.callback
+ }
+}
+
+impl CallbackInterface {
+ /// Create a new CallbackInterface object for the given `JSObject`.
+ pub fn new(callback: *mut JSObject) -> CallbackInterface {
+ CallbackInterface {
+ object: CallbackObject {
+ callback: Traceable::new(callback)
+ }
+ }
+ }
+
+ /// Returns the property with the given `name`, if it is a callable object,
+ /// or `Err(())` otherwise. If it returns `Err(())`, a JSAPI exception is
+ /// pending.
+ pub fn GetCallableProperty(&self, cx: *mut JSContext, name: &str) -> Result<JSVal, ()> {
+ let mut callable = UndefinedValue();
+ unsafe {
+ let name = name.to_c_str();
+ if JS_GetProperty(cx, self.callback(), name.as_ptr(), &mut callable) == 0 {
+ return Err(());
+ }
+
+ if !callable.is_object() ||
+ JS_ObjectIsCallable(cx, callable.to_object()) == 0 {
+ // FIXME(#347)
+ //ThrowErrorMessage(cx, MSG_NOT_CALLABLE, description.get());
+ return Err(());
+ }
+ }
+ Ok(callable)
+ }
+}
+
+/// Wraps the reflector for `p` into the compartment of `cx`.
+pub fn WrapCallThisObject<T: Reflectable>(cx: *mut JSContext,
+ p: &JSRef<T>) -> *mut JSObject {
+ let mut obj = p.reflector().get_jsobject();
+ assert!(obj.is_not_null());
+
+ unsafe {
+ if JS_WrapObject(cx, &mut obj) == 0 {
+ return ptr::mut_null();
+ }
+ }
+
+ return obj;
+}
+
+/// A class that performs whatever setup we need to safely make a call while
+/// this class is on the stack. After `new` returns, the call is safe to make.
+pub struct CallSetup {
+ /// The `JSContext` used for the call.
+ cx: *mut JSContext,
+ /// The exception handling used for the call.
+ _handling: ExceptionHandling
+}
+
+impl CallSetup {
+ /// Performs the setup needed to make a call.
+ pub fn new<T: CallbackContainer>(callback: &T, handling: ExceptionHandling) -> CallSetup {
+ let global = global_object_for_js_object(callback.callback());
+ let global = global.root();
+ let cx = global.root_ref().get_cx();
+ CallSetup {
+ cx: cx,
+ _handling: handling
+ }
+ }
+
+ /// Returns the `JSContext` used for the call.
+ pub fn GetContext(&self) -> *mut JSContext {
+ self.cx
+ }
+}
diff --git a/components/script/dom/bindings/codegen/BindingGen.py b/components/script/dom/bindings/codegen/BindingGen.py
new file mode 100644
index 00000000000..408280dacfb
--- /dev/null
+++ b/components/script/dom/bindings/codegen/BindingGen.py
@@ -0,0 +1,52 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+sys.path.append("./parser/")
+sys.path.append("./ply/")
+import os
+import cPickle
+import WebIDL
+from Configuration import *
+from CodegenRust import CGBindingRoot, replaceFileIfChanged
+
+def generate_binding_rs(config, outputprefix, webidlfile):
+ """
+ |config| Is the configuration object.
+ |outputprefix| is a prefix to use for the header guards and filename.
+ """
+
+ filename = outputprefix + ".rs"
+ root = CGBindingRoot(config, outputprefix, webidlfile)
+ if replaceFileIfChanged(filename, root.define()):
+ print "Generating binding implementation: %s" % (filename)
+
+def main():
+ # Parse arguments.
+ from optparse import OptionParser
+ usagestring = "usage: %prog configFile outputPrefix webIDLFile"
+ o = OptionParser(usage=usagestring)
+ o.add_option("--verbose-errors", action='store_true', default=False,
+ help="When an error happens, display the Python traceback.")
+ (options, args) = o.parse_args()
+
+ if len(args) != 3:
+ o.error(usagestring)
+ configFile = os.path.normpath(args[0])
+ outputPrefix = args[1]
+ webIDLFile = os.path.normpath(args[2])
+
+ # Load the parsing results
+ f = open('ParserResults.pkl', 'rb')
+ parserData = cPickle.load(f)
+ f.close()
+
+ # Create the configuration data.
+ config = Configuration(configFile, parserData)
+
+ # Generate the prototype classes.
+ generate_binding_rs(config, outputPrefix, webIDLFile);
+
+if __name__ == '__main__':
+ main()
diff --git a/components/script/dom/bindings/codegen/BindingUtils.cpp b/components/script/dom/bindings/codegen/BindingUtils.cpp
new file mode 100644
index 00000000000..27ac92e3596
--- /dev/null
+++ b/components/script/dom/bindings/codegen/BindingUtils.cpp
@@ -0,0 +1,633 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdarg.h>
+
+#include "BindingUtils.h"
+
+#include "WrapperFactory.h"
+#include "xpcprivate.h"
+#include "XPCQuickStubs.h"
+
+namespace mozilla {
+namespace dom {
+
+JSErrorFormatString ErrorFormatString[] = {
+#define MSG_DEF(_name, _argc, _str) \
+ { _str, _argc, JSEXN_TYPEERR },
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+};
+
+const JSErrorFormatString*
+GetErrorMessage(void* aUserRef, const char* aLocale,
+ const unsigned aErrorNumber)
+{
+ MOZ_ASSERT(aErrorNumber < ArrayLength(ErrorFormatString));
+ return &ErrorFormatString[aErrorNumber];
+}
+
+bool
+ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...)
+{
+ va_list ap;
+ va_start(ap, aErrorNumber);
+ JS_ReportErrorNumberVA(aCx, GetErrorMessage, NULL,
+ static_cast<const unsigned>(aErrorNumber), ap);
+ va_end(ap);
+ return false;
+}
+
+bool
+DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs)
+{
+ for (; cs->name; ++cs) {
+ JSBool ok =
+ JS_DefineProperty(cx, obj, cs->name, cs->value, NULL, NULL,
+ JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
+ if (!ok) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline bool
+Define(JSContext* cx, JSObject* obj, JSFunctionSpec* spec) {
+ return JS_DefineFunctions(cx, obj, spec);
+}
+static inline bool
+Define(JSContext* cx, JSObject* obj, JSPropertySpec* spec) {
+ return JS_DefineProperties(cx, obj, spec);
+}
+static inline bool
+Define(JSContext* cx, JSObject* obj, ConstantSpec* spec) {
+ return DefineConstants(cx, obj, spec);
+}
+
+template<typename T>
+bool
+DefinePrefable(JSContext* cx, JSObject* obj, Prefable<T>* props)
+{
+ MOZ_ASSERT(props);
+ MOZ_ASSERT(props->specs);
+ do {
+ // Define if enabled
+ if (props->enabled) {
+ if (!Define(cx, obj, props->specs)) {
+ return false;
+ }
+ }
+ } while ((++props)->specs);
+ return true;
+}
+
+// We should use JSFunction objects for interface objects, but we need a custom
+// hasInstance hook because we have new interface objects on prototype chains of
+// old (XPConnect-based) bindings. Because Function.prototype.toString throws if
+// passed a non-Function object we also need to provide our own toString method
+// for interface objects.
+
+enum {
+ TOSTRING_CLASS_RESERVED_SLOT = 0,
+ TOSTRING_NAME_RESERVED_SLOT = 1
+};
+
+JSBool
+InterfaceObjectToString(JSContext* cx, unsigned argc, JS::Value *vp)
+{
+ JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp));
+
+ JSObject* obj = JS_THIS_OBJECT(cx, vp);
+ if (!obj) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO,
+ "null", "object");
+ return false;
+ }
+
+ jsval v = js::GetFunctionNativeReserved(callee, TOSTRING_CLASS_RESERVED_SLOT);
+ JSClass* clasp = static_cast<JSClass*>(JSVAL_TO_PRIVATE(v));
+
+ v = js::GetFunctionNativeReserved(callee, TOSTRING_NAME_RESERVED_SLOT);
+ JSString* jsname = static_cast<JSString*>(JSVAL_TO_STRING(v));
+ size_t length;
+ const jschar* name = JS_GetInternedStringCharsAndLength(jsname, &length);
+
+ if (js::GetObjectJSClass(obj) != clasp) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
+ NS_ConvertUTF16toUTF8(name).get(), "toString",
+ "object");
+ return false;
+ }
+
+ nsString str;
+ str.AppendLiteral("function ");
+ str.Append(name, length);
+ str.AppendLiteral("() {");
+ str.Append('\n');
+ str.AppendLiteral(" [native code]");
+ str.Append('\n');
+ str.AppendLiteral("}");
+
+ return xpc::NonVoidStringToJsval(cx, str, vp);
+}
+
+static JSObject*
+CreateInterfaceObject(JSContext* cx, JSObject* global, JSObject* receiver,
+ JSClass* constructorClass, JSNative constructorNative,
+ unsigned ctorNargs, JSObject* proto,
+ Prefable<JSFunctionSpec>* staticMethods,
+ Prefable<ConstantSpec>* constants,
+ const char* name)
+{
+ JSObject* constructor;
+ if (constructorClass) {
+ JSObject* functionProto = JS_GetFunctionPrototype(cx, global);
+ if (!functionProto) {
+ return NULL;
+ }
+ constructor = JS_NewObject(cx, constructorClass, functionProto, global);
+ } else {
+ MOZ_ASSERT(constructorNative);
+ JSFunction* fun = JS_NewFunction(cx, constructorNative, ctorNargs,
+ JSFUN_CONSTRUCTOR, global, name);
+ if (!fun) {
+ return NULL;
+ }
+ constructor = JS_GetFunctionObject(fun);
+ }
+ if (!constructor) {
+ return NULL;
+ }
+
+ if (staticMethods && !DefinePrefable(cx, constructor, staticMethods)) {
+ return NULL;
+ }
+
+ if (constructorClass) {
+ JSFunction* toString = js::DefineFunctionWithReserved(cx, constructor,
+ "toString",
+ InterfaceObjectToString,
+ 0, 0);
+ if (!toString) {
+ return NULL;
+ }
+
+ JSObject* toStringObj = JS_GetFunctionObject(toString);
+ js::SetFunctionNativeReserved(toStringObj, TOSTRING_CLASS_RESERVED_SLOT,
+ PRIVATE_TO_JSVAL(constructorClass));
+
+ JSString *str = ::JS_InternString(cx, name);
+ if (!str) {
+ return NULL;
+ }
+ js::SetFunctionNativeReserved(toStringObj, TOSTRING_NAME_RESERVED_SLOT,
+ STRING_TO_JSVAL(str));
+ }
+
+ if (constants && !DefinePrefable(cx, constructor, constants)) {
+ return NULL;
+ }
+
+ if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) {
+ return NULL;
+ }
+
+ JSBool alreadyDefined;
+ if (!JS_AlreadyHasOwnProperty(cx, receiver, name, &alreadyDefined)) {
+ return NULL;
+ }
+
+ // This is Enumerable: False per spec.
+ if (!alreadyDefined &&
+ !JS_DefineProperty(cx, receiver, name, OBJECT_TO_JSVAL(constructor), NULL,
+ NULL, 0)) {
+ return NULL;
+ }
+
+ return constructor;
+}
+
+static JSObject*
+CreateInterfacePrototypeObject(JSContext* cx, JSObject* global,
+ JSObject* parentProto, JSClass* protoClass,
+ Prefable<JSFunctionSpec>* methods,
+ Prefable<JSPropertySpec>* properties,
+ Prefable<ConstantSpec>* constants)
+{
+ JSObject* ourProto = JS_NewObjectWithUniqueType(cx, protoClass, parentProto,
+ global);
+ if (!ourProto) {
+ return NULL;
+ }
+
+ if (methods && !DefinePrefable(cx, ourProto, methods)) {
+ return NULL;
+ }
+
+ if (properties && !DefinePrefable(cx, ourProto, properties)) {
+ return NULL;
+ }
+
+ if (constants && !DefinePrefable(cx, ourProto, constants)) {
+ return NULL;
+ }
+
+ return ourProto;
+}
+
+JSObject*
+CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject *receiver,
+ JSObject* protoProto, JSClass* protoClass,
+ JSClass* constructorClass, JSNative constructor,
+ unsigned ctorNargs, const DOMClass* domClass,
+ Prefable<JSFunctionSpec>* methods,
+ Prefable<JSPropertySpec>* properties,
+ Prefable<ConstantSpec>* constants,
+ Prefable<JSFunctionSpec>* staticMethods, const char* name)
+{
+ MOZ_ASSERT(protoClass || constructorClass || constructor,
+ "Need at least one class or a constructor!");
+ MOZ_ASSERT(!(methods || properties) || protoClass,
+ "Methods or properties but no protoClass!");
+ MOZ_ASSERT(!staticMethods || constructorClass || constructor,
+ "Static methods but no constructorClass or constructor!");
+ MOZ_ASSERT(bool(name) == bool(constructorClass || constructor),
+ "Must have name precisely when we have an interface object");
+ MOZ_ASSERT(!constructorClass || !constructor);
+
+ JSObject* proto;
+ if (protoClass) {
+ proto = CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
+ methods, properties, constants);
+ if (!proto) {
+ return NULL;
+ }
+
+ js::SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT,
+ JS::PrivateValue(const_cast<DOMClass*>(domClass)));
+ }
+ else {
+ proto = NULL;
+ }
+
+ JSObject* interface;
+ if (constructorClass || constructor) {
+ interface = CreateInterfaceObject(cx, global, receiver, constructorClass,
+ constructor, ctorNargs, proto,
+ staticMethods, constants, name);
+ if (!interface) {
+ return NULL;
+ }
+ }
+
+ return protoClass ? proto : interface;
+}
+
+static bool
+NativeInterface2JSObjectAndThrowIfFailed(XPCLazyCallContext& aLccx,
+ JSContext* aCx,
+ JS::Value* aRetval,
+ xpcObjectHelper& aHelper,
+ const nsIID* aIID,
+ bool aAllowNativeWrapper)
+{
+ nsresult rv;
+ if (!XPCConvert::NativeInterface2JSObject(aLccx, aRetval, NULL, aHelper, aIID,
+ NULL, aAllowNativeWrapper, &rv)) {
+ // I can't tell if NativeInterface2JSObject throws JS exceptions
+ // or not. This is a sloppy stab at the right semantics; the
+ // method really ought to be fixed to behave consistently.
+ if (!JS_IsExceptionPending(aCx)) {
+ Throw<true>(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool
+DoHandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope,
+ nsISupports* value, JS::Value* vp)
+{
+ if (JS_IsExceptionPending(cx)) {
+ return false;
+ }
+
+ XPCLazyCallContext lccx(JS_CALLER, cx, scope);
+
+ if (value) {
+ xpcObjectHelper helper(value);
+ return NativeInterface2JSObjectAndThrowIfFailed(lccx, cx, vp, helper, NULL,
+ true);
+ }
+
+ return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+}
+
+// Can only be called with the immediate prototype of the instance object. Can
+// only be called on the prototype of an object known to be a DOM instance.
+JSBool
+InstanceClassHasProtoAtDepth(JSHandleObject protoObject, uint32_t protoID,
+ uint32_t depth)
+{
+ const DOMClass* domClass = static_cast<DOMClass*>(
+ js::GetReservedSlot(protoObject, DOM_PROTO_INSTANCE_CLASS_SLOT).toPrivate());
+ return (uint32_t)domClass->mInterfaceChain[depth] == protoID;
+}
+
+// Only set allowNativeWrapper to false if you really know you need it, if in
+// doubt use true. Setting it to false disables security wrappers.
+bool
+XPCOMObjectToJsval(JSContext* cx, JSObject* scope, xpcObjectHelper &helper,
+ const nsIID* iid, bool allowNativeWrapper, JS::Value* rval)
+{
+ XPCLazyCallContext lccx(JS_CALLER, cx, scope);
+
+ if (!NativeInterface2JSObjectAndThrowIfFailed(lccx, cx, rval, helper, iid,
+ allowNativeWrapper)) {
+ return false;
+ }
+
+#ifdef DEBUG
+ JSObject* jsobj = JSVAL_TO_OBJECT(*rval);
+ if (jsobj && !js::GetObjectParent(jsobj))
+ NS_ASSERTION(js::GetObjectClass(jsobj)->flags & JSCLASS_IS_GLOBAL,
+ "Why did we recreate this wrapper?");
+#endif
+
+ return true;
+}
+
+JSBool
+QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ JS::Value thisv = JS_THIS(cx, vp);
+ if (thisv == JSVAL_NULL)
+ return false;
+
+ // Get the object. It might be a security wrapper, in which case we do a checked
+ // unwrap.
+ JSObject* origObj = JSVAL_TO_OBJECT(thisv);
+ JSObject* obj = js::UnwrapObjectChecked(cx, origObj);
+ if (!obj)
+ return false;
+
+ nsISupports* native;
+ if (!UnwrapDOMObjectToISupports(obj, native)) {
+ return Throw<true>(cx, NS_ERROR_FAILURE);
+ }
+
+ if (argc < 1) {
+ return Throw<true>(cx, NS_ERROR_XPC_NOT_ENOUGH_ARGS);
+ }
+
+ JS::Value* argv = JS_ARGV(cx, vp);
+ if (!argv[0].isObject()) {
+ return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+ }
+
+ nsIJSIID* iid;
+ xpc_qsSelfRef iidRef;
+ if (NS_FAILED(xpc_qsUnwrapArg<nsIJSIID>(cx, argv[0], &iid, &iidRef.ptr,
+ &argv[0]))) {
+ return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+ }
+ MOZ_ASSERT(iid);
+
+ if (iid->GetID()->Equals(NS_GET_IID(nsIClassInfo))) {
+ nsresult rv;
+ nsCOMPtr<nsIClassInfo> ci = do_QueryInterface(native, &rv);
+ if (NS_FAILED(rv)) {
+ return Throw<true>(cx, rv);
+ }
+
+ return WrapObject(cx, origObj, ci, &NS_GET_IID(nsIClassInfo), vp);
+ }
+
+ // Lie, otherwise we need to check classinfo or QI
+ *vp = thisv;
+ return true;
+}
+
+JSBool
+ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp)
+{
+ return ThrowErrorMessage(cx, MSG_ILLEGAL_CONSTRUCTOR);
+}
+
+bool
+XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id,
+ JSPropertyDescriptor* desc,
+ // And the things we need to determine the descriptor
+ Prefable<JSFunctionSpec>* methods,
+ jsid* methodIds,
+ JSFunctionSpec* methodSpecs,
+ size_t methodCount,
+ Prefable<JSPropertySpec>* attributes,
+ jsid* attributeIds,
+ JSPropertySpec* attributeSpecs,
+ size_t attributeCount,
+ Prefable<ConstantSpec>* constants,
+ jsid* constantIds,
+ ConstantSpec* constantSpecs,
+ size_t constantCount)
+{
+ for (size_t prefIdx = 0; prefIdx < methodCount; ++prefIdx) {
+ MOZ_ASSERT(methods[prefIdx].specs);
+ if (methods[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = methods[prefIdx].specs - methodSpecs;
+ for ( ; methodIds[i] != JSID_VOID; ++i) {
+ if (id == methodIds[i]) {
+ JSFunction *fun = JS_NewFunctionById(cx, methodSpecs[i].call.op,
+ methodSpecs[i].nargs, 0,
+ wrapper, id);
+ if (!fun) {
+ return false;
+ }
+ SET_JITINFO(fun, methodSpecs[i].call.info);
+ JSObject *funobj = JS_GetFunctionObject(fun);
+ desc->value.setObject(*funobj);
+ desc->attrs = methodSpecs[i].flags;
+ desc->obj = wrapper;
+ desc->setter = nullptr;
+ desc->getter = nullptr;
+ return true;
+ }
+ }
+ }
+ }
+
+ for (size_t prefIdx = 0; prefIdx < attributeCount; ++prefIdx) {
+ MOZ_ASSERT(attributes[prefIdx].specs);
+ if (attributes[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = attributes[prefIdx].specs - attributeSpecs;
+ for ( ; attributeIds[i] != JSID_VOID; ++i) {
+ if (id == attributeIds[i]) {
+ // Because of centralization, we need to make sure we fault in the
+ // JitInfos as well. At present, until the JSAPI changes, the easiest
+ // way to do this is wrap them up as functions ourselves.
+ desc->attrs = attributeSpecs[i].flags & ~JSPROP_NATIVE_ACCESSORS;
+ // They all have getters, so we can just make it.
+ JSObject *global = JS_GetGlobalForObject(cx, wrapper);
+ JSFunction *fun = JS_NewFunction(cx, (JSNative)attributeSpecs[i].getter.op,
+ 0, 0, global, NULL);
+ if (!fun)
+ return false;
+ SET_JITINFO(fun, attributeSpecs[i].getter.info);
+ JSObject *funobj = JS_GetFunctionObject(fun);
+ desc->getter = js::CastAsJSPropertyOp(funobj);
+ desc->attrs |= JSPROP_GETTER;
+ if (attributeSpecs[i].setter.op) {
+ // We have a setter! Make it.
+ fun = JS_NewFunction(cx, (JSNative)attributeSpecs[i].setter.op,
+ 1, 0, global, NULL);
+ if (!fun)
+ return false;
+ SET_JITINFO(fun, attributeSpecs[i].setter.info);
+ funobj = JS_GetFunctionObject(fun);
+ desc->setter = js::CastAsJSStrictPropertyOp(funobj);
+ desc->attrs |= JSPROP_SETTER;
+ } else {
+ desc->setter = NULL;
+ }
+ desc->obj = wrapper;
+ return true;
+ }
+ }
+ }
+ }
+
+ for (size_t prefIdx = 0; prefIdx < constantCount; ++prefIdx) {
+ MOZ_ASSERT(constants[prefIdx].specs);
+ if (constants[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = constants[prefIdx].specs - constantSpecs;
+ for ( ; constantIds[i] != JSID_VOID; ++i) {
+ if (id == constantIds[i]) {
+ desc->attrs = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT;
+ desc->obj = wrapper;
+ desc->value = constantSpecs[i].value;
+ return true;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+XrayEnumerateProperties(JS::AutoIdVector& props,
+ Prefable<JSFunctionSpec>* methods,
+ jsid* methodIds,
+ JSFunctionSpec* methodSpecs,
+ size_t methodCount,
+ Prefable<JSPropertySpec>* attributes,
+ jsid* attributeIds,
+ JSPropertySpec* attributeSpecs,
+ size_t attributeCount,
+ Prefable<ConstantSpec>* constants,
+ jsid* constantIds,
+ ConstantSpec* constantSpecs,
+ size_t constantCount)
+{
+ for (size_t prefIdx = 0; prefIdx < methodCount; ++prefIdx) {
+ MOZ_ASSERT(methods[prefIdx].specs);
+ if (methods[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = methods[prefIdx].specs - methodSpecs;
+ for ( ; methodIds[i] != JSID_VOID; ++i) {
+ if ((methodSpecs[i].flags & JSPROP_ENUMERATE) &&
+ !props.append(methodIds[i])) {
+ return false;
+ }
+ }
+ }
+ }
+
+ for (size_t prefIdx = 0; prefIdx < attributeCount; ++prefIdx) {
+ MOZ_ASSERT(attributes[prefIdx].specs);
+ if (attributes[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = attributes[prefIdx].specs - attributeSpecs;
+ for ( ; attributeIds[i] != JSID_VOID; ++i) {
+ if ((attributeSpecs[i].flags & JSPROP_ENUMERATE) &&
+ !props.append(attributeIds[i])) {
+ return false;
+ }
+ }
+ }
+ }
+
+ for (size_t prefIdx = 0; prefIdx < constantCount; ++prefIdx) {
+ MOZ_ASSERT(constants[prefIdx].specs);
+ if (constants[prefIdx].enabled) {
+ // Set i to be the index into our full list of ids/specs that we're
+ // looking at now.
+ size_t i = constants[prefIdx].specs - constantSpecs;
+ for ( ; constantIds[i] != JSID_VOID; ++i) {
+ if (!props.append(constantIds[i])) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+GetPropertyOnPrototype(JSContext* cx, JSObject* proxy, jsid id, bool* found,
+ JS::Value* vp)
+{
+ JSObject* proto;
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ *found = false;
+ return true;
+ }
+
+ JSBool hasProp;
+ if (!JS_HasPropertyById(cx, proto, id, &hasProp)) {
+ return false;
+ }
+
+ *found = hasProp;
+ if (!hasProp || !vp) {
+ return true;
+ }
+
+ return JS_ForwardGetPropertyTo(cx, proto, id, proxy, vp);
+}
+
+bool
+HasPropertyOnPrototype(JSContext* cx, JSObject* proxy, DOMProxyHandler* handler,
+ jsid id)
+{
+ Maybe<JSAutoCompartment> ac;
+ if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
+ proxy = js::UnwrapObject(proxy);
+ ac.construct(cx, proxy);
+ }
+ MOZ_ASSERT(js::IsProxy(proxy) && js::GetProxyHandler(proxy) == handler);
+
+ bool found;
+ // We ignore an error from GetPropertyOnPrototype.
+ return !GetPropertyOnPrototype(cx, proxy, id, &found, NULL) || found;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/components/script/dom/bindings/codegen/BindingUtils.h b/components/script/dom/bindings/codegen/BindingUtils.h
new file mode 100644
index 00000000000..ee9d6c3691c
--- /dev/null
+++ b/components/script/dom/bindings/codegen/BindingUtils.h
@@ -0,0 +1,1151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BindingUtils_h__
+#define mozilla_dom_BindingUtils_h__
+
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/dom/workers/Workers.h"
+#include "mozilla/ErrorResult.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+
+#include "nsIXPConnect.h"
+#include "qsObjectHelper.h"
+#include "xpcpublic.h"
+#include "nsTraceRefcnt.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/Likely.h"
+
+// nsGlobalWindow implements nsWrapperCache, but doesn't always use it. Don't
+// try to use it without fixing that first.
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+
+enum ErrNum {
+#define MSG_DEF(_name, _argc, _str) \
+ _name,
+#include "mozilla/dom/Errors.msg"
+#undef MSG_DEF
+ Err_Limit
+};
+
+bool
+ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...);
+
+template<bool mainThread>
+inline bool
+Throw(JSContext* cx, nsresult rv)
+{
+ using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
+
+ // XXX Introduce exception machinery.
+ if (mainThread) {
+ xpc::Throw(cx, rv);
+ } else {
+ if (!JS_IsExceptionPending(cx)) {
+ ThrowDOMExceptionForNSResult(cx, rv);
+ }
+ }
+ return false;
+}
+
+template<bool mainThread>
+inline bool
+ThrowMethodFailedWithDetails(JSContext* cx, const ErrorResult& rv,
+ const char* /* ifaceName */,
+ const char* /* memberName */)
+{
+ return Throw<mainThread>(cx, rv.ErrorCode());
+}
+
+inline bool
+IsDOMClass(const JSClass* clasp)
+{
+ return clasp->flags & JSCLASS_IS_DOMJSCLASS;
+}
+
+inline bool
+IsDOMClass(const js::Class* clasp)
+{
+ return IsDOMClass(Jsvalify(clasp));
+}
+
+// It's ok for eRegularDOMObject and eProxyDOMObject to be the same, but
+// eNonDOMObject should always be different from the other two. This enum
+// shouldn't be used to differentiate between non-proxy and proxy bindings.
+enum DOMObjectSlot {
+ eNonDOMObject = -1,
+ eRegularDOMObject = DOM_OBJECT_SLOT,
+ eProxyDOMObject = DOM_PROXY_OBJECT_SLOT
+};
+
+template <class T>
+inline T*
+UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot)
+{
+ MOZ_ASSERT(slot != eNonDOMObject,
+ "Don't pass non-DOM objects to this function");
+
+#ifdef DEBUG
+ if (IsDOMClass(js::GetObjectClass(obj))) {
+ MOZ_ASSERT(slot == eRegularDOMObject);
+ } else {
+ MOZ_ASSERT(js::IsObjectProxyClass(js::GetObjectClass(obj)) ||
+ js::IsFunctionProxyClass(js::GetObjectClass(obj)));
+ MOZ_ASSERT(js::GetProxyHandler(obj)->family() == ProxyFamily());
+ MOZ_ASSERT(IsNewProxyBinding(js::GetProxyHandler(obj)));
+ MOZ_ASSERT(slot == eProxyDOMObject);
+ }
+#endif
+
+ JS::Value val = js::GetReservedSlot(obj, slot);
+ // XXXbz/khuey worker code tries to unwrap interface objects (which have
+ // nothing here). That needs to stop.
+ // XXX We don't null-check UnwrapObject's result; aren't we going to crash
+ // anyway?
+ if (val.isUndefined()) {
+ return NULL;
+ }
+
+ return static_cast<T*>(val.toPrivate());
+}
+
+// Only use this with a new DOM binding object (either proxy or regular).
+inline const DOMClass*
+GetDOMClass(JSObject* obj)
+{
+ js::Class* clasp = js::GetObjectClass(obj);
+ if (IsDOMClass(clasp)) {
+ return &DOMJSClass::FromJSClass(clasp)->mClass;
+ }
+
+ js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
+ MOZ_ASSERT(handler->family() == ProxyFamily());
+ MOZ_ASSERT(IsNewProxyBinding(handler));
+ return &static_cast<DOMProxyHandler*>(handler)->mClass;
+}
+
+inline DOMObjectSlot
+GetDOMClass(JSObject* obj, const DOMClass*& result)
+{
+ js::Class* clasp = js::GetObjectClass(obj);
+ if (IsDOMClass(clasp)) {
+ result = &DOMJSClass::FromJSClass(clasp)->mClass;
+ return eRegularDOMObject;
+ }
+
+ if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
+ js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
+ if (handler->family() == ProxyFamily() && IsNewProxyBinding(handler)) {
+ result = &static_cast<DOMProxyHandler*>(handler)->mClass;
+ return eProxyDOMObject;
+ }
+ }
+
+ return eNonDOMObject;
+}
+
+inline bool
+UnwrapDOMObjectToISupports(JSObject* obj, nsISupports*& result)
+{
+ const DOMClass* clasp;
+ DOMObjectSlot slot = GetDOMClass(obj, clasp);
+ if (slot == eNonDOMObject || !clasp->mDOMObjectIsISupports) {
+ return false;
+ }
+
+ result = UnwrapDOMObject<nsISupports>(obj, slot);
+ return true;
+}
+
+inline bool
+IsDOMObject(JSObject* obj)
+{
+ js::Class* clasp = js::GetObjectClass(obj);
+ return IsDOMClass(clasp) ||
+ ((js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) &&
+ (js::GetProxyHandler(obj)->family() == ProxyFamily() &&
+ IsNewProxyBinding(js::GetProxyHandler(obj))));
+}
+
+// Some callers don't want to set an exception when unwrapping fails
+// (for example, overload resolution uses unwrapping to tell what sort
+// of thing it's looking at).
+// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>).
+template <prototypes::ID PrototypeID, class T, typename U>
+inline nsresult
+UnwrapObject(JSContext* cx, JSObject* obj, U& value)
+{
+ /* First check to see whether we have a DOM object */
+ const DOMClass* domClass;
+ DOMObjectSlot slot = GetDOMClass(obj, domClass);
+ if (slot == eNonDOMObject) {
+ /* Maybe we have a security wrapper or outer window? */
+ if (!js::IsWrapper(obj)) {
+ /* Not a DOM object, not a wrapper, just bail */
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+
+ obj = xpc::Unwrap(cx, obj, false);
+ if (!obj) {
+ return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
+ }
+ MOZ_ASSERT(!js::IsWrapper(obj));
+ slot = GetDOMClass(obj, domClass);
+ if (slot == eNonDOMObject) {
+ /* We don't have a DOM object */
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+ }
+ }
+
+ /* This object is a DOM object. Double-check that it is safely
+ castable to T by checking whether it claims to inherit from the
+ class identified by protoID. */
+ if (domClass->mInterfaceChain[PrototypeTraits<PrototypeID>::Depth] ==
+ PrototypeID) {
+ value = UnwrapDOMObject<T>(obj, slot);
+ return NS_OK;
+ }
+
+ /* It's the wrong sort of DOM object */
+ return NS_ERROR_XPC_BAD_CONVERT_JS;
+}
+
+inline bool
+IsArrayLike(JSContext* cx, JSObject* obj)
+{
+ MOZ_ASSERT(obj);
+ // For simplicity, check for security wrappers up front. In case we
+ // have a security wrapper, don't forget to enter the compartment of
+ // the underlying object after unwrapping.
+ Maybe<JSAutoCompartment> ac;
+ if (js::IsWrapper(obj)) {
+ obj = xpc::Unwrap(cx, obj, false);
+ if (!obj) {
+ // Let's say it's not
+ return false;
+ }
+
+ ac.construct(cx, obj);
+ }
+
+ // XXXbz need to detect platform objects (including listbinding
+ // ones) with indexGetters here!
+ return JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj, cx);
+}
+
+inline bool
+IsPlatformObject(JSContext* cx, JSObject* obj)
+{
+ // XXXbz Should be treating list-binding objects as platform objects
+ // too? The one consumer so far wants non-array-like platform
+ // objects, so listbindings that have an indexGetter should test
+ // false from here. Maybe this function should have a different
+ // name?
+ MOZ_ASSERT(obj);
+ // Fast-path the common case
+ JSClass* clasp = js::GetObjectJSClass(obj);
+ if (IsDOMClass(clasp)) {
+ return true;
+ }
+ // Now for simplicity check for security wrappers before anything else
+ if (js::IsWrapper(obj)) {
+ obj = xpc::Unwrap(cx, obj, false);
+ if (!obj) {
+ // Let's say it's not
+ return false;
+ }
+ clasp = js::GetObjectJSClass(obj);
+ }
+ return IS_WRAPPER_CLASS(js::Valueify(clasp)) || IsDOMClass(clasp) ||
+ JS_IsArrayBufferObject(obj, cx);
+}
+
+// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>).
+template <class T, typename U>
+inline nsresult
+UnwrapObject(JSContext* cx, JSObject* obj, U& value)
+{
+ return UnwrapObject<static_cast<prototypes::ID>(
+ PrototypeIDMap<T>::PrototypeID), T>(cx, obj, value);
+}
+
+const size_t kProtoOrIfaceCacheCount =
+ prototypes::id::_ID_Count + constructors::id::_ID_Count;
+
+inline void
+AllocateProtoOrIfaceCache(JSObject* obj)
+{
+ MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+ MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined());
+
+ // Important: The () at the end ensure zero-initialization
+ JSObject** protoOrIfaceArray = new JSObject*[kProtoOrIfaceCacheCount]();
+
+ js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT,
+ JS::PrivateValue(protoOrIfaceArray));
+}
+
+inline void
+TraceProtoOrIfaceCache(JSTracer* trc, JSObject* obj)
+{
+ MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+
+ if (!HasProtoOrIfaceArray(obj))
+ return;
+ JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj);
+ for (size_t i = 0; i < kProtoOrIfaceCacheCount; ++i) {
+ JSObject* proto = protoOrIfaceArray[i];
+ if (proto) {
+ JS_CALL_OBJECT_TRACER(trc, proto, "protoOrIfaceArray[i]");
+ }
+ }
+}
+
+inline void
+DestroyProtoOrIfaceCache(JSObject* obj)
+{
+ MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
+
+ JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj);
+
+ delete [] protoOrIfaceArray;
+}
+
+struct ConstantSpec
+{
+ const char* name;
+ JS::Value value;
+};
+
+/**
+ * Add constants to an object.
+ */
+bool
+DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs);
+
+template<typename T>
+struct Prefable {
+ // A boolean indicating whether this set of specs is enabled
+ bool enabled;
+ // Array of specs, terminated in whatever way is customary for T.
+ // Null to indicate a end-of-array for Prefable, when such an
+ // indicator is needed.
+ T* specs;
+};
+
+/*
+ * Create a DOM interface object (if constructorClass is non-null) and/or a
+ * DOM interface prototype object (if protoClass is non-null).
+ *
+ * global is used as the parent of the interface object and the interface
+ * prototype object
+ * receiver is the object on which we need to define the interface object as a
+ * property
+ * protoProto is the prototype to use for the interface prototype object.
+ * protoClass is the JSClass to use for the interface prototype object.
+ * This is null if we should not create an interface prototype
+ * object.
+ * constructorClass is the JSClass to use for the interface object.
+ * This is null if we should not create an interface object or
+ * if it should be a function object.
+ * constructor is the JSNative to use as a constructor. If this is non-null, it
+ * should be used as a JSNative to back the interface object, which
+ * should be a Function. If this is null, then we should create an
+ * object of constructorClass, unless that's also null, in which
+ * case we should not create an interface object at all.
+ * ctorNargs is the length of the constructor function; 0 if no constructor
+ * instanceClass is the JSClass of instance objects for this class. This can
+ * be null if this is not a concrete proto.
+ * methods and properties are to be defined on the interface prototype object;
+ * these arguments are allowed to be null if there are no
+ * methods or properties respectively.
+ * constants are to be defined on the interface object and on the interface
+ * prototype object; allowed to be null if there are no constants.
+ * staticMethods are to be defined on the interface object; allowed to be null
+ * if there are no static methods.
+ *
+ * At least one of protoClass and constructorClass should be non-null.
+ * If constructorClass is non-null, the resulting interface object will be
+ * defined on the given global with property name |name|, which must also be
+ * non-null.
+ *
+ * returns the interface prototype object if protoClass is non-null, else it
+ * returns the interface object.
+ */
+JSObject*
+CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject* receiver,
+ JSObject* protoProto, JSClass* protoClass,
+ JSClass* constructorClass, JSNative constructor,
+ unsigned ctorNargs, const DOMClass* domClass,
+ Prefable<JSFunctionSpec>* methods,
+ Prefable<JSPropertySpec>* properties,
+ Prefable<ConstantSpec>* constants,
+ Prefable<JSFunctionSpec>* staticMethods, const char* name);
+
+template <class T>
+inline bool
+WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp)
+{
+ JSObject* obj = value->GetWrapper();
+ if (obj && js::GetObjectCompartment(obj) == js::GetObjectCompartment(scope)) {
+ *vp = JS::ObjectValue(*obj);
+ return true;
+ }
+
+ if (!obj) {
+ bool triedToWrap;
+ obj = value->WrapObject(cx, scope, &triedToWrap);
+ if (!obj) {
+ // At this point, obj is null, so just return false. We could
+ // try to communicate triedToWrap to the caller, but in practice
+ // callers seem to be testing JS_IsExceptionPending(cx) to
+ // figure out whether WrapObject() threw instead.
+ return false;
+ }
+ }
+
+ // When called via XrayWrapper, we end up here while running in the
+ // chrome compartment. But the obj we have would be created in
+ // whatever the content compartment is. So at this point we need to
+ // make sure it's correctly wrapped for the compartment of |scope|.
+ // cx should already be in the compartment of |scope| here.
+ MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
+ *vp = JS::ObjectValue(*obj);
+ return JS_WrapValue(cx, vp);
+}
+
+// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr).
+template <template <typename> class SmartPtr, class T>
+inline bool
+WrapNewBindingObject(JSContext* cx, JSObject* scope, const SmartPtr<T>& value,
+ JS::Value* vp)
+{
+ return WrapNewBindingObject(cx, scope, value.get(), vp);
+}
+
+template <class T>
+inline bool
+WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, T* value,
+ JS::Value* vp)
+{
+ // We try to wrap in the compartment of the underlying object of "scope"
+ JSObject* obj;
+ {
+ // scope for the JSAutoCompartment so that we restore the compartment
+ // before we call JS_WrapValue.
+ Maybe<JSAutoCompartment> ac;
+ if (js::IsWrapper(scope)) {
+ scope = xpc::Unwrap(cx, scope, false);
+ if (!scope)
+ return false;
+ ac.construct(cx, scope);
+ }
+
+ obj = value->WrapObject(cx, scope);
+ }
+
+ // We can end up here in all sorts of compartments, per above. Make
+ // sure to JS_WrapValue!
+ *vp = JS::ObjectValue(*obj);
+ return JS_WrapValue(cx, vp);
+}
+
+// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr).
+template <template <typename> class SmartPtr, typename T>
+inline bool
+WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope,
+ const SmartPtr<T>& value, JS::Value* vp)
+{
+ return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), vp);
+}
+
+/**
+ * A method to handle new-binding wrap failure, by possibly falling back to
+ * wrapping as a non-new-binding object.
+ */
+bool
+DoHandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope,
+ nsISupports* value, JS::Value* vp);
+
+/**
+ * An easy way to call the above when you have a value which
+ * multiply-inherits from nsISupports.
+ */
+template <class T>
+bool
+HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, T* value,
+ JS::Value* vp)
+{
+ nsCOMPtr<nsISupports> val;
+ CallQueryInterface(value, getter_AddRefs(val));
+ return DoHandleNewBindingWrappingFailure(cx, scope, val, vp);
+}
+
+// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr).
+template <template <typename> class SmartPtr, class T>
+MOZ_ALWAYS_INLINE bool
+HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope,
+ const SmartPtr<T>& value, JS::Value* vp)
+{
+ return HandleNewBindingWrappingFailure(cx, scope, value.get(), vp);
+}
+
+struct EnumEntry {
+ const char* value;
+ size_t length;
+};
+
+template<bool Fatal>
+inline bool
+EnumValueNotFound(JSContext* cx, const jschar* chars, size_t length,
+ const char* type)
+{
+ return false;
+}
+
+template<>
+inline bool
+EnumValueNotFound<false>(JSContext* cx, const jschar* chars, size_t length,
+ const char* type)
+{
+ // TODO: Log a warning to the console.
+ return true;
+}
+
+template<>
+inline bool
+EnumValueNotFound<true>(JSContext* cx, const jschar* chars, size_t length,
+ const char* type)
+{
+ NS_LossyConvertUTF16toASCII deflated(static_cast<const PRUnichar*>(chars),
+ length);
+ return ThrowErrorMessage(cx, MSG_INVALID_ENUM_VALUE, deflated.get(), type);
+}
+
+
+template<bool InvalidValueFatal>
+inline int
+FindEnumStringIndex(JSContext* cx, JS::Value v, const EnumEntry* values,
+ const char* type, bool* ok)
+{
+ // JS_StringEqualsAscii is slow as molasses, so don't use it here.
+ JSString* str = JS_ValueToString(cx, v);
+ if (!str) {
+ *ok = false;
+ return 0;
+ }
+ JS::Anchor<JSString*> anchor(str);
+ size_t length;
+ const jschar* chars = JS_GetStringCharsAndLength(cx, str, &length);
+ if (!chars) {
+ *ok = false;
+ return 0;
+ }
+ int i = 0;
+ for (const EnumEntry* value = values; value->value; ++value, ++i) {
+ if (length != value->length) {
+ continue;
+ }
+
+ bool equal = true;
+ const char* val = value->value;
+ for (size_t j = 0; j != length; ++j) {
+ if (unsigned(val[j]) != unsigned(chars[j])) {
+ equal = false;
+ break;
+ }
+ }
+
+ if (equal) {
+ *ok = true;
+ return i;
+ }
+ }
+
+ *ok = EnumValueNotFound<InvalidValueFatal>(cx, chars, length, type);
+ return -1;
+}
+
+inline nsWrapperCache*
+GetWrapperCache(nsWrapperCache* cache)
+{
+ return cache;
+}
+
+inline nsWrapperCache*
+GetWrapperCache(nsGlobalWindow* not_allowed);
+
+inline nsWrapperCache*
+GetWrapperCache(void* p)
+{
+ return NULL;
+}
+
+struct ParentObject {
+ template<class T>
+ ParentObject(T* aObject) :
+ mObject(aObject),
+ mWrapperCache(GetWrapperCache(aObject))
+ {}
+
+ template<class T, template<typename> class SmartPtr>
+ ParentObject(const SmartPtr<T>& aObject) :
+ mObject(aObject.get()),
+ mWrapperCache(GetWrapperCache(aObject.get()))
+ {}
+
+ ParentObject(nsISupports* aObject, nsWrapperCache* aCache) :
+ mObject(aObject),
+ mWrapperCache(aCache)
+ {}
+
+ nsISupports* const mObject;
+ nsWrapperCache* const mWrapperCache;
+};
+
+inline nsWrapperCache*
+GetWrapperCache(const ParentObject& aParentObject)
+{
+ return aParentObject.mWrapperCache;
+}
+
+template<class T>
+inline nsISupports*
+GetParentPointer(T* aObject)
+{
+ return ToSupports(aObject);
+}
+
+inline nsISupports*
+GetParentPointer(const ParentObject& aObject)
+{
+ return ToSupports(aObject.mObject);
+}
+
+template<class T>
+inline void
+ClearWrapper(T* p, nsWrapperCache* cache)
+{
+ cache->ClearWrapper();
+}
+
+template<class T>
+inline void
+ClearWrapper(T* p, void*)
+{
+ nsWrapperCache* cache;
+ CallQueryInterface(p, &cache);
+ ClearWrapper(p, cache);
+}
+
+// Can only be called with the immediate prototype of the instance object. Can
+// only be called on the prototype of an object known to be a DOM instance.
+JSBool
+InstanceClassHasProtoAtDepth(JSHandleObject protoObject, uint32_t protoID,
+ uint32_t depth);
+
+// Only set allowNativeWrapper to false if you really know you need it, if in
+// doubt use true. Setting it to false disables security wrappers.
+bool
+XPCOMObjectToJsval(JSContext* cx, JSObject* scope, xpcObjectHelper &helper,
+ const nsIID* iid, bool allowNativeWrapper, JS::Value* rval);
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, T* p, nsWrapperCache* cache,
+ const nsIID* iid, JS::Value* vp)
+{
+ if (xpc_FastGetCachedWrapper(cache, scope, vp))
+ return true;
+ qsObjectHelper helper(p, cache);
+ return XPCOMObjectToJsval(cx, scope, helper, iid, true, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, T* p, const nsIID* iid,
+ JS::Value* vp)
+{
+ return WrapObject(cx, scope, p, GetWrapperCache(p), iid, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, T* p, JS::Value* vp)
+{
+ return WrapObject(cx, scope, p, NULL, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, const nsIID* iid,
+ JS::Value* vp)
+{
+ return WrapObject(cx, scope, p.get(), iid, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, JS::Value* vp)
+{
+ return WrapObject(cx, scope, p, NULL, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, const nsIID* iid,
+ JS::Value* vp)
+{
+ return WrapObject(cx, scope, p.get(), iid, vp);
+}
+
+template<class T>
+inline bool
+WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, JS::Value* vp)
+{
+ return WrapObject(cx, scope, p, NULL, vp);
+}
+
+template<>
+inline bool
+WrapObject<JSObject>(JSContext* cx, JSObject* scope, JSObject* p, JS::Value* vp)
+{
+ vp->setObjectOrNull(p);
+ return true;
+}
+
+template<typename T>
+static inline JSObject*
+WrapNativeParent(JSContext* cx, JSObject* scope, const T& p)
+{
+ if (!GetParentPointer(p))
+ return scope;
+
+ nsWrapperCache* cache = GetWrapperCache(p);
+ JSObject* obj;
+ if (cache && (obj = cache->GetWrapper())) {
+#ifdef DEBUG
+ qsObjectHelper helper(GetParentPointer(p), cache);
+ JS::Value debugVal;
+
+ bool ok = XPCOMObjectToJsval(cx, scope, helper, NULL, false, &debugVal);
+ NS_ASSERTION(ok && JSVAL_TO_OBJECT(debugVal) == obj,
+ "Unexpected object in nsWrapperCache");
+#endif
+ return obj;
+ }
+
+ qsObjectHelper helper(GetParentPointer(p), cache);
+ JS::Value v;
+ return XPCOMObjectToJsval(cx, scope, helper, NULL, false, &v) ?
+ JSVAL_TO_OBJECT(v) :
+ NULL;
+}
+
+static inline bool
+InternJSString(JSContext* cx, jsid& id, const char* chars)
+{
+ if (JSString *str = ::JS_InternString(cx, chars)) {
+ id = INTERNED_STRING_TO_JSID(cx, str);
+ return true;
+ }
+ return false;
+}
+
+// Spec needs a name property
+template <typename Spec>
+static bool
+InitIds(JSContext* cx, Prefable<Spec>* prefableSpecs, jsid* ids)
+{
+ MOZ_ASSERT(prefableSpecs);
+ MOZ_ASSERT(prefableSpecs->specs);
+ do {
+ // We ignore whether the set of ids is enabled and just intern all the IDs,
+ // because this is only done once per application runtime.
+ Spec* spec = prefableSpecs->specs;
+ do {
+ if (!InternJSString(cx, *ids, spec->name)) {
+ return false;
+ }
+ } while (++ids, (++spec)->name);
+
+ // We ran out of ids for that pref. Put a JSID_VOID in on the id
+ // corresponding to the list terminator for the pref.
+ *ids = JSID_VOID;
+ ++ids;
+ } while ((++prefableSpecs)->specs);
+
+ return true;
+}
+
+JSBool
+QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp);
+JSBool
+ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool
+GetPropertyOnPrototype(JSContext* cx, JSObject* proxy, jsid id, bool* found,
+ JS::Value* vp);
+
+bool
+HasPropertyOnPrototype(JSContext* cx, JSObject* proxy, DOMProxyHandler* handler,
+ jsid id);
+
+template<class T>
+class NonNull
+{
+public:
+ NonNull()
+#ifdef DEBUG
+ : inited(false)
+#endif
+ {}
+
+ operator T&() {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+ return *ptr;
+ }
+
+ operator const T&() const {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "NonNull<T> was set to null");
+ return *ptr;
+ }
+
+ void operator=(T* t) {
+ ptr = t;
+ MOZ_ASSERT(ptr);
+#ifdef DEBUG
+ inited = true;
+#endif
+ }
+
+ template<typename U>
+ void operator=(U* t) {
+ ptr = t->ToAStringPtr();
+ MOZ_ASSERT(ptr);
+#ifdef DEBUG
+ inited = true;
+#endif
+ }
+
+ T** Slot() {
+#ifdef DEBUG
+ inited = true;
+#endif
+ return &ptr;
+ }
+
+protected:
+ T* ptr;
+#ifdef DEBUG
+ bool inited;
+#endif
+};
+
+template<class T>
+class OwningNonNull
+{
+public:
+ OwningNonNull()
+#ifdef DEBUG
+ : inited(false)
+#endif
+ {}
+
+ operator T&() {
+ MOZ_ASSERT(inited);
+ MOZ_ASSERT(ptr, "OwningNonNull<T> was set to null");
+ return *ptr;
+ }
+
+ void operator=(T* t) {
+ init(t);
+ }
+
+ void operator=(const already_AddRefed<T>& t) {
+ init(t);
+ }
+
+protected:
+ template<typename U>
+ void init(U t) {
+ ptr = t;
+ MOZ_ASSERT(ptr);
+#ifdef DEBUG
+ inited = true;
+#endif
+ }
+
+ nsRefPtr<T> ptr;
+#ifdef DEBUG
+ bool inited;
+#endif
+};
+
+// A struct that has the same layout as an nsDependentString but much
+// faster constructor and destructor behavior
+struct FakeDependentString {
+ FakeDependentString() :
+ mFlags(nsDependentString::F_TERMINATED)
+ {
+ }
+
+ void SetData(const nsDependentString::char_type* aData,
+ nsDependentString::size_type aLength) {
+ MOZ_ASSERT(mFlags == nsDependentString::F_TERMINATED);
+ mData = aData;
+ mLength = aLength;
+ }
+
+ void Truncate() {
+ mData = nsDependentString::char_traits::sEmptyBuffer;
+ mLength = 0;
+ }
+
+ void SetNull() {
+ Truncate();
+ mFlags |= nsDependentString::F_VOIDED;
+ }
+
+ const nsAString* ToAStringPtr() const {
+ return reinterpret_cast<const nsDependentString*>(this);
+ }
+
+ nsAString* ToAStringPtr() {
+ return reinterpret_cast<nsDependentString*>(this);
+ }
+
+ operator const nsAString& () const {
+ return *reinterpret_cast<const nsDependentString*>(this);
+ }
+
+private:
+ const nsDependentString::char_type* mData;
+ nsDependentString::size_type mLength;
+ uint32_t mFlags;
+
+ // A class to use for our static asserts to ensure our object layout
+ // matches that of nsDependentString.
+ class DependentStringAsserter;
+ friend class DependentStringAsserter;
+
+ class DepedentStringAsserter : public nsDependentString {
+ public:
+ static void StaticAsserts() {
+ MOZ_STATIC_ASSERT(sizeof(FakeDependentString) == sizeof(nsDependentString),
+ "Must have right object size");
+ MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mData) ==
+ offsetof(DepedentStringAsserter, mData),
+ "Offset of mData should match");
+ MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mLength) ==
+ offsetof(DepedentStringAsserter, mLength),
+ "Offset of mLength should match");
+ MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mFlags) ==
+ offsetof(DepedentStringAsserter, mFlags),
+ "Offset of mFlags should match");
+ }
+ };
+};
+
+enum StringificationBehavior {
+ eStringify,
+ eEmpty,
+ eNull
+};
+
+// pval must not be null and must point to a rooted JS::Value
+static inline bool
+ConvertJSValueToString(JSContext* cx, const JS::Value& v, JS::Value* pval,
+ StringificationBehavior nullBehavior,
+ StringificationBehavior undefinedBehavior,
+ FakeDependentString& result)
+{
+ JSString *s;
+ if (v.isString()) {
+ s = v.toString();
+ } else {
+ StringificationBehavior behavior;
+ if (v.isNull()) {
+ behavior = nullBehavior;
+ } else if (v.isUndefined()) {
+ behavior = undefinedBehavior;
+ } else {
+ behavior = eStringify;
+ }
+
+ if (behavior != eStringify) {
+ if (behavior == eEmpty) {
+ result.Truncate();
+ } else {
+ result.SetNull();
+ }
+ return true;
+ }
+
+ s = JS_ValueToString(cx, v);
+ if (!s) {
+ return false;
+ }
+ pval->setString(s); // Root the new string.
+ }
+
+ size_t len;
+ const jschar *chars = JS_GetStringCharsZAndLength(cx, s, &len);
+ if (!chars) {
+ return false;
+ }
+
+ result.SetData(chars, len);
+ return true;
+}
+
+// Class for representing optional arguments.
+template<typename T>
+class Optional {
+public:
+ Optional() {}
+
+ bool WasPassed() const {
+ return !mImpl.empty();
+ }
+
+ void Construct() {
+ mImpl.construct();
+ }
+
+ template <class T1, class T2>
+ void Construct(const T1 &t1, const T2 &t2) {
+ mImpl.construct(t1, t2);
+ }
+
+ const T& Value() const {
+ return mImpl.ref();
+ }
+
+ T& Value() {
+ return mImpl.ref();
+ }
+
+private:
+ // Forbid copy-construction and assignment
+ Optional(const Optional& other) MOZ_DELETE;
+ const Optional &operator=(const Optional &other) MOZ_DELETE;
+
+ Maybe<T> mImpl;
+};
+
+// Specialization for strings.
+template<>
+class Optional<nsAString> {
+public:
+ Optional() : mPassed(false) {}
+
+ bool WasPassed() const {
+ return mPassed;
+ }
+
+ void operator=(const nsAString* str) {
+ MOZ_ASSERT(str);
+ mStr = str;
+ mPassed = true;
+ }
+
+ void operator=(const FakeDependentString* str) {
+ MOZ_ASSERT(str);
+ mStr = str->ToAStringPtr();
+ mPassed = true;
+ }
+
+ const nsAString& Value() const {
+ MOZ_ASSERT(WasPassed());
+ return *mStr;
+ }
+
+private:
+ // Forbid copy-construction and assignment
+ Optional(const Optional& other) MOZ_DELETE;
+ const Optional &operator=(const Optional &other) MOZ_DELETE;
+
+ bool mPassed;
+ const nsAString* mStr;
+};
+
+// Class for representing sequences in arguments. We use an auto array that can
+// hold 16 elements, to avoid having to allocate in common cases. This needs to
+// be fallible because web content controls the length of the array, and can
+// easily try to create very large lengths.
+template<typename T>
+class Sequence : public AutoFallibleTArray<T, 16>
+{
+public:
+ Sequence() : AutoFallibleTArray<T, 16>() {}
+};
+
+// Class for holding the type of members of a union. The union type has an enum
+// to keep track of which of its UnionMembers has been constructed.
+template<class T>
+class UnionMember {
+ AlignedStorage2<T> storage;
+
+public:
+ T& SetValue() {
+ new (storage.addr()) T();
+ return *storage.addr();
+ }
+ const T& Value() const {
+ return *storage.addr();
+ }
+ void Destroy() {
+ storage.addr()->~T();
+ }
+};
+
+// Implementation of the bits that XrayWrapper needs
+bool
+XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id,
+ JSPropertyDescriptor* desc,
+ // And the things we need to determine the descriptor
+ Prefable<JSFunctionSpec>* methods,
+ jsid* methodIds,
+ JSFunctionSpec* methodSpecs,
+ size_t methodCount,
+ Prefable<JSPropertySpec>* attributes,
+ jsid* attributeIds,
+ JSPropertySpec* attributeSpecs,
+ size_t attributeCount,
+ Prefable<ConstantSpec>* constants,
+ jsid* constantIds,
+ ConstantSpec* constantSpecs,
+ size_t constantCount);
+
+bool
+XrayEnumerateProperties(JS::AutoIdVector& props,
+ Prefable<JSFunctionSpec>* methods,
+ jsid* methodIds,
+ JSFunctionSpec* methodSpecs,
+ size_t methodCount,
+ Prefable<JSPropertySpec>* attributes,
+ jsid* attributeIds,
+ JSPropertySpec* attributeSpecs,
+ size_t attributeCount,
+ Prefable<ConstantSpec>* constants,
+ jsid* constantIds,
+ ConstantSpec* constantSpecs,
+ size_t constantCount);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_BindingUtils_h__ */
diff --git a/components/script/dom/bindings/codegen/Bindings.conf b/components/script/dom/bindings/codegen/Bindings.conf
new file mode 100644
index 00000000000..f8119bc71f5
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Bindings.conf
@@ -0,0 +1,28 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# DOM Bindings Configuration.
+#
+# The WebIDL interfaces are defined in dom/webidls. For each such interface,
+# there is a corresponding entry in the configuration table below.
+# The configuration table maps each interface name to a |descriptor|.
+#
+# Valid fields for all descriptors:
+# * createGlobal: True for global objects.
+# * outerObjectHook: string to use in place of default value for outerObject and thisObject
+# JS class hooks
+
+DOMInterfaces = {
+
+'EventListener': {
+ 'nativeType': 'EventListenerBinding::EventListener',
+},
+'Window': {
+ 'outerObjectHook': 'Some(bindings::utils::outerize_global)',
+},
+
+#FIXME(jdm): This should be 'register': False, but then we don't generate enum types
+'TestBinding': {},
+
+}
diff --git a/components/script/dom/bindings/codegen/Codegen.py b/components/script/dom/bindings/codegen/Codegen.py
new file mode 100644
index 00000000000..6d2cc0bde36
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Codegen.py
@@ -0,0 +1,5788 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Common codegen classes.
+
+import os
+import string
+import operator
+
+from WebIDL import *
+from Configuration import NoSuchDescriptorError
+
+AUTOGENERATED_WARNING_COMMENT = \
+ "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
+ADDPROPERTY_HOOK_NAME = '_addProperty'
+FINALIZE_HOOK_NAME = '_finalize'
+TRACE_HOOK_NAME = '_trace'
+CONSTRUCT_HOOK_NAME = '_constructor'
+HASINSTANCE_HOOK_NAME = '_hasInstance'
+
+def replaceFileIfChanged(filename, newContents):
+ """
+ Read a copy of the old file, so that we don't touch it if it hasn't changed.
+ Returns True if the file was updated, false otherwise.
+ """
+ oldFileContents = ""
+ try:
+ oldFile = open(filename, 'rb')
+ oldFileContents = ''.join(oldFile.readlines())
+ oldFile.close()
+ except:
+ pass
+
+ if newContents == oldFileContents:
+ return False
+
+ f = open(filename, 'wb')
+ f.write(newContents)
+ f.close()
+
+def toStringBool(arg):
+ return str(not not arg).lower()
+
+def toBindingNamespace(arg):
+ return re.sub("((_workers)?$)", "Binding\\1", arg);
+
+class CGThing():
+ """
+ Abstract base class for things that spit out code.
+ """
+ def __init__(self):
+ pass # Nothing for now
+ def declare(self):
+ """Produce code for a header file."""
+ assert(False) # Override me!
+ def define(self):
+ """Produce code for a cpp file."""
+ assert(False) # Override me!
+
+class CGNativePropertyHooks(CGThing):
+ """
+ Generate a NativePropertyHooks for a given descriptor
+ """
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ def declare(self):
+ if self.descriptor.workers:
+ return ""
+ return "extern const NativePropertyHooks NativeHooks;\n"
+ def define(self):
+ if self.descriptor.workers:
+ return ""
+ if self.descriptor.concrete and self.descriptor.proxy:
+ resolveOwnProperty = "ResolveOwnProperty"
+ enumerateOwnProperties = "EnumerateOwnProperties"
+ else:
+ enumerateOwnProperties = resolveOwnProperty = "NULL"
+ parent = self.descriptor.interface.parent
+ parentHooks = ("&" + toBindingNamespace(parent.identifier.name) + "::NativeHooks"
+ if parent else 'NULL')
+ return """
+const NativePropertyHooks NativeHooks = { %s, ResolveProperty, %s, EnumerateProperties, %s };
+""" % (resolveOwnProperty, enumerateOwnProperties, parentHooks)
+
+def DOMClass(descriptor):
+ protoList = ['prototypes::id::' + proto for proto in descriptor.prototypeChain]
+ # Pad out the list to the right length with _ID_Count so we
+ # guarantee that all the lists are the same length. _ID_Count
+ # is never the ID of any prototype, so it's safe to use as
+ # padding.
+ protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList)))
+ prototypeChainString = ', '.join(protoList)
+ nativeHooks = "NULL" if descriptor.workers else "&NativeHooks"
+ return """{
+ { %s },
+ %s, %s
+}""" % (prototypeChainString, toStringBool(descriptor.nativeIsISupports),
+ nativeHooks)
+
+class CGDOMJSClass(CGThing):
+ """
+ Generate a DOMJSClass for a given descriptor
+ """
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ def declare(self):
+ return "extern DOMJSClass Class;\n"
+ def define(self):
+ traceHook = TRACE_HOOK_NAME if self.descriptor.customTrace else 'NULL'
+ return """
+DOMJSClass Class = {
+ { "%s",
+ JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1),
+ %s, /* addProperty */
+ JS_PropertyStub, /* delProperty */
+ JS_PropertyStub, /* getProperty */
+ JS_StrictPropertyStub, /* setProperty */
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ %s, /* finalize */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* hasInstance */
+ NULL, /* construct */
+ %s, /* trace */
+ JSCLASS_NO_INTERNAL_MEMBERS
+ },
+ %s
+};
+""" % (self.descriptor.interface.identifier.name,
+ ADDPROPERTY_HOOK_NAME if self.descriptor.concrete and not self.descriptor.workers and self.descriptor.wrapperCache else 'JS_PropertyStub',
+ FINALIZE_HOOK_NAME, traceHook,
+ CGIndenter(CGGeneric(DOMClass(self.descriptor))).define())
+
+class CGPrototypeJSClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ def declare(self):
+ # We're purely for internal consumption
+ return ""
+ def define(self):
+ return """static JSClass PrototypeClass = {
+ "%sPrototype",
+ JSCLASS_HAS_RESERVED_SLOTS(1),
+ JS_PropertyStub, /* addProperty */
+ JS_PropertyStub, /* delProperty */
+ JS_PropertyStub, /* getProperty */
+ JS_StrictPropertyStub, /* setProperty */
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ NULL, /* finalize */
+ NULL, /* checkAccess */
+ NULL, /* call */
+ NULL, /* hasInstance */
+ NULL, /* construct */
+ NULL, /* trace */
+ JSCLASS_NO_INTERNAL_MEMBERS
+};
+""" % (self.descriptor.interface.identifier.name)
+
+class CGInterfaceObjectJSClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ def declare(self):
+ # We're purely for internal consumption
+ return ""
+ def define(self):
+ if not self.descriptor.hasInstanceInterface:
+ return ""
+ ctorname = "NULL" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME
+ hasinstance = HASINSTANCE_HOOK_NAME
+ return """
+static JSClass InterfaceObjectClass = {
+ "Function", 0,
+ JS_PropertyStub, /* addProperty */
+ JS_PropertyStub, /* delProperty */
+ JS_PropertyStub, /* getProperty */
+ JS_StrictPropertyStub, /* setProperty */
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ NULL, /* finalize */
+ NULL, /* checkAccess */
+ %s, /* call */
+ %s, /* hasInstance */
+ %s, /* construct */
+ NULL, /* trace */
+ JSCLASS_NO_INTERNAL_MEMBERS
+};
+""" % (ctorname, hasinstance, ctorname)
+
+class CGList(CGThing):
+ """
+ Generate code for a list of GCThings. Just concatenates them together, with
+ an optional joiner string. "\n" is a common joiner.
+ """
+ def __init__(self, children, joiner=""):
+ CGThing.__init__(self)
+ self.children = children
+ self.joiner = joiner
+ def append(self, child):
+ self.children.append(child)
+ def prepend(self, child):
+ self.children.insert(0, child)
+ def join(self, generator):
+ return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator)))
+ def declare(self):
+ return self.join(child.declare() for child in self.children if child is not None)
+ def define(self):
+ return self.join(child.define() for child in self.children if child is not None)
+
+class CGGeneric(CGThing):
+ """
+ A class that spits out a fixed string into the codegen. Can spit out a
+ separate string for the declaration too.
+ """
+ def __init__(self, define="", declare=""):
+ self.declareText = declare
+ self.defineText = define
+ def declare(self):
+ return self.declareText
+ def define(self):
+ return self.defineText
+
+# We'll want to insert the indent at the beginnings of lines, but we
+# don't want to indent empty lines. So only indent lines that have a
+# non-newline character on them.
+lineStartDetector = re.compile("^(?=[^\n#])", re.MULTILINE)
+class CGIndenter(CGThing):
+ """
+ A class that takes another CGThing and generates code that indents that
+ CGThing by some number of spaces. The default indent is two spaces.
+ """
+ def __init__(self, child, indentLevel=2, declareOnly=False):
+ CGThing.__init__(self)
+ self.child = child
+ self.indent = " " * indentLevel
+ self.declareOnly = declareOnly
+ def declare(self):
+ decl = self.child.declare()
+ if decl is not "":
+ return re.sub(lineStartDetector, self.indent, decl)
+ else:
+ return ""
+ def define(self):
+ defn = self.child.define()
+ if defn is not "" and not self.declareOnly:
+ return re.sub(lineStartDetector, self.indent, defn)
+ else:
+ return defn
+
+class CGWrapper(CGThing):
+ """
+ Generic CGThing that wraps other CGThings with pre and post text.
+ """
+ def __init__(self, child, pre="", post="", declarePre=None,
+ declarePost=None, definePre=None, definePost=None,
+ declareOnly=False, defineOnly=False, reindent=False):
+ CGThing.__init__(self)
+ self.child = child
+ self.declarePre = declarePre or pre
+ self.declarePost = declarePost or post
+ self.definePre = definePre or pre
+ self.definePost = definePost or post
+ self.declareOnly = declareOnly
+ self.defineOnly = defineOnly
+ self.reindent = reindent
+ def declare(self):
+ if self.defineOnly:
+ return ''
+ decl = self.child.declare()
+ if self.reindent:
+ # We don't use lineStartDetector because we don't want to
+ # insert whitespace at the beginning of our _first_ line.
+ decl = stripTrailingWhitespace(
+ decl.replace("\n", "\n" + (" " * len(self.declarePre))))
+ return self.declarePre + decl + self.declarePost
+ def define(self):
+ if self.declareOnly:
+ return ''
+ defn = self.child.define()
+ if self.reindent:
+ # We don't use lineStartDetector because we don't want to
+ # insert whitespace at the beginning of our _first_ line.
+ defn = stripTrailingWhitespace(
+ defn.replace("\n", "\n" + (" " * len(self.definePre))))
+ return self.definePre + defn + self.definePost
+
+class CGIfWrapper(CGWrapper):
+ def __init__(self, child, condition):
+ pre = CGWrapper(CGGeneric(condition), pre="if (", post=") {\n",
+ reindent=True)
+ CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(),
+ post="\n}")
+
+class CGNamespace(CGWrapper):
+ def __init__(self, namespace, child, declareOnly=False):
+ pre = "namespace %s {\n" % namespace
+ post = "} // namespace %s\n" % namespace
+ CGWrapper.__init__(self, child, pre=pre, post=post,
+ declareOnly=declareOnly)
+ @staticmethod
+ def build(namespaces, child, declareOnly=False):
+ """
+ Static helper method to build multiple wrapped namespaces.
+ """
+ if not namespaces:
+ return CGWrapper(child, declareOnly=declareOnly)
+ inner = CGNamespace.build(namespaces[1:], child, declareOnly=declareOnly)
+ return CGNamespace(namespaces[0], inner, declareOnly=declareOnly)
+
+class CGIncludeGuard(CGWrapper):
+ """
+ Generates include guards for a header.
+ """
+ def __init__(self, prefix, child):
+ """|prefix| is the filename without the extension."""
+ define = 'mozilla_dom_%s_h__' % prefix
+ CGWrapper.__init__(self, child,
+ declarePre='#ifndef %s\n#define %s\n\n' % (define, define),
+ declarePost='\n#endif // %s\n' % define)
+
+def getTypes(descriptor):
+ """
+ Get all argument and return types for all members of the descriptor
+ """
+ members = [m for m in descriptor.interface.members]
+ if descriptor.interface.ctor():
+ members.append(descriptor.interface.ctor())
+ signatures = [s for m in members if m.isMethod() for s in m.signatures()]
+ types = []
+ for s in signatures:
+ assert len(s) == 2
+ (returnType, arguments) = s
+ types.append(returnType)
+ types.extend([a.type for a in arguments])
+
+ types.extend(a.type for a in members if a.isAttr())
+ return types
+
+class CGHeaders(CGWrapper):
+ """
+ Generates the appropriate include statements.
+ """
+ def __init__(self, descriptors, dictionaries, declareIncludes,
+ defineIncludes, child):
+ """
+ Builds a set of includes to cover |descriptors|.
+
+ Also includes the files in |declareIncludes| in the header
+ file and the files in |defineIncludes| in the .cpp.
+ """
+
+ # Determine the filenames for which we need headers.
+ interfaceDeps = [d.interface for d in descriptors]
+ ancestors = []
+ for iface in interfaceDeps:
+ while iface.parent:
+ ancestors.append(iface.parent)
+ iface = iface.parent
+ interfaceDeps.extend(ancestors)
+ bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
+
+ # Grab all the implementation declaration files we need.
+ implementationIncludes = set(d.headerFile for d in descriptors)
+
+ # Now find all the things we'll need as arguments because we
+ # need to wrap or unwrap them.
+ bindingHeaders = set()
+ for d in descriptors:
+ types = getTypes(d)
+ for dictionary in dictionaries:
+ curDict = dictionary
+ while curDict:
+ types.extend([m.type for m in curDict.members])
+ curDict = curDict.parent
+
+ for t in types:
+ if t.unroll().isUnion():
+ # UnionConversions.h includes UnionTypes.h
+ bindingHeaders.add("mozilla/dom/UnionConversions.h")
+ elif t.unroll().isInterface():
+ if t.unroll().isSpiderMonkeyInterface():
+ bindingHeaders.add("jsfriendapi.h")
+ bindingHeaders.add("mozilla/dom/TypedArray.h")
+ else:
+ typeDesc = d.getDescriptor(t.unroll().inner.identifier.name)
+ if typeDesc is not None:
+ implementationIncludes.add(typeDesc.headerFile)
+ bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface))
+ elif t.unroll().isDictionary():
+ bindingHeaders.add(self.getDeclarationFilename(t.unroll().inner))
+
+ declareIncludes = set(declareIncludes)
+ for d in dictionaries:
+ if d.parent:
+ declareIncludes.add(self.getDeclarationFilename(d.parent))
+ bindingHeaders.add(self.getDeclarationFilename(d))
+
+ # Let the machinery do its thing.
+ def _includeString(includes):
+ return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
+ CGWrapper.__init__(self, child,
+ declarePre=_includeString(sorted(declareIncludes)),
+ definePre=_includeString(sorted(set(defineIncludes) |
+ bindingIncludes |
+ bindingHeaders |
+ implementationIncludes)))
+ @staticmethod
+ def getDeclarationFilename(decl):
+ # Use our local version of the header, not the exported one, so that
+ # test bindings, which don't export, will work correctly.
+ basename = os.path.basename(decl.filename())
+ return basename.replace('.webidl', 'Binding.h')
+
+def SortedTuples(l):
+ """
+ Sort a list of tuples based on the first item in the tuple
+ """
+ return sorted(l, key=operator.itemgetter(0))
+
+def SortedDictValues(d):
+ """
+ Returns a list of values from the dict sorted by key.
+ """
+ # Create a list of tuples containing key and value, sorted on key.
+ d = SortedTuples(d.items())
+ # We're only interested in the values.
+ return (i[1] for i in d)
+
+def UnionTypes(descriptors):
+ """
+ Returns a tuple containing a set of header filenames to include, a set of
+ tuples containing a type declaration and a boolean if the type is a struct
+ for member types of the unions and a CGList containing CGUnionStructs for
+ every union.
+ """
+
+ # Now find all the things we'll need as arguments and return values because
+ # we need to wrap or unwrap them.
+ headers = set()
+ declarations = set()
+ unionStructs = dict()
+ for d in descriptors:
+ if d.interface.isExternal():
+ continue
+
+ for t in getTypes(d):
+ t = t.unroll()
+ if t.isUnion():
+ name = str(t)
+ if not name in unionStructs:
+ unionStructs[name] = CGUnionStruct(t, d)
+ for f in t.flatMemberTypes:
+ f = f.unroll()
+ if f.isInterface():
+ if f.isSpiderMonkeyInterface():
+ headers.add("jsfriendapi.h")
+ headers.add("mozilla/dom/TypedArray.h")
+ else:
+ typeDesc = d.getDescriptor(f.inner.identifier.name)
+ if typeDesc is not None:
+ declarations.add((typeDesc.nativeType, False))
+ elif f.isDictionary():
+ declarations.add((f.inner.identifier.name, True))
+
+ return (headers, declarations, CGList(SortedDictValues(unionStructs), "\n"))
+
+def UnionConversions(descriptors):
+ """
+ Returns a CGThing to declare all union argument conversion helper structs.
+ """
+ # Now find all the things we'll need as arguments because we
+ # need to unwrap them.
+ unionConversions = dict()
+ for d in descriptors:
+ if d.interface.isExternal():
+ continue
+
+ def addUnionTypes(type):
+ if type.isUnion():
+ type = type.unroll()
+ name = str(type)
+ if not name in unionConversions:
+ unionConversions[name] = CGUnionConversionStruct(type, d)
+
+ members = [m for m in d.interface.members]
+ if d.interface.ctor():
+ members.append(d.interface.ctor())
+ signatures = [s for m in members if m.isMethod() for s in m.signatures()]
+ for s in signatures:
+ assert len(s) == 2
+ (_, arguments) = s
+ for a in arguments:
+ addUnionTypes(a.type)
+
+ for m in members:
+ if m.isAttr() and not m.readonly:
+ addUnionTypes(m.type)
+
+ return CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
+ post="\n\n")
+
+class Argument():
+ """
+ A class for outputting the type and name of an argument
+ """
+ def __init__(self, argType, name):
+ self.argType = argType
+ self.name = name
+ def __str__(self):
+ return self.argType + ' ' + self.name
+
+class CGAbstractMethod(CGThing):
+ """
+ An abstract class for generating code for a method. Subclasses
+ should override definition_body to create the actual code.
+
+ descriptor is the descriptor for the interface the method is associated with
+
+ name is the name of the method as a string
+
+ returnType is the IDLType of the return value
+
+ args is a list of Argument objects
+
+ inline should be True to generate an inline method, whose body is
+ part of the declaration.
+
+ alwaysInline should be True to generate an inline method annotated with
+ MOZ_ALWAYS_INLINE.
+
+ static should be True to generate a static method, which only has
+ a definition.
+
+ If templateArgs is not None it should be a list of strings containing
+ template arguments, and the function will be templatized using those
+ arguments.
+ """
+ def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, static=False, templateArgs=None):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.name = name
+ self.returnType = returnType
+ self.args = args
+ self.inline = inline
+ self.alwaysInline = alwaysInline
+ self.static = static
+ self.templateArgs = templateArgs
+ def _argstring(self):
+ return ', '.join([str(a) for a in self.args])
+ def _template(self):
+ if self.templateArgs is None:
+ return ''
+ return 'template <%s>\n' % ', '.join(self.templateArgs)
+ def _decorators(self):
+ decorators = []
+ if self.alwaysInline:
+ decorators.append('MOZ_ALWAYS_INLINE')
+ elif self.inline:
+ decorators.append('inline')
+ if self.static:
+ decorators.append('static')
+ decorators.append(self.returnType)
+ maybeNewline = " " if self.inline else "\n"
+ return ' '.join(decorators) + maybeNewline
+ def declare(self):
+ if self.inline:
+ return self._define()
+ return "%s%s%s(%s);\n" % (self._template(), self._decorators(), self.name, self._argstring())
+ def _define(self):
+ return self.definition_prologue() + "\n" + self.definition_body() + self.definition_epilogue()
+ def define(self):
+ return "" if self.inline else self._define()
+ def definition_prologue(self):
+ return "%s%s%s(%s)\n{" % (self._template(), self._decorators(),
+ self.name, self._argstring())
+ def definition_epilogue(self):
+ return "\n}\n"
+ def definition_body(self):
+ assert(False) # Override me!
+
+class CGAbstractStaticMethod(CGAbstractMethod):
+ """
+ Abstract base class for codegen of implementation-only (no
+ declaration) static methods.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
+ inline=False, static=True)
+ def declare(self):
+ # We only have implementation
+ return ""
+
+class CGAbstractClassHook(CGAbstractStaticMethod):
+ """
+ Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
+ 'this' unwrapping as it assumes that the unwrapped type is always known.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractStaticMethod.__init__(self, descriptor, name, returnType,
+ args)
+
+ def definition_body_prologue(self):
+ return """
+ %s* self = UnwrapDOMObject<%s>(obj, eRegularDOMObject);
+""" % (self.descriptor.nativeType, self.descriptor.nativeType)
+
+ def definition_body(self):
+ return self.definition_body_prologue() + self.generate_code()
+
+ def generate_code(self):
+ # Override me
+ assert(False)
+
+class CGAddPropertyHook(CGAbstractClassHook):
+ """
+ A hook for addProperty, used to preserve our wrapper from GC.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
+ Argument('JSHandleId', 'id'), Argument('JSMutableHandleValue', 'vp')]
+ CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
+ 'JSBool', args)
+
+ def generate_code(self):
+ # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=774279
+ # Using a real trace hook might enable us to deal with non-nsISupports
+ # wrappercached things here.
+ assert self.descriptor.nativeIsISupports
+ return """ nsContentUtils::PreserveWrapper(reinterpret_cast<nsISupports*>(self), self);
+ return true;"""
+
+def finalizeHook(descriptor, hookName, context):
+ if descriptor.customFinalize:
+ return """if (self) {
+ self->%s(%s);
+}""" % (hookName, context)
+ clearWrapper = "ClearWrapper(self, self);\n" if descriptor.wrapperCache else ""
+ if descriptor.workers:
+ release = "self->Release();"
+ else:
+ assert descriptor.nativeIsISupports
+ release = """XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
+if (rt) {
+ rt->DeferredRelease(reinterpret_cast<nsISupports*>(self));
+} else {
+ NS_RELEASE(self);
+}"""
+ return clearWrapper + release
+
+class CGClassFinalizeHook(CGAbstractClassHook):
+ """
+ A hook for finalize, used to release our native object.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'obj')]
+ CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
+ 'void', args)
+
+ def generate_code(self):
+ return CGIndenter(CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))).define()
+
+class CGClassTraceHook(CGAbstractClassHook):
+ """
+ A hook to trace through our native object; used for GC and CC
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSTracer*', 'trc'), Argument('JSObject*', 'obj')]
+ CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void',
+ args)
+
+ def generate_code(self):
+ return """ if (self) {
+ self->%s(%s);
+ }""" % (self.name, self.args[0].name)
+
+class CGClassConstructHook(CGAbstractStaticMethod):
+ """
+ JS-visible constructor for our objects
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')]
+ CGAbstractStaticMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME,
+ 'JSBool', args)
+ self._ctor = self.descriptor.interface.ctor()
+
+ def define(self):
+ if not self._ctor:
+ return ""
+ return CGAbstractStaticMethod.define(self)
+
+ def definition_body(self):
+ return self.generate_code()
+
+ def generate_code(self):
+ preamble = """
+ JSObject* obj = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
+"""
+ if self.descriptor.workers:
+ preArgs = ["cx", "obj"]
+ else:
+ preamble += """
+ nsISupports* global;
+ xpc_qsSelfRef globalRef;
+ {
+ nsresult rv;
+ JS::Value val = OBJECT_TO_JSVAL(obj);
+ rv = xpc_qsUnwrapArg<nsISupports>(cx, val, &global, &globalRef.ptr, &val);
+ if (NS_FAILED(rv)) {
+ return Throw<true>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+ }
+ }
+"""
+ preArgs = ["global"]
+
+ name = self._ctor.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
+ callGenerator = CGMethodCall(preArgs, nativeName, True,
+ self.descriptor, self._ctor)
+ return preamble + callGenerator.define();
+
+class CGClassHasInstanceHook(CGAbstractStaticMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
+ Argument('JSMutableHandleValue', 'vp'), Argument('JSBool*', 'bp')]
+ CGAbstractStaticMethod.__init__(self, descriptor, HASINSTANCE_HOOK_NAME,
+ 'JSBool', args)
+
+ def define(self):
+ if not self.descriptor.hasInstanceInterface:
+ return ""
+ return CGAbstractStaticMethod.define(self)
+
+ def definition_body(self):
+ return self.generate_code()
+
+ def generate_code(self):
+ return """ if (!vp.isObject()) {
+ *bp = false;
+ return true;
+ }
+
+ jsval protov;
+ if (!JS_GetProperty(cx, obj, "prototype", &protov))
+ return false;
+ if (!protov.isObject()) {
+ JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROTOTYPE,
+ "%s");
+ return false;
+ }
+ JSObject *objProto = &protov.toObject();
+
+ JSObject* instance = &vp.toObject();
+ JSObject* proto;
+ if (!JS_GetPrototype(cx, instance, &proto))
+ return false;
+ while (proto) {
+ if (proto == objProto) {
+ *bp = true;
+ return true;
+ }
+ if (!JS_GetPrototype(cx, proto, &proto))
+ return false;
+ }
+
+ nsISupports* native =
+ nsContentUtils::XPConnect()->GetNativeOfWrapper(cx, instance);
+ nsCOMPtr<%s> qiResult = do_QueryInterface(native);
+ *bp = !!qiResult;
+ return true;
+""" % (self.descriptor.name, self.descriptor.hasInstanceInterface)
+
+def isChromeOnly(m):
+ return m.getExtendedAttribute("ChromeOnly")
+
+class PropertyDefiner:
+ """
+ A common superclass for defining things on prototype objects.
+
+ Subclasses should implement generateArray to generate the actual arrays of
+ things we're defining. They should also set self.chrome to the list of
+ things exposed to chrome and self.regular to the list of things exposed to
+ web pages. self.chrome must be a superset of self.regular but also include
+ all the ChromeOnly stuff.
+ """
+ def __init__(self, descriptor, name):
+ self.descriptor = descriptor
+ self.name = name
+ # self.prefCacheData will store an array of (prefname, bool*)
+ # pairs for our bool var caches. generateArray will fill it
+ # in as needed.
+ self.prefCacheData = []
+ def hasChromeOnly(self):
+ return len(self.chrome) > len(self.regular)
+ def hasNonChromeOnly(self):
+ return len(self.regular) > 0
+ def variableName(self, chrome):
+ if chrome and self.hasChromeOnly():
+ return "sChrome" + self.name
+ if self.hasNonChromeOnly():
+ return "s" + self.name
+ return "NULL"
+ def usedForXrays(self, chrome):
+ # We only need Xrays for methods, attributes and constants. And we only
+ # need them for the non-chrome ones if we have no chromeonly things.
+ # Otherwise (we have chromeonly attributes) we need Xrays for the chrome
+ # methods/attributes/constants. Finally, in workers there are no Xrays.
+ return ((self.name is "Methods" or self.name is "Attributes" or
+ self.name is "Constants") and
+ chrome == self.hasChromeOnly() and
+ not self.descriptor.workers)
+
+ def __str__(self):
+ # We only need to generate id arrays for things that will end
+ # up used via ResolveProperty or EnumerateProperties.
+ str = self.generateArray(self.regular, self.variableName(False),
+ self.usedForXrays(False))
+ if self.hasChromeOnly():
+ str += self.generateArray(self.chrome, self.variableName(True),
+ self.usedForXrays(True))
+ return str
+
+ @staticmethod
+ def getControllingPref(interfaceMember):
+ prefName = interfaceMember.getExtendedAttribute("Pref")
+ if prefName is None:
+ return None
+ # It's a list of strings
+ assert(len(prefName) is 1)
+ assert(prefName[0] is not None)
+ return prefName[0]
+
+ def generatePrefableArray(self, array, name, specTemplate, specTerminator,
+ specType, getPref, getDataTuple, doIdArrays):
+ """
+ This method generates our various arrays.
+
+ array is an array of interface members as passed to generateArray
+
+ name is the name as passed to generateArray
+
+ specTemplate is a template for each entry of the spec array
+
+ specTerminator is a terminator for the spec array (inserted every time
+ our controlling pref changes and at the end of the array)
+
+ specType is the actual typename of our spec
+
+ getPref is a callback function that takes an array entry and returns
+ the corresponding pref value.
+
+ getDataTuple is a callback function that takes an array entry and
+ returns a tuple suitable for substitution into specTemplate.
+ """
+
+ # We want to generate a single list of specs, but with specTerminator
+ # inserted at every point where the pref name controlling the member
+ # changes. That will make sure the order of the properties as exposed
+ # on the interface and interface prototype objects does not change when
+ # pref control is added to members while still allowing us to define all
+ # the members in the smallest number of JSAPI calls.
+ assert(len(array) is not 0)
+ lastPref = getPref(array[0]) # So we won't put a specTerminator
+ # at the very front of the list.
+ specs = []
+ prefableSpecs = []
+ if doIdArrays:
+ prefableIds = []
+
+ prefableTemplate = ' { true, &%s[%d] }'
+ prefCacheTemplate = '&%s[%d].enabled'
+ def switchToPref(props, pref):
+ # Remember the info about where our pref-controlled
+ # booleans live.
+ if pref is not None:
+ props.prefCacheData.append(
+ (pref, prefCacheTemplate % (name, len(prefableSpecs)))
+ )
+ # Set up pointers to the new sets of specs and ids
+ # inside prefableSpecs and prefableIds
+ prefableSpecs.append(prefableTemplate %
+ (name + "_specs", len(specs)))
+
+ switchToPref(self, lastPref)
+
+ for member in array:
+ curPref = getPref(member)
+ if lastPref != curPref:
+ # Terminate previous list
+ specs.append(specTerminator)
+ # And switch to our new pref
+ switchToPref(self, curPref)
+ lastPref = curPref
+ # And the actual spec
+ specs.append(specTemplate % getDataTuple(member))
+ specs.append(specTerminator)
+ prefableSpecs.append(" { false, NULL }");
+
+ arrays = (("static %s %s_specs[] = {\n" +
+ ',\n'.join(specs) + "\n" +
+ "};\n\n" +
+ "static Prefable<%s> %s[] = {\n" +
+ ',\n'.join(prefableSpecs) + "\n" +
+ "};\n\n") % (specType, name, specType, name))
+ if doIdArrays:
+ arrays += ("static jsid %s_ids[%i] = { JSID_VOID };\n\n" %
+ (name, len(specs)))
+ return arrays
+
+
+# The length of a method is the maximum of the lengths of the
+# argument lists of all its overloads.
+def methodLength(method):
+ signatures = method.signatures()
+ return max([len(arguments) for (retType, arguments) in signatures])
+
+class MethodDefiner(PropertyDefiner):
+ """
+ A class for defining methods on a prototype object.
+ """
+ def __init__(self, descriptor, name, static):
+ PropertyDefiner.__init__(self, descriptor, name)
+
+ # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
+ # We should be able to check for special operations without an
+ # identifier. For now we check if the name starts with __
+ methods = [m for m in descriptor.interface.members if
+ m.isMethod() and m.isStatic() == static and
+ not m.isIdentifierLess()]
+ self.chrome = [{"name": m.identifier.name,
+ "length": methodLength(m),
+ "flags": "JSPROP_ENUMERATE",
+ "pref": PropertyDefiner.getControllingPref(m) }
+ for m in methods]
+ self.regular = [{"name": m.identifier.name,
+ "length": methodLength(m),
+ "flags": "JSPROP_ENUMERATE",
+ "pref": PropertyDefiner.getControllingPref(m) }
+ for m in methods if not isChromeOnly(m)]
+
+ # FIXME Check for an existing iterator on the interface first.
+ if any(m.isGetter() and m.isIndexed() for m in methods):
+ self.chrome.append({"name": 'iterator',
+ "methodInfo": False,
+ "nativeName": "JS_ArrayIterator",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "pref": None })
+ self.regular.append({"name": 'iterator',
+ "methodInfo": False,
+ "nativeName": "JS_ArrayIterator",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE",
+ "pref": None })
+
+ if not descriptor.interface.parent and not static and not descriptor.workers:
+ self.chrome.append({"name": 'QueryInterface',
+ "methodInfo": False,
+ "length": 1,
+ "flags": "0",
+ "pref": None })
+
+ if static:
+ if not descriptor.interface.hasInterfaceObject():
+ # static methods go on the interface object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+ else:
+ if not descriptor.interface.hasInterfacePrototypeObject():
+ # non-static methods go on the interface prototype object
+ assert not self.hasChromeOnly() and not self.hasNonChromeOnly()
+
+ def generateArray(self, array, name, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def pref(m):
+ return m["pref"]
+
+ def specData(m):
+ if m.get("methodInfo", True):
+ jitinfo = ("&%s_methodinfo" % m["name"])
+ accessor = "genericMethod"
+ else:
+ jitinfo = "nullptr"
+ accessor = m.get("nativeName", m["name"])
+ return (m["name"], accessor, jitinfo, m["length"], m["flags"])
+
+ return self.generatePrefableArray(
+ array, name,
+ ' JS_FNINFO("%s", %s, %s, %s, %s)',
+ ' JS_FS_END',
+ 'JSFunctionSpec',
+ pref, specData, doIdArrays)
+
+class AttrDefiner(PropertyDefiner):
+ def __init__(self, descriptor, name):
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ self.chrome = [m for m in descriptor.interface.members if m.isAttr()]
+ self.regular = [m for m in self.chrome if not isChromeOnly(m)]
+
+ def generateArray(self, array, name, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def flags(attr):
+ return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS"
+
+ def getter(attr):
+ native = ("genericLenientGetter" if attr.hasLenientThis()
+ else "genericGetter")
+ return ("{(JSPropertyOp)%(native)s, &%(name)s_getterinfo}"
+ % {"name" : attr.identifier.name,
+ "native" : native})
+
+ def setter(attr):
+ if attr.readonly:
+ return "JSOP_NULLWRAPPER"
+ native = ("genericLenientSetter" if attr.hasLenientThis()
+ else "genericSetter")
+ return ("{(JSStrictPropertyOp)%(native)s, &%(name)s_setterinfo}"
+ % {"name" : attr.identifier.name,
+ "native" : native})
+
+ def specData(attr):
+ return (attr.identifier.name, flags(attr), getter(attr),
+ setter(attr))
+
+ return self.generatePrefableArray(
+ array, name,
+ ' { "%s", 0, %s, %s, %s}',
+ ' { 0, 0, 0, JSOP_NULLWRAPPER, JSOP_NULLWRAPPER }',
+ 'JSPropertySpec',
+ PropertyDefiner.getControllingPref, specData, doIdArrays)
+
+class ConstDefiner(PropertyDefiner):
+ """
+ A class for definining constants on the interface object
+ """
+ def __init__(self, descriptor, name):
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ self.chrome = [m for m in descriptor.interface.members if m.isConst()]
+ self.regular = [m for m in self.chrome if not isChromeOnly(m)]
+
+ def generateArray(self, array, name, doIdArrays):
+ if len(array) == 0:
+ return ""
+
+ def specData(const):
+ return (const.identifier.name,
+ convertConstIDLValueToJSVal(const.value))
+
+ return self.generatePrefableArray(
+ array, name,
+ ' { "%s", %s }',
+ ' { 0, JSVAL_VOID }',
+ 'ConstantSpec',
+ PropertyDefiner.getControllingPref, specData, doIdArrays)
+
+class PropertyArrays():
+ def __init__(self, descriptor):
+ self.staticMethods = MethodDefiner(descriptor, "StaticMethods", True)
+ self.methods = MethodDefiner(descriptor, "Methods", False)
+ self.attrs = AttrDefiner(descriptor, "Attributes")
+ self.consts = ConstDefiner(descriptor, "Constants")
+
+ @staticmethod
+ def arrayNames():
+ return [ "staticMethods", "methods", "attrs", "consts" ]
+
+ @staticmethod
+ def xrayRelevantArrayNames():
+ return [ "methods", "attrs", "consts" ]
+
+ def hasChromeOnly(self):
+ return reduce(lambda b, a: b or getattr(self, a).hasChromeOnly(),
+ self.arrayNames(), False)
+ def variableNames(self, chrome):
+ names = {}
+ for array in self.arrayNames():
+ names[array] = getattr(self, array).variableName(chrome)
+ return names
+ def __str__(self):
+ define = ""
+ for array in self.arrayNames():
+ define += str(getattr(self, array))
+ return define
+
+class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
+ """
+ Generate the CreateInterfaceObjects method for an interface descriptor.
+
+ properties should be a PropertyArrays instance.
+ """
+ def __init__(self, descriptor, properties):
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'),
+ Argument('JSObject*', 'aReceiver')]
+ CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'JSObject*', args)
+ self.properties = properties
+ def definition_body(self):
+ protoChain = self.descriptor.prototypeChain
+ if len(protoChain) == 1:
+ getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)"
+ else:
+ parentProtoName = self.descriptor.prototypeChain[-2]
+ getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" %
+ toBindingNamespace(parentProtoName))
+
+ needInterfaceObject = self.descriptor.interface.hasInterfaceObject()
+ needInterfacePrototypeObject = self.descriptor.interface.hasInterfacePrototypeObject()
+
+ # if we don't need to create anything, why are we generating this?
+ assert needInterfaceObject or needInterfacePrototypeObject
+
+ idsToInit = []
+ # There is no need to init any IDs in workers, because worker bindings
+ # don't have Xrays.
+ if not self.descriptor.workers:
+ for var in self.properties.xrayRelevantArrayNames():
+ props = getattr(self.properties, var)
+ # We only have non-chrome ids to init if we have no chrome ids.
+ if props.hasChromeOnly():
+ idsToInit.append(props.variableName(True))
+ elif props.hasNonChromeOnly():
+ idsToInit.append(props.variableName(False))
+ if len(idsToInit) > 0:
+ initIds = CGList(
+ [CGGeneric("!InitIds(aCx, %s, %s_ids)" % (varname, varname)) for
+ varname in idsToInit], ' ||\n')
+ if len(idsToInit) > 1:
+ initIds = CGWrapper(initIds, pre="(", post=")", reindent=True)
+ initIds = CGList(
+ [CGGeneric("%s_ids[0] == JSID_VOID &&" % idsToInit[0]), initIds],
+ "\n")
+ initIds = CGWrapper(initIds, pre="if (", post=") {", reindent=True)
+ initIds = CGList(
+ [initIds,
+ CGGeneric((" %s_ids[0] = JSID_VOID;\n"
+ " return NULL;") % idsToInit[0]),
+ CGGeneric("}")],
+ "\n")
+ else:
+ initIds = None
+
+ prefCacheData = []
+ for var in self.properties.arrayNames():
+ props = getattr(self.properties, var)
+ prefCacheData.extend(props.prefCacheData)
+ if len(prefCacheData) is not 0:
+ prefCacheData = [
+ CGGeneric('Preferences::AddBoolVarCache(%s, "%s");' % (ptr, pref)) for
+ (pref, ptr) in prefCacheData]
+ prefCache = CGWrapper(CGIndenter(CGList(prefCacheData, "\n")),
+ pre=("static bool sPrefCachesInited = false;\n"
+ "if (!sPrefCachesInited) {\n"
+ " sPrefCachesInited = true;\n"),
+ post="\n}")
+ else:
+ prefCache = None
+
+ getParentProto = ("JSObject* parentProto = %s;\n" +
+ "if (!parentProto) {\n" +
+ " return NULL;\n" +
+ "}\n") % getParentProto
+
+ needInterfaceObjectClass = (needInterfaceObject and
+ self.descriptor.hasInstanceInterface)
+ needConstructor = (needInterfaceObject and
+ not self.descriptor.hasInstanceInterface)
+ if self.descriptor.interface.ctor():
+ constructHook = CONSTRUCT_HOOK_NAME
+ constructArgs = methodLength(self.descriptor.interface.ctor())
+ else:
+ constructHook = "ThrowingConstructor"
+ constructArgs = 0
+
+ if self.descriptor.concrete:
+ if self.descriptor.proxy:
+ domClass = "&Class"
+ else:
+ domClass = "&Class.mClass"
+ else:
+ domClass = "nullptr"
+
+ call = """return dom::CreateInterfaceObjects(aCx, aGlobal, aReceiver, parentProto,
+ %s, %s, %s, %d,
+ %s,
+ %%(methods)s, %%(attrs)s,
+ %%(consts)s, %%(staticMethods)s,
+ %s);""" % (
+ "&PrototypeClass" if needInterfacePrototypeObject else "NULL",
+ "&InterfaceObjectClass" if needInterfaceObjectClass else "NULL",
+ constructHook if needConstructor else "NULL",
+ constructArgs,
+ domClass,
+ '"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "NULL")
+ if self.properties.hasChromeOnly():
+ if self.descriptor.workers:
+ accessCheck = "mozilla::dom::workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker()"
+ else:
+ accessCheck = "xpc::AccessCheck::isChrome(js::GetObjectCompartment(aGlobal))"
+ chrome = CGIfWrapper(CGGeneric(call % self.properties.variableNames(True)),
+ accessCheck)
+ chrome = CGWrapper(chrome, pre="\n\n")
+ else:
+ chrome = None
+
+ functionBody = CGList(
+ [CGGeneric(getParentProto), initIds, prefCache, chrome,
+ CGGeneric(call % self.properties.variableNames(False))],
+ "\n\n")
+ return CGIndenter(functionBody).define()
+
+class CGGetPerInterfaceObject(CGAbstractMethod):
+ """
+ A method for getting a per-interface object (a prototype object or interface
+ constructor object).
+ """
+ def __init__(self, descriptor, name, idPrefix=""):
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aGlobal'),
+ Argument('JSObject*', 'aReceiver')]
+ CGAbstractMethod.__init__(self, descriptor, name,
+ 'JSObject*', args, inline=True)
+ self.id = idPrefix + "id::" + self.descriptor.name
+ def definition_body(self):
+ return """
+
+ /* aGlobal and aReceiver are usually the same, but they can be different
+ too. For example a sandbox often has an xray wrapper for a window as the
+ prototype of the sandbox's global. In that case aReceiver is the xray
+ wrapper and aGlobal is the sandbox's global.
+ */
+
+ /* Make sure our global is sane. Hopefully we can remove this sometime */
+ if (!(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL)) {
+ return NULL;
+ }
+ /* Check to see whether the interface objects are already installed */
+ JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal);
+ JSObject* cachedObject = protoOrIfaceArray[%s];
+ if (!cachedObject) {
+ protoOrIfaceArray[%s] = cachedObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver);
+ }
+
+ /* cachedObject might _still_ be null, but that's OK */
+ return cachedObject;""" % (self.id, self.id)
+
+class CGGetProtoObjectMethod(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface prototype object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject",
+ "prototypes::")
+ def definition_body(self):
+ return """
+ /* Get the interface prototype object for this class. This will create the
+ object as needed. */""" + CGGetPerInterfaceObject.definition_body(self)
+
+class CGGetConstructorObjectMethod(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface constructor object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject",
+ "constructors::")
+ def definition_body(self):
+ return """
+ /* Get the interface object for this class. This will create the object as
+ needed. */""" + CGGetPerInterfaceObject.definition_body(self)
+
+def CheckPref(descriptor, globalName, varName, retval, wrapperCache = None):
+ """
+ Check whether bindings should be enabled for this descriptor. If not, set
+ varName to false and return retval.
+ """
+ if not descriptor.prefable:
+ return ""
+
+ if wrapperCache:
+ wrapperCache = " %s->ClearIsDOMBinding();\n" % (wrapperCache)
+ else:
+ wrapperCache = ""
+
+ failureCode = (" %s = false;\n" +
+ " return %s;") % (varName, retval)
+ return """
+ {
+ XPCWrappedNativeScope* scope =
+ XPCWrappedNativeScope::FindInJSObjectScope(aCx, %s);
+ if (!scope) {
+%s
+ }
+
+ if (!scope->ExperimentalBindingsEnabled()) {
+%s%s
+ }
+ }
+""" % (globalName, failureCode, wrapperCache, failureCode)
+
+class CGDefineDOMInterfaceMethod(CGAbstractMethod):
+ """
+ A method for resolve hooks to try to lazily define the interface object for
+ a given interface.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aReceiver'),
+ Argument('bool*', 'aEnabled')]
+ CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'bool', args)
+
+ def declare(self):
+ if self.descriptor.workers:
+ return ''
+ return CGAbstractMethod.declare(self)
+
+ def define(self):
+ if self.descriptor.workers:
+ return ''
+ return CGAbstractMethod.define(self)
+
+ def definition_body(self):
+ if self.descriptor.interface.hasInterfacePrototypeObject():
+ # We depend on GetProtoObject defining an interface constructor
+ # object as needed.
+ getter = "GetProtoObject"
+ else:
+ getter = "GetConstructorObject"
+
+ return (" JSObject* global = JS_GetGlobalForObject(aCx, aReceiver);\n" +
+ CheckPref(self.descriptor, "global", "*aEnabled", "false") +
+ """
+ *aEnabled = true;
+ return !!%s(aCx, global, aReceiver);""" % (getter))
+
+class CGPrefEnabled(CGAbstractMethod):
+ """
+ A method for testing whether the preference controlling this
+ interface is enabled. When it's not, the interface should not be
+ visible on the global.
+ """
+ def __init__(self, descriptor):
+ CGAbstractMethod.__init__(self, descriptor, 'PrefEnabled', 'bool', [])
+
+ def declare(self):
+ return CGAbstractMethod.declare(self)
+
+ def define(self):
+ return CGAbstractMethod.define(self)
+
+ def definition_body(self):
+ return " return %s::PrefEnabled();" % self.descriptor.nativeType
+
+class CGIsMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSObject*', 'obj')]
+ CGAbstractMethod.__init__(self, descriptor, 'Is', 'bool', args)
+
+ def definition_body(self):
+ # Non-proxy implementation would check
+ # js::GetObjectJSClass(obj) == &Class.mBase
+ return """ return IsProxy(obj);"""
+
+def CreateBindingJSObject(descriptor, parent):
+ if descriptor.proxy:
+ create = """ JSObject *obj = NewProxyObject(aCx, DOMProxyHandler::getInstance(),
+ JS::PrivateValue(aObject), proto, %s);
+ if (!obj) {
+ return NULL;
+ }
+
+"""
+ else:
+ create = """ JSObject* obj = JS_NewObject(aCx, &Class.mBase, proto, %s);
+ if (!obj) {
+ return NULL;
+ }
+
+ js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
+"""
+ return create % parent
+
+class CGWrapWithCacheMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
+ Argument(descriptor.nativeType + '*', 'aObject'),
+ Argument('nsWrapperCache*', 'aCache'),
+ Argument('bool*', 'aTriedToWrap')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
+
+ def definition_body(self):
+ if self.descriptor.workers:
+ return """ *aTriedToWrap = true;
+ return aObject->GetJSObject();"""
+
+ return """ *aTriedToWrap = true;
+
+ JSObject* parent = WrapNativeParent(aCx, aScope, aObject->GetParentObject());
+ if (!parent) {
+ return NULL;
+ }
+
+ JSAutoCompartment ac(aCx, parent);
+ JSObject* global = JS_GetGlobalForObject(aCx, parent);
+%s
+ JSObject* proto = GetProtoObject(aCx, global, global);
+ if (!proto) {
+ return NULL;
+ }
+
+%s
+ NS_ADDREF(aObject);
+
+ aCache->SetWrapper(obj);
+
+ return obj;""" % (CheckPref(self.descriptor, "global", "*aTriedToWrap", "NULL", "aCache"),
+ CreateBindingJSObject(self.descriptor, "parent"))
+
+class CGWrapMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
+ Argument('T*', 'aObject'), Argument('bool*', 'aTriedToWrap')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args, inline=True, templateArgs=["class T"])
+
+ def definition_body(self):
+ return " return Wrap(aCx, aScope, aObject, aObject, aTriedToWrap);"
+
+class CGWrapNonWrapperCacheMethod(CGAbstractMethod):
+ def __init__(self, descriptor):
+ # XXX can we wrap if we don't have an interface prototype object?
+ assert descriptor.interface.hasInterfacePrototypeObject()
+ args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
+ Argument(descriptor.nativeType + '*', 'aObject')]
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
+
+ def definition_body(self):
+ return """
+ JSObject* global = JS_GetGlobalForObject(aCx, aScope);
+ JSObject* proto = GetProtoObject(aCx, global, global);
+ if (!proto) {
+ return NULL;
+ }
+
+%s
+ NS_ADDREF(aObject);
+
+ return obj;""" % CreateBindingJSObject(self.descriptor, "global")
+
+builtinNames = {
+ IDLType.Tags.bool: 'bool',
+ IDLType.Tags.int8: 'int8_t',
+ IDLType.Tags.int16: 'int16_t',
+ IDLType.Tags.int32: 'int32_t',
+ IDLType.Tags.int64: 'int64_t',
+ IDLType.Tags.uint8: 'uint8_t',
+ IDLType.Tags.uint16: 'uint16_t',
+ IDLType.Tags.uint32: 'uint32_t',
+ IDLType.Tags.uint64: 'uint64_t',
+ IDLType.Tags.float: 'float',
+ IDLType.Tags.double: 'double'
+}
+
+numericTags = [
+ IDLType.Tags.int8, IDLType.Tags.uint8,
+ IDLType.Tags.int16, IDLType.Tags.uint16,
+ IDLType.Tags.int32, IDLType.Tags.uint32,
+ IDLType.Tags.int64, IDLType.Tags.uint64,
+ IDLType.Tags.float, IDLType.Tags.double
+ ]
+
+class CastableObjectUnwrapper():
+ """
+ A class for unwrapping an object named by the "source" argument
+ based on the passed-in descriptor and storing it in a variable
+ called by the name in the "target" argument.
+
+ codeOnFailure is the code to run if unwrapping fails.
+ """
+ def __init__(self, descriptor, source, target, codeOnFailure):
+ assert descriptor.castable
+
+ self.substitution = { "type" : descriptor.nativeType,
+ "protoID" : "prototypes::id::" + descriptor.name,
+ "source" : source,
+ "target" : target,
+ "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure), 4).define() }
+ if descriptor.hasXPConnectImpls:
+ # We don't use xpc_qsUnwrapThis because it will always throw on
+ # unwrap failure, whereas we want to control whether we throw or
+ # not.
+ self.substitution["codeOnFailure"] = CGIndenter(CGGeneric(string.Template(
+ "${type} *objPtr;\n"
+ "xpc_qsSelfRef objRef;\n"
+ "JS::Value val = JS::ObjectValue(*${source});\n"
+ "nsresult rv = xpc_qsUnwrapArg<${type}>(cx, val, &objPtr, &objRef.ptr, &val);\n"
+ "if (NS_FAILED(rv)) {\n"
+ "${codeOnFailure}\n"
+ "}\n"
+ "// We should be castable!\n"
+ "MOZ_ASSERT(!objRef.ptr);\n"
+ "// We should have an object, too!\n"
+ "MOZ_ASSERT(objPtr);\n"
+ "${target} = objPtr;").substitute(self.substitution)), 4).define()
+
+ def __str__(self):
+ return string.Template(
+"""{
+ nsresult rv = UnwrapObject<${protoID}, ${type}>(cx, ${source}, ${target});
+ if (NS_FAILED(rv)) {
+${codeOnFailure}
+ }
+}""").substitute(self.substitution)
+
+class FailureFatalCastableObjectUnwrapper(CastableObjectUnwrapper):
+ """
+ As CastableObjectUnwrapper, but defaulting to throwing if unwrapping fails
+ """
+ def __init__(self, descriptor, source, target):
+ CastableObjectUnwrapper.__init__(self, descriptor, source, target,
+ "return Throw<%s>(cx, rv);" %
+ toStringBool(not descriptor.workers))
+
+class CallbackObjectUnwrapper:
+ """
+ A class for unwrapping objects implemented in JS.
+
+ |source| is the JSObject we want to use in native code.
+ |target| is an nsCOMPtr of the appropriate type in which we store the result.
+ """
+ def __init__(self, descriptor, source, target, codeOnFailure=None):
+ if codeOnFailure is None:
+ codeOnFailure = ("return Throw<%s>(cx, rv);" %
+ toStringBool(not descriptor.workers))
+ self.descriptor = descriptor
+ self.substitution = { "nativeType" : descriptor.nativeType,
+ "source" : source,
+ "target" : target,
+ "codeOnFailure" : CGIndenter(CGGeneric(codeOnFailure)).define() }
+
+ def __str__(self):
+ if self.descriptor.workers:
+ return string.Template(
+ "${target} = ${source};"
+ ).substitute(self.substitution)
+
+ return string.Template(
+ """nsresult rv;
+XPCCallContext ccx(JS_CALLER, cx);
+if (!ccx.IsValid()) {
+ rv = NS_ERROR_XPC_BAD_CONVERT_JS;
+${codeOnFailure}
+}
+
+const nsIID& iid = NS_GET_IID(${nativeType});
+nsRefPtr<nsXPCWrappedJS> wrappedJS;
+rv = nsXPCWrappedJS::GetNewOrUsed(ccx, ${source}, iid,
+ NULL, getter_AddRefs(wrappedJS));
+if (NS_FAILED(rv) || !wrappedJS) {
+${codeOnFailure}
+}
+
+// Use a temp nsCOMPtr for the null-check, because ${target} might be
+// OwningNonNull, not an nsCOMPtr.
+nsCOMPtr<${nativeType}> tmp = do_QueryObject(wrappedJS.get());
+if (!tmp) {
+${codeOnFailure}
+}
+${target} = tmp.forget();""").substitute(self.substitution)
+
+def dictionaryHasSequenceMember(dictionary):
+ return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in
+ dictionary.members) or
+ (dictionary.parent and
+ dictionaryHasSequenceMember(dictionary.parent)))
+
+def typeIsSequenceOrHasSequenceMember(type):
+ if type.nullable():
+ type = type.inner
+ if type.isSequence():
+ return True
+ if type.isArray():
+ elementType = type.inner
+ return typeIsSequenceOrHasSequenceMember(elementType)
+ if type.isDictionary():
+ return dictionaryHasSequenceMember(type.inner)
+ if type.isUnion():
+ return any(typeIsSequenceOrHasSequenceMember(m.type) for m in
+ type.flatMemberTypes)
+ return False
+
+def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
+ isDefinitelyObject=False,
+ isMember=False,
+ isOptional=False,
+ invalidEnumValueFatal=True,
+ defaultValue=None,
+ treatNullAs="Default",
+ treatUndefinedAs="Default",
+ isEnforceRange=False,
+ isClamp=False):
+ """
+ Get a template for converting a JS value to a native object based on the
+ given type and descriptor. If failureCode is given, then we're actually
+ testing whether we can convert the argument to the desired type. That
+ means that failures to convert due to the JS value being the wrong type of
+ value need to use failureCode instead of throwing exceptions. Failures to
+ convert that are due to JS exceptions (from toString or valueOf methods) or
+ out of memory conditions need to throw exceptions no matter what
+ failureCode is.
+
+ If isDefinitelyObject is True, that means we know the value
+ isObject() and we have no need to recheck that.
+
+ if isMember is True, we're being converted from a property of some
+ JS object, not from an actual method argument, so we can't rely on
+ our jsval being rooted or outliving us in any way. Any caller
+ passing true needs to ensure that it is handled correctly in
+ typeIsSequenceOrHasSequenceMember.
+
+ If isOptional is true, then we are doing conversion of an optional
+ argument with no default value.
+
+ invalidEnumValueFatal controls whether an invalid enum value conversion
+ attempt will throw (if true) or simply return without doing anything (if
+ false).
+
+ If defaultValue is not None, it's the IDL default value for this conversion
+
+ If isEnforceRange is true, we're converting an integer and throwing if the
+ value is out of range.
+
+ If isClamp is true, we're converting an integer and clamping if the
+ value is out of range.
+
+ The return value from this function is a tuple consisting of four things:
+
+ 1) A string representing the conversion code. This will have template
+ substitution performed on it as follows:
+
+ ${val} replaced by an expression for the JS::Value in question
+ ${valPtr} is a pointer to the JS::Value in question
+ ${holderName} replaced by the holder's name, if any
+ ${declName} replaced by the declaration's name
+ ${haveValue} replaced by an expression that evaluates to a boolean
+ for whether we have a JS::Value. Only used when
+ defaultValue is not None.
+
+ 2) A CGThing representing the native C++ type we're converting to
+ (declType). This is allowed to be None if the conversion code is
+ supposed to be used as-is.
+ 3) A CGThing representing the type of a "holder" (holderType) which will
+ hold a possible reference to the C++ thing whose type we returned in #1,
+ or None if no such holder is needed.
+ 4) A boolean indicating whether the caller has to do optional-argument handling.
+ This will only be true if isOptional is true and if the returned template
+ expects both declType and holderType to be wrapped in Optional<>, with
+ ${declName} and ${holderName} adjusted to point to the Value() of the
+ Optional, and Construct() calls to be made on the Optional<>s as needed.
+
+ ${declName} must be in scope before the generated code is entered.
+
+ If holderType is not None then ${holderName} must be in scope
+ before the generated code is entered.
+ """
+ # If we have a defaultValue then we're not actually optional for
+ # purposes of what we need to be declared as.
+ assert(defaultValue is None or not isOptional)
+
+ # Also, we should not have a defaultValue if we know we're an object
+ assert(not isDefinitelyObject or defaultValue is None)
+
+ # Helper functions for dealing with failures due to the JS value being the
+ # wrong type of value
+ def onFailureNotAnObject(failureCode):
+ return CGWrapper(CGGeneric(
+ failureCode or
+ 'return ThrowErrorMessage(cx, MSG_NOT_OBJECT);'), post="\n")
+ def onFailureBadType(failureCode, typeName):
+ return CGWrapper(CGGeneric(
+ failureCode or
+ 'return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");' % typeName), post="\n")
+
+ # A helper function for handling default values. Takes a template
+ # body and the C++ code to set the default value and wraps the
+ # given template body in handling for the default value.
+ def handleDefault(template, setDefault):
+ if defaultValue is None:
+ return template
+ return CGWrapper(
+ CGIndenter(CGGeneric(template)),
+ pre="if (${haveValue}) {\n",
+ post=("\n"
+ "} else {\n"
+ "%s;\n"
+ "}" %
+ CGIndenter(CGGeneric(setDefault)).define())).define()
+
+ # A helper function for handling null default values. Much like
+ # handleDefault, but checks that the default value, if it exists, is null.
+ def handleDefaultNull(template, codeToSetNull):
+ if (defaultValue is not None and
+ not isinstance(defaultValue, IDLNullValue)):
+ raise TypeError("Can't handle non-null default value here")
+ return handleDefault(template, codeToSetNull)
+
+ # A helper function for wrapping up the template body for
+ # possibly-nullable objecty stuff
+ def wrapObjectTemplate(templateBody, isDefinitelyObject, type,
+ codeToSetNull, failureCode=None):
+ if not isDefinitelyObject:
+ # Handle the non-object cases by wrapping up the whole
+ # thing in an if cascade.
+ templateBody = (
+ "if (${val}.isObject()) {\n" +
+ CGIndenter(CGGeneric(templateBody)).define() + "\n")
+ if type.nullable():
+ templateBody += (
+ "} else if (${val}.isNullOrUndefined()) {\n"
+ " %s;\n" % codeToSetNull)
+ templateBody += (
+ "} else {\n" +
+ CGIndenter(onFailureNotAnObject(failureCode)).define() +
+ "}")
+ if type.nullable():
+ templateBody = handleDefaultNull(templateBody, codeToSetNull)
+ else:
+ assert(defaultValue is None)
+
+ return templateBody
+
+ assert not (isEnforceRange and isClamp) # These are mutually exclusive
+
+ if type.isArray():
+ raise TypeError("Can't handle array arguments yet")
+
+ if type.isSequence():
+ assert not isEnforceRange and not isClamp
+
+ if failureCode is not None:
+ raise TypeError("Can't handle sequences when failureCode is not None")
+ nullable = type.nullable();
+ # Be very careful not to change "type": we need it later
+ if nullable:
+ elementType = type.inner.inner
+ else:
+ elementType = type.inner
+
+ # We have to be careful with reallocation behavior for arrays. In
+ # particular, if we have a sequence of elements which are themselves
+ # sequences (so nsAutoTArrays) or have sequences as members, we have a
+ # problem. In that case, resizing the outermost nsAutoTarray to the
+ # right size will memmove its elements, but nsAutoTArrays are not
+ # memmovable and hence will end up with pointers to bogus memory, which
+ # is bad. To deal with this, we disallow sequences, arrays,
+ # dictionaries, and unions which contain sequences as sequence item
+ # types. If WebIDL ever adds another container type, we'd have to
+ # disallow it as well.
+ if typeIsSequenceOrHasSequenceMember(elementType):
+ raise TypeError("Can't handle a sequence containing another "
+ "sequence as an element or member of an element. "
+ "See the big comment explaining why.\n%s" %
+ str(type.location))
+
+ (elementTemplate, elementDeclType,
+ elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate(
+ elementType, descriptorProvider, isMember=True)
+ if dealWithOptional:
+ raise TypeError("Shouldn't have optional things in sequences")
+ if elementHolderType is not None:
+ raise TypeError("Shouldn't need holders for sequences")
+
+ typeName = CGWrapper(elementDeclType, pre="Sequence< ", post=" >")
+ if nullable:
+ typeName = CGWrapper(typeName, pre="Nullable< ", post=" >")
+ arrayRef = "${declName}.Value()"
+ else:
+ arrayRef = "${declName}"
+ # If we're optional, the const will come from the Optional
+ mutableTypeName = typeName
+ if not isOptional:
+ typeName = CGWrapper(typeName, pre="const ")
+
+ templateBody = ("""JSObject* seq = &${val}.toObject();\n
+if (!IsArrayLike(cx, seq)) {
+ return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
+}
+uint32_t length;
+// JS_GetArrayLength actually works on all objects
+if (!JS_GetArrayLength(cx, seq, &length)) {
+ return false;
+}
+Sequence< %s > &arr = const_cast< Sequence< %s >& >(%s);
+if (!arr.SetCapacity(length)) {
+ return Throw<%s>(cx, NS_ERROR_OUT_OF_MEMORY);
+}
+for (uint32_t i = 0; i < length; ++i) {
+ jsval temp;
+ if (!JS_GetElement(cx, seq, i, &temp)) {
+ return false;
+ }
+""" % (toStringBool(descriptorProvider.workers),
+ elementDeclType.define(),
+ elementDeclType.define(),
+ arrayRef,
+ toStringBool(descriptorProvider.workers)))
+
+ templateBody += CGIndenter(CGGeneric(
+ string.Template(elementTemplate).substitute(
+ {
+ "val" : "temp",
+ "valPtr": "&temp",
+ "declName" : "(*arr.AppendElement())"
+ }
+ ))).define()
+
+ templateBody += "\n}"
+ templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject,
+ type,
+ "const_cast< %s & >(${declName}).SetNull()" % mutableTypeName.define())
+ return (templateBody, typeName, None, isOptional)
+
+ if type.isUnion():
+ if isMember:
+ raise TypeError("Can't handle unions as members, we have a "
+ "holderType")
+ nullable = type.nullable();
+ if nullable:
+ type = type.inner
+
+ assert(defaultValue is None or
+ (isinstance(defaultValue, IDLNullValue) and nullable))
+
+ unionArgumentObj = "${holderName}"
+ if isOptional or nullable:
+ unionArgumentObj += ".ref()"
+
+ memberTypes = type.flatMemberTypes
+ names = []
+
+ interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes)
+ if len(interfaceMemberTypes) > 0:
+ interfaceObject = []
+ for memberType in interfaceMemberTypes:
+ if type.isGeckoInterface():
+ name = memberType.inner.identifier.name
+ else:
+ name = memberType.name
+ interfaceObject.append(CGGeneric("(failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext" % (unionArgumentObj, name)))
+ names.append(name)
+ interfaceObject = CGWrapper(CGList(interfaceObject, " ||\n"), pre="done = ", post=";\n", reindent=True)
+ else:
+ interfaceObject = None
+
+ arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes)
+ if len(arrayObjectMemberTypes) > 0:
+ assert len(arrayObjectMemberTypes) == 1
+ memberType = arrayObjectMemberTypes[0]
+ name = memberType.name
+ arrayObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+ # XXX Now we're supposed to check for an array or a platform object
+ # that supports indexed properties... skip that last for now. It's a
+ # bit of a pain.
+ arrayObject = CGWrapper(CGIndenter(arrayObject),
+ pre="if (IsArrayLike(cx, &argObj)) {\n",
+ post="}")
+ names.append(name)
+ else:
+ arrayObject = None
+
+ dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes)
+ if len(dateObjectMemberTypes) > 0:
+ assert len(dateObjectMemberTypes) == 1
+ memberType = dateObjectMemberTypes[0]
+ name = memberType.name
+ dateObject = CGGeneric("%s.SetTo%s(cx, ${val}, ${valPtr});\n"
+ "done = true;" % (unionArgumentObj, name))
+ dateObject = CGWrapper(CGIndenter(dateObject),
+ pre="if (JS_ObjectIsDate(cx, &argObj)) {\n",
+ post="\n}")
+ names.append(name)
+ else:
+ dateObject = None
+
+ callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes)
+ if len(callbackMemberTypes) > 0:
+ assert len(callbackMemberTypes) == 1
+ memberType = callbackMemberTypes[0]
+ name = memberType.name
+ callbackObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+ names.append(name)
+ else:
+ callbackObject = None
+
+ dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes)
+ if len(dictionaryMemberTypes) > 0:
+ raise TypeError("No support for unwrapping dictionaries as member "
+ "of a union")
+ else:
+ dictionaryObject = None
+
+ if callbackObject or dictionaryObject:
+ nonPlatformObject = CGList([callbackObject, dictionaryObject], "\n")
+ nonPlatformObject = CGWrapper(CGIndenter(nonPlatformObject),
+ pre="if (!IsPlatformObject(cx, &argObj)) {\n",
+ post="\n}")
+ else:
+ nonPlatformObject = None
+
+ objectMemberTypes = filter(lambda t: t.isObject(), memberTypes)
+ if len(objectMemberTypes) > 0:
+ object = CGGeneric("%s.SetToObject(&argObj);\n"
+ "done = true;" % unionArgumentObj)
+ else:
+ object = None
+
+ hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object
+ if hasObjectTypes:
+ # If we try more specific object types first then we need to check
+ # whether that succeeded before converting to object.
+ if object and (interfaceObject or arrayObject or dateObject or nonPlatformObject):
+ object = CGWrapper(CGIndenter(object), pre="if (!done) {\n",
+ post=("\n}"))
+
+ if arrayObject or dateObject or nonPlatformObject:
+ # An object can be both an array object and not a platform
+ # object, but we shouldn't have both in the union's members
+ # because they are not distinguishable.
+ assert not (arrayObject and nonPlatformObject)
+ templateBody = CGList([arrayObject, dateObject, nonPlatformObject], " else ")
+ else:
+ templateBody = None
+ if interfaceObject:
+ if templateBody:
+ templateBody = CGList([templateBody, object], "\n")
+ templateBody = CGWrapper(CGIndenter(templateBody),
+ pre="if (!done) {\n", post=("\n}"))
+ templateBody = CGList([interfaceObject, templateBody], "\n")
+ else:
+ templateBody = CGList([templateBody, object], "\n")
+
+ if any([arrayObject, dateObject, nonPlatformObject, object]):
+ templateBody.prepend(CGGeneric("JSObject& argObj = ${val}.toObject();"))
+ templateBody = CGWrapper(CGIndenter(templateBody),
+ pre="if (${val}.isObject()) {\n",
+ post="\n}")
+ else:
+ templateBody = CGGeneric()
+
+ otherMemberTypes = filter(lambda t: t.isString() or t.isEnum(),
+ memberTypes)
+ otherMemberTypes.extend(t for t in memberTypes if t.isPrimitive())
+ if len(otherMemberTypes) > 0:
+ assert len(otherMemberTypes) == 1
+ memberType = otherMemberTypes[0]
+ if memberType.isEnum():
+ name = memberType.inner.identifier.name
+ else:
+ name = memberType.name
+ other = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
+ names.append(name)
+ if hasObjectTypes:
+ other = CGWrapper(CGIndenter(other), "{\n", post="\n}")
+ if object:
+ join = " else "
+ else:
+ other = CGWrapper(other, pre="if (!done) ")
+ join = "\n"
+ templateBody = CGList([templateBody, other], join)
+ else:
+ other = None
+
+ templateBody = CGWrapper(templateBody, pre="bool done = false, failed = false, tryNext;\n")
+ throw = CGGeneric("if (failed) {\n"
+ " return false;\n"
+ "}\n"
+ "if (!done) {\n"
+ " return ThrowErrorMessage(cx, MSG_NOT_IN_UNION, \"%s\");\n"
+ "}" % ", ".join(names))
+ templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw], "\n")), pre="{\n", post="\n}")
+
+ typeName = type.name
+ argumentTypeName = typeName + "Argument"
+ if nullable:
+ typeName = "Nullable<" + typeName + " >"
+ if isOptional:
+ nonConstDecl = "const_cast<Optional<" + typeName + " >& >(${declName})"
+ else:
+ nonConstDecl = "const_cast<" + typeName + "& >(${declName})"
+ typeName = "const " + typeName
+
+ def handleNull(templateBody, setToNullVar, extraConditionForNull=""):
+ null = CGGeneric("if (%s${val}.isNullOrUndefined()) {\n"
+ " %s.SetNull();\n"
+ "}" % (extraConditionForNull, setToNullVar))
+ templateBody = CGWrapper(CGIndenter(templateBody), pre="{\n", post="\n}")
+ return CGList([null, templateBody], " else ")
+
+ if type.hasNullableType:
+ templateBody = handleNull(templateBody, unionArgumentObj)
+
+ declType = CGGeneric(typeName)
+ holderType = CGGeneric(argumentTypeName)
+ if isOptional:
+ mutableDecl = nonConstDecl + ".Value()"
+ declType = CGWrapper(declType, pre="const Optional<", post=" >")
+ holderType = CGWrapper(holderType, pre="Maybe<", post=" >")
+ constructDecl = CGGeneric(nonConstDecl + ".Construct();")
+ if nullable:
+ constructHolder = CGGeneric("${holderName}.construct(%s.SetValue());" % mutableDecl)
+ else:
+ constructHolder = CGGeneric("${holderName}.construct(${declName}.Value());")
+ else:
+ mutableDecl = nonConstDecl
+ constructDecl = None
+ if nullable:
+ holderType = CGWrapper(holderType, pre="Maybe<", post=" >")
+ constructHolder = CGGeneric("${holderName}.construct(%s.SetValue());" % mutableDecl)
+ else:
+ constructHolder = CGWrapper(holderType, post=" ${holderName}(${declName});")
+ holderType = None
+
+ templateBody = CGList([constructHolder, templateBody], "\n")
+ if nullable:
+ if defaultValue:
+ assert(isinstance(defaultValue, IDLNullValue))
+ valueMissing = "!(${haveValue}) || "
+ else:
+ valueMissing = ""
+ templateBody = handleNull(templateBody, mutableDecl,
+ extraConditionForNull=valueMissing)
+ templateBody = CGList([constructDecl, templateBody], "\n")
+
+ return templateBody.define(), declType, holderType, False
+
+ if type.isGeckoInterface():
+ assert not isEnforceRange and not isClamp
+
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name)
+ # This is an interface that we implement as a concrete class
+ # or an XPCOM interface.
+
+ # Allow null pointers for nullable types and old-binding classes
+ argIsPointer = type.nullable() or type.unroll().inner.isExternal()
+
+ # Sequences and non-worker callbacks have to hold a strong ref to the
+ # thing being passed down.
+ forceOwningType = (descriptor.interface.isCallback() and
+ not descriptor.workers) or isMember
+
+ typeName = descriptor.nativeType
+ typePtr = typeName + "*"
+
+ # Compute a few things:
+ # - declType is the type we want to return as the first element of our
+ # tuple.
+ # - holderType is the type we want to return as the third element
+ # of our tuple.
+
+ # Set up some sensible defaults for these things insofar as we can.
+ holderType = None
+ if argIsPointer:
+ if forceOwningType:
+ declType = "nsRefPtr<" + typeName + ">"
+ else:
+ declType = typePtr
+ else:
+ if forceOwningType:
+ declType = "OwningNonNull<" + typeName + ">"
+ else:
+ declType = "NonNull<" + typeName + ">"
+
+ templateBody = ""
+ if descriptor.castable:
+ if descriptor.prefable:
+ raise TypeError("We don't support prefable castable object "
+ "arguments (like %s), because we don't know "
+ "how to handle them being preffed off" %
+ descriptor.interface.identifier.name)
+ if descriptor.interface.isConsequential():
+ raise TypeError("Consequential interface %s being used as an "
+ "argument but flagged as castable" %
+ descriptor.interface.identifier.name)
+ if failureCode is not None:
+ templateBody += str(CastableObjectUnwrapper(
+ descriptor,
+ "&${val}.toObject()",
+ "${declName}",
+ failureCode))
+ else:
+ templateBody += str(FailureFatalCastableObjectUnwrapper(
+ descriptor,
+ "&${val}.toObject()",
+ "${declName}"))
+ elif descriptor.interface.isCallback():
+ templateBody += str(CallbackObjectUnwrapper(
+ descriptor,
+ "&${val}.toObject()",
+ "${declName}",
+ codeOnFailure=failureCode))
+ elif descriptor.workers:
+ templateBody += "${declName} = &${val}.toObject();"
+ else:
+ # Either external, or new-binding non-castable. We always have a
+ # holder for these, because we don't actually know whether we have
+ # to addref when unwrapping or not. So we just pass an
+ # getter_AddRefs(nsRefPtr) to XPConnect and if we'll need a release
+ # it'll put a non-null pointer in there.
+ if forceOwningType:
+ # Don't return a holderType in this case; our declName
+ # will just own stuff.
+ templateBody += "nsRefPtr<" + typeName + "> ${holderName};\n"
+ else:
+ holderType = "nsRefPtr<" + typeName + ">"
+ templateBody += (
+ "jsval tmpVal = ${val};\n" +
+ typePtr + " tmp;\n"
+ "if (NS_FAILED(xpc_qsUnwrapArg<" + typeName + ">(cx, ${val}, &tmp, static_cast<" + typeName + "**>(getter_AddRefs(${holderName})), &tmpVal))) {\n")
+ templateBody += CGIndenter(onFailureBadType(failureCode,
+ descriptor.interface.identifier.name)).define()
+ templateBody += ("}\n"
+ "MOZ_ASSERT(tmp);\n")
+
+ if not isDefinitelyObject:
+ # Our tmpVal will go out of scope, so we can't rely on it
+ # for rooting
+ templateBody += (
+ "if (tmpVal != ${val} && !${holderName}) {\n"
+ " // We have to have a strong ref, because we got this off\n"
+ " // some random object that might get GCed\n"
+ " ${holderName} = tmp;\n"
+ "}\n")
+
+ # And store our tmp, before it goes out of scope.
+ templateBody += "${declName} = tmp;"
+
+ templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject,
+ type, "${declName} = NULL",
+ failureCode)
+
+ declType = CGGeneric(declType)
+ if holderType is not None:
+ holderType = CGGeneric(holderType)
+ return (templateBody, declType, holderType, isOptional)
+
+ if type.isSpiderMonkeyInterface():
+ assert not isEnforceRange and not isClamp
+ if isMember:
+ raise TypeError("Can't handle member arraybuffers or "
+ "arraybuffer views because making sure all the "
+ "objects are properly rooted is hard")
+ name = type.name
+ # By default, we use a Maybe<> to hold our typed array. And in the optional
+ # non-nullable case we want to pass Optional<TypedArray> to consumers, not
+ # Optional<NonNull<TypedArray> >, so jump though some hoops to do that.
+ holderType = "Maybe<%s>" % name
+ constructLoc = "${holderName}"
+ constructMethod = "construct"
+ constructInternal = "ref"
+ if type.nullable():
+ if isOptional:
+ declType = "const Optional<" + name + "*>"
+ else:
+ declType = name + "*"
+ else:
+ if isOptional:
+ declType = "const Optional<" + name + ">"
+ # We don't need a holder in this case
+ holderType = None
+ constructLoc = "(const_cast<Optional<" + name + ">& >(${declName}))"
+ constructMethod = "Construct"
+ constructInternal = "Value"
+ else:
+ declType = "NonNull<" + name + ">"
+ template = (
+ "%s.%s(cx, &${val}.toObject());\n"
+ "if (!%s.%s().inited()) {\n"
+ "%s" # No newline here because onFailureBadType() handles that
+ "}\n" %
+ (constructLoc, constructMethod, constructLoc, constructInternal,
+ CGIndenter(onFailureBadType(failureCode, type.name)).define()))
+ nullableTarget = ""
+ if type.nullable():
+ if isOptional:
+ mutableDecl = "(const_cast<Optional<" + name + "*>& >(${declName}))"
+ template += "%s.Construct();\n" % mutableDecl
+ nullableTarget = "%s.Value()" % mutableDecl
+ else:
+ nullableTarget = "${declName}"
+ template += "%s = ${holderName}.addr();" % nullableTarget
+ elif not isOptional:
+ template += "${declName} = ${holderName}.addr();"
+ template = wrapObjectTemplate(template, isDefinitelyObject, type,
+ "%s = NULL" % nullableTarget,
+ failureCode)
+
+ if holderType is not None:
+ holderType = CGGeneric(holderType)
+ # We handle all the optional stuff ourselves; no need for caller to do it.
+ return (template, CGGeneric(declType), holderType, False)
+
+ if type.isString():
+ assert not isEnforceRange and not isClamp
+
+ treatAs = {
+ "Default": "eStringify",
+ "EmptyString": "eEmpty",
+ "Null": "eNull"
+ }
+ if type.nullable():
+ # For nullable strings null becomes a null string.
+ treatNullAs = "Null"
+ # For nullable strings undefined becomes a null string unless
+ # specified otherwise.
+ if treatUndefinedAs == "Default":
+ treatUndefinedAs = "Null"
+ nullBehavior = treatAs[treatNullAs]
+ if treatUndefinedAs == "Missing":
+ raise TypeError("We don't support [TreatUndefinedAs=Missing]")
+ undefinedBehavior = treatAs[treatUndefinedAs]
+
+ def getConversionCode(varName):
+ conversionCode = (
+ "if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, %s)) {\n"
+ " return false;\n"
+ "}" % (nullBehavior, undefinedBehavior, varName))
+ if defaultValue is None:
+ return conversionCode
+
+ if isinstance(defaultValue, IDLNullValue):
+ assert(type.nullable())
+ return handleDefault(conversionCode,
+ "%s.SetNull()" % varName)
+ return handleDefault(
+ conversionCode,
+ ("static const PRUnichar data[] = { %s };\n"
+ "%s.SetData(data, ArrayLength(data) - 1)" %
+ (", ".join(["'" + char + "'" for char in defaultValue.value] + ["0"]),
+ varName)))
+
+ if isMember:
+ # We have to make a copy, because our jsval may well not
+ # live as long as our string needs to.
+ declType = CGGeneric("nsString")
+ return (
+ "{\n"
+ " FakeDependentString str;\n"
+ "%s\n"
+ " ${declName} = str;\n"
+ "}\n" % CGIndenter(CGGeneric(getConversionCode("str"))).define(),
+ declType, None, isOptional)
+
+ if isOptional:
+ declType = "Optional<nsAString>"
+ else:
+ declType = "NonNull<nsAString>"
+
+ return (
+ "%s\n"
+ "const_cast<%s&>(${declName}) = &${holderName};" %
+ (getConversionCode("${holderName}"), declType),
+ CGGeneric("const " + declType), CGGeneric("FakeDependentString"),
+ # No need to deal with Optional here; we have handled it already
+ False)
+
+ if type.isEnum():
+ assert not isEnforceRange and not isClamp
+
+ if type.nullable():
+ raise TypeError("We don't support nullable enumerated arguments "
+ "yet")
+ enum = type.inner.identifier.name
+ if invalidEnumValueFatal:
+ handleInvalidEnumValueCode = " MOZ_ASSERT(index >= 0);\n"
+ else:
+ handleInvalidEnumValueCode = (
+ " if (index < 0) {\n"
+ " return true;\n"
+ " }\n")
+
+ template = (
+ "{\n"
+ " bool ok;\n"
+ " int index = FindEnumStringIndex<%(invalidEnumValueFatal)s>(cx, ${val}, %(values)s, \"%(enumtype)s\", &ok);\n"
+ " if (!ok) {\n"
+ " return false;\n"
+ " }\n"
+ "%(handleInvalidEnumValueCode)s"
+ " ${declName} = static_cast<%(enumtype)s>(index);\n"
+ "}" % { "enumtype" : enum,
+ "values" : enum + "Values::strings",
+ "invalidEnumValueFatal" : toStringBool(invalidEnumValueFatal),
+ "handleInvalidEnumValueCode" : handleInvalidEnumValueCode })
+
+ if defaultValue is not None:
+ assert(defaultValue.type.tag() == IDLType.Tags.domstring)
+ template = handleDefault(template,
+ ("${declName} = %sValues::%s" %
+ (enum,
+ getEnumValueName(defaultValue.value))))
+ return (template, CGGeneric(enum), None, isOptional)
+
+ if type.isCallback():
+ assert not isEnforceRange and not isClamp
+
+ if isMember:
+ raise TypeError("Can't handle member callbacks; need to sort out "
+ "rooting issues")
+ # XXXbz we're going to assume that callback types are always
+ # nullable and always have [TreatNonCallableAsNull] for now.
+ haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())"
+ if defaultValue is not None:
+ assert(isinstance(defaultValue, IDLNullValue))
+ haveCallable = "${haveValue} && " + haveCallable
+ return (
+ "if (%s) {\n"
+ " ${declName} = &${val}.toObject();\n"
+ "} else {\n"
+ " ${declName} = NULL;\n"
+ "}" % haveCallable,
+ CGGeneric("JSObject*"), None, isOptional)
+
+ if type.isAny():
+ assert not isEnforceRange and not isClamp
+
+ if isMember:
+ raise TypeError("Can't handle member 'any'; need to sort out "
+ "rooting issues")
+ templateBody = "${declName} = ${val};"
+ templateBody = handleDefaultNull(templateBody,
+ "${declName} = JS::NullValue()")
+ return (templateBody, CGGeneric("JS::Value"), None, isOptional)
+
+ if type.isObject():
+ assert not isEnforceRange and not isClamp
+
+ if isMember:
+ raise TypeError("Can't handle member 'object'; need to sort out "
+ "rooting issues")
+ template = wrapObjectTemplate("${declName} = &${val}.toObject();",
+ isDefinitelyObject, type,
+ "${declName} = NULL",
+ failureCode)
+ if type.nullable():
+ declType = CGGeneric("JSObject*")
+ else:
+ declType = CGGeneric("NonNull<JSObject>")
+ return (template, declType, None, isOptional)
+
+ if type.isDictionary():
+ if failureCode is not None:
+ raise TypeError("Can't handle dictionaries when failureCode is not None")
+ # There are no nullable dictionaries
+ assert not type.nullable()
+ # All optional dictionaries always have default values, so we
+ # should be able to assume not isOptional here.
+ assert not isOptional
+
+ typeName = CGDictionary.makeDictionaryName(type.inner,
+ descriptorProvider.workers)
+ actualTypeName = typeName
+ selfRef = "${declName}"
+
+ declType = CGGeneric(actualTypeName)
+
+ # If we're a member of something else, the const
+ # will come from the Optional or our container.
+ if not isMember:
+ declType = CGWrapper(declType, pre="const ")
+ selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
+
+ # We do manual default value handling here, because we
+ # actually do want a jsval, and we only handle null anyway
+ if defaultValue is not None:
+ assert(isinstance(defaultValue, IDLNullValue))
+ val = "(${haveValue}) ? ${val} : JSVAL_NULL"
+ else:
+ val = "${val}"
+
+ template = ("if (!%s.Init(cx, %s)) {\n"
+ " return false;\n"
+ "}" % (selfRef, val))
+
+ return (template, declType, None, False)
+
+ if not type.isPrimitive():
+ raise TypeError("Need conversion for argument type '%s'" % str(type))
+
+ typeName = builtinNames[type.tag()]
+
+ conversionBehavior = "eDefault"
+ if isEnforceRange:
+ conversionBehavior = "eEnforceRange"
+ elif isClamp:
+ conversionBehavior = "eClamp"
+
+ if type.nullable():
+ dataLoc = "${declName}.SetValue()"
+ nullCondition = "${val}.isNullOrUndefined()"
+ if defaultValue is not None and isinstance(defaultValue, IDLNullValue):
+ nullCondition = "!(${haveValue}) || " + nullCondition
+ template = (
+ "if (%s) {\n"
+ " ${declName}.SetNull();\n"
+ "} else if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n"
+ " return false;\n"
+ "}" % (nullCondition, typeName, conversionBehavior, dataLoc))
+ declType = CGGeneric("Nullable<" + typeName + ">")
+ else:
+ assert(defaultValue is None or
+ not isinstance(defaultValue, IDLNullValue))
+ dataLoc = "${declName}"
+ template = (
+ "if (!ValueToPrimitive<%s, %s>(cx, ${val}, &%s)) {\n"
+ " return false;\n"
+ "}" % (typeName, conversionBehavior, dataLoc))
+ declType = CGGeneric(typeName)
+ if (defaultValue is not None and
+ # We already handled IDLNullValue, so just deal with the other ones
+ not isinstance(defaultValue, IDLNullValue)):
+ tag = defaultValue.type.tag()
+ if tag in numericTags:
+ defaultStr = defaultValue.value
+ else:
+ assert(tag == IDLType.Tags.bool)
+ defaultStr = toStringBool(defaultValue.value)
+ template = CGWrapper(CGIndenter(CGGeneric(template)),
+ pre="if (${haveValue}) {\n",
+ post=("\n"
+ "} else {\n"
+ " %s = %s;\n"
+ "}" % (dataLoc, defaultStr))).define()
+
+ return (template, declType, None, isOptional)
+
+def instantiateJSToNativeConversionTemplate(templateTuple, replacements,
+ argcAndIndex=None):
+ """
+ Take a tuple as returned by getJSToNativeConversionTemplate and a set of
+ replacements as required by the strings in such a tuple, and generate code
+ to convert into stack C++ types.
+
+ If argcAndIndex is not None it must be a dict that can be used to
+ replace ${argc} and ${index}, where ${index} is the index of this
+ argument (0-based) and ${argc} is the total number of arguments.
+ """
+ (templateBody, declType, holderType, dealWithOptional) = templateTuple
+
+ if dealWithOptional and argcAndIndex is None:
+ raise TypeError("Have to deal with optional things, but don't know how")
+ if argcAndIndex is not None and declType is None:
+ raise TypeError("Need to predeclare optional things, so they will be "
+ "outside the check for big enough arg count!");
+
+ result = CGList([], "\n")
+ # Make a copy of "replacements" since we may be about to start modifying it
+ replacements = dict(replacements)
+ originalHolderName = replacements["holderName"]
+ if holderType is not None:
+ if dealWithOptional:
+ replacements["holderName"] = (
+ "const_cast< %s & >(%s.Value())" %
+ (holderType.define(), originalHolderName))
+ mutableHolderType = CGWrapper(holderType, pre="Optional< ", post=" >")
+ holderType = CGWrapper(mutableHolderType, pre="const ")
+ result.append(
+ CGList([holderType, CGGeneric(" "),
+ CGGeneric(originalHolderName),
+ CGGeneric(";")]))
+
+ originalDeclName = replacements["declName"]
+ if declType is not None:
+ if dealWithOptional:
+ replacements["declName"] = (
+ "const_cast< %s & >(%s.Value())" %
+ (declType.define(), originalDeclName))
+ mutableDeclType = CGWrapper(declType, pre="Optional< ", post=" >")
+ declType = CGWrapper(mutableDeclType, pre="const ")
+ result.append(
+ CGList([declType, CGGeneric(" "),
+ CGGeneric(originalDeclName),
+ CGGeneric(";")]))
+
+ conversion = CGGeneric(
+ string.Template(templateBody).substitute(replacements)
+ )
+
+ if argcAndIndex is not None:
+ if dealWithOptional:
+ declConstruct = CGIndenter(
+ CGGeneric("const_cast< %s &>(%s).Construct();" %
+ (mutableDeclType.define(), originalDeclName)))
+ if holderType is not None:
+ holderConstruct = CGIndenter(
+ CGGeneric("const_cast< %s &>(%s).Construct();" %
+ (mutableHolderType.define(), originalHolderName)))
+ else:
+ holderConstruct = None
+ else:
+ declConstruct = None
+ holderConstruct = None
+
+ conversion = CGList(
+ [CGGeneric(
+ string.Template("if (${index} < ${argc}) {").substitute(
+ argcAndIndex
+ )),
+ declConstruct,
+ holderConstruct,
+ CGIndenter(conversion),
+ CGGeneric("}")],
+ "\n")
+
+ result.append(conversion)
+ # Add an empty CGGeneric to get an extra newline after the argument
+ # conversion.
+ result.append(CGGeneric(""))
+ return result;
+
+def convertConstIDLValueToJSVal(value):
+ if isinstance(value, IDLNullValue):
+ return "JSVAL_NULL"
+ tag = value.type.tag()
+ if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
+ IDLType.Tags.uint16, IDLType.Tags.int32]:
+ return "INT_TO_JSVAL(%s)" % (value.value)
+ if tag == IDLType.Tags.uint32:
+ return "UINT_TO_JSVAL(%s)" % (value.value)
+ if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
+ return "DOUBLE_TO_JSVAL(%s)" % (value.value)
+ if tag == IDLType.Tags.bool:
+ return "JSVAL_TRUE" if value.value else "JSVAL_FALSE"
+ if tag in [IDLType.Tags.float, IDLType.Tags.double]:
+ return "DOUBLE_TO_JSVAL(%s)" % (value.value)
+ raise TypeError("Const value of unhandled type: " + value.type)
+
+class CGArgumentConverter(CGThing):
+ """
+ A class that takes an IDL argument object, its index in the
+ argument list, and the argv and argc strings and generates code to
+ unwrap the argument to the right native type.
+ """
+ def __init__(self, argument, index, argv, argc, descriptorProvider,
+ invalidEnumValueFatal=True):
+ CGThing.__init__(self)
+ self.argument = argument
+ if argument.variadic:
+ raise TypeError("We don't support variadic arguments yet " +
+ str(argument.location))
+ assert(not argument.defaultValue or argument.optional)
+
+ replacer = {
+ "index" : index,
+ "argc" : argc,
+ "argv" : argv
+ }
+ self.replacementVariables = {
+ "declName" : "arg%d" % index,
+ "holderName" : ("arg%d" % index) + "_holder"
+ }
+ self.replacementVariables["val"] = string.Template(
+ "${argv}[${index}]"
+ ).substitute(replacer)
+ self.replacementVariables["valPtr"] = (
+ "&" + self.replacementVariables["val"])
+ if argument.defaultValue:
+ self.replacementVariables["haveValue"] = string.Template(
+ "${index} < ${argc}").substitute(replacer)
+ self.descriptorProvider = descriptorProvider
+ if self.argument.optional and not self.argument.defaultValue:
+ self.argcAndIndex = replacer
+ else:
+ self.argcAndIndex = None
+ self.invalidEnumValueFatal = invalidEnumValueFatal
+
+ def define(self):
+ return instantiateJSToNativeConversionTemplate(
+ getJSToNativeConversionTemplate(self.argument.type,
+ self.descriptorProvider,
+ isOptional=(self.argcAndIndex is not None),
+ invalidEnumValueFatal=self.invalidEnumValueFatal,
+ defaultValue=self.argument.defaultValue,
+ treatNullAs=self.argument.treatNullAs,
+ treatUndefinedAs=self.argument.treatUndefinedAs,
+ isEnforceRange=self.argument.enforceRange,
+ isClamp=self.argument.clamp),
+ self.replacementVariables,
+ self.argcAndIndex).define()
+
+def getWrapTemplateForType(type, descriptorProvider, result, successCode,
+ isCreator):
+ """
+ Reflect a C++ value stored in "result", of IDL type "type" into JS. The
+ "successCode" is the code to run once we have successfully done the
+ conversion. The resulting string should be used with string.Template, it
+ needs the following keys when substituting: jsvalPtr/jsvalRef/obj.
+
+ Returns (templateString, infallibility of conversion template)
+ """
+ haveSuccessCode = successCode is not None
+ if not haveSuccessCode:
+ successCode = "return true;"
+
+ def setValue(value, callWrapValue=False):
+ """
+ Returns the code to set the jsval to value. If "callWrapValue" is true
+ JS_WrapValue will be called on the jsval.
+ """
+ if not callWrapValue:
+ tail = successCode
+ elif haveSuccessCode:
+ tail = ("if (!JS_WrapValue(cx, ${jsvalPtr})) {\n" +
+ " return false;\n" +
+ "}\n" +
+ successCode)
+ else:
+ tail = "return JS_WrapValue(cx, ${jsvalPtr});"
+ return ("${jsvalRef} = %s;\n" +
+ tail) % (value)
+
+ def wrapAndSetPtr(wrapCall, failureCode=None):
+ """
+ Returns the code to set the jsval by calling "wrapCall". "failureCode"
+ is the code to run if calling "wrapCall" fails
+ """
+ if failureCode is None:
+ if not haveSuccessCode:
+ return "return " + wrapCall + ";"
+ failureCode = "return false;"
+ str = ("if (!%s) {\n" +
+ CGIndenter(CGGeneric(failureCode)).define() + "\n" +
+ "}\n" +
+ successCode) % (wrapCall)
+ return str
+
+ if type is None or type.isVoid():
+ return (setValue("JSVAL_VOID"), True)
+
+ if type.isArray():
+ raise TypeError("Can't handle array return values yet")
+
+ if type.isSequence():
+ if type.nullable():
+ # Nullable sequences are Nullable< nsTArray<T> >
+ (recTemplate, recInfall) = getWrapTemplateForType(type.inner, descriptorProvider,
+ "%s.Value()" % result, successCode,
+ isCreator)
+ return ("""
+if (%s.IsNull()) {
+%s
+}
+%s""" % (result, CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define(), recTemplate), recInfall)
+
+ # Now do non-nullable sequences. We use setting the element
+ # in the array as our succcess code because when we succeed in
+ # wrapping that's what we should do.
+ innerTemplate = wrapForType(
+ type.inner, descriptorProvider,
+ {
+ 'result' : "%s[i]" % result,
+ 'successCode': ("if (!JS_DefineElement(cx, returnArray, i, tmp,\n"
+ " NULL, NULL, JSPROP_ENUMERATE)) {\n"
+ " return false;\n"
+ "}"),
+ 'jsvalRef': "tmp",
+ 'jsvalPtr': "&tmp",
+ 'isCreator': isCreator
+ }
+ )
+ innerTemplate = CGIndenter(CGGeneric(innerTemplate)).define()
+ return (("""
+uint32_t length = %s.Length();
+JSObject *returnArray = JS_NewArrayObject(cx, length, NULL);
+if (!returnArray) {
+ return false;
+}
+jsval tmp;
+for (uint32_t i = 0; i < length; ++i) {
+%s
+}\n""" % (result, innerTemplate)) + setValue("JS::ObjectValue(*returnArray)"), False)
+
+ if type.isGeckoInterface():
+ descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name)
+ if type.nullable():
+ wrappingCode = ("if (!%s) {\n" % (result) +
+ CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" +
+ "}\n")
+ else:
+ wrappingCode = ""
+ if (not descriptor.interface.isExternal() and
+ not descriptor.interface.isCallback()):
+ if descriptor.wrapperCache:
+ wrapMethod = "WrapNewBindingObject"
+ else:
+ if not isCreator:
+ raise MethodNotCreatorError(descriptor.interface.identifier.name)
+ wrapMethod = "WrapNewBindingNonWrapperCachedObject"
+ wrap = "%s(cx, ${obj}, %s, ${jsvalPtr})" % (wrapMethod, result)
+ # We don't support prefable stuff in workers.
+ assert(not descriptor.prefable or not descriptor.workers)
+ if not descriptor.prefable:
+ # Non-prefable bindings can only fail to wrap as a new-binding object
+ # if they already threw an exception. Same thing for
+ # non-prefable bindings.
+ failed = ("MOZ_ASSERT(JS_IsExceptionPending(cx));\n" +
+ "return false;")
+ else:
+ if descriptor.notflattened:
+ raise TypeError("%s is prefable but not flattened; "
+ "fallback won't work correctly" %
+ descriptor.interface.identifier.name)
+ # Try old-style wrapping for bindings which might be preffed off.
+ failed = wrapAndSetPtr("HandleNewBindingWrappingFailure(cx, ${obj}, %s, ${jsvalPtr})" % result)
+ wrappingCode += wrapAndSetPtr(wrap, failed)
+ else:
+ if descriptor.notflattened:
+ getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
+ else:
+ getIID = ""
+ wrap = "WrapObject(cx, ${obj}, %s, %s${jsvalPtr})" % (result, getIID)
+ wrappingCode += wrapAndSetPtr(wrap)
+ return (wrappingCode, False)
+
+ if type.isString():
+ if type.nullable():
+ return (wrapAndSetPtr("xpc::StringToJsval(cx, %s, ${jsvalPtr})" % result), False)
+ else:
+ return (wrapAndSetPtr("xpc::NonVoidStringToJsval(cx, %s, ${jsvalPtr})" % result), False)
+
+ if type.isEnum():
+ if type.nullable():
+ raise TypeError("We don't support nullable enumerated return types "
+ "yet")
+ return ("""MOZ_ASSERT(uint32_t(%(result)s) < ArrayLength(%(strings)s));
+JSString* %(resultStr)s = JS_NewStringCopyN(cx, %(strings)s[uint32_t(%(result)s)].value, %(strings)s[uint32_t(%(result)s)].length);
+if (!%(resultStr)s) {
+ return false;
+}
+""" % { "result" : result,
+ "resultStr" : result + "_str",
+ "strings" : type.inner.identifier.name + "Values::strings" } +
+ setValue("JS::StringValue(%s_str)" % result), False)
+
+ if type.isCallback():
+ assert not type.isInterface()
+ # XXXbz we're going to assume that callback types are always
+ # nullable and always have [TreatNonCallableAsNull] for now.
+ # See comments in WrapNewBindingObject explaining why we need
+ # to wrap here.
+ # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
+ return (setValue("JS::ObjectOrNullValue(%s)" % result, True), False)
+
+ if type.tag() == IDLType.Tags.any:
+ # See comments in WrapNewBindingObject explaining why we need
+ # to wrap here.
+ # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
+ return (setValue(result, True), False)
+
+ if type.isObject() or type.isSpiderMonkeyInterface():
+ # See comments in WrapNewBindingObject explaining why we need
+ # to wrap here.
+ if type.nullable():
+ toValue = "JS::ObjectOrNullValue(%s)"
+ else:
+ toValue = "JS::ObjectValue(*%s)"
+ # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
+ return (setValue(toValue % result, True), False)
+
+ if not type.isPrimitive():
+ raise TypeError("Need to learn to wrap %s" % type)
+
+ if type.nullable():
+ (recTemplate, recInfal) = getWrapTemplateForType(type.inner, descriptorProvider,
+ "%s.Value()" % result, successCode,
+ isCreator)
+ return ("if (%s.IsNull()) {\n" % result +
+ CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" +
+ "}\n" + recTemplate, recInfal)
+
+ tag = type.tag()
+
+ if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
+ IDLType.Tags.uint16, IDLType.Tags.int32]:
+ return (setValue("INT_TO_JSVAL(int32_t(%s))" % result), True)
+
+ elif tag in [IDLType.Tags.int64, IDLType.Tags.uint64, IDLType.Tags.float,
+ IDLType.Tags.double]:
+ # XXXbz will cast to double do the "even significand" thing that webidl
+ # calls for for 64-bit ints? Do we care?
+ return (setValue("JS_NumberValue(double(%s))" % result), True)
+
+ elif tag == IDLType.Tags.uint32:
+ return (setValue("UINT_TO_JSVAL(%s)" % result), True)
+
+ elif tag == IDLType.Tags.bool:
+ return (setValue("BOOLEAN_TO_JSVAL(%s)" % result), True)
+
+ else:
+ raise TypeError("Need to learn to wrap primitive: %s" % type)
+
+def wrapForType(type, descriptorProvider, templateValues):
+ """
+ Reflect a C++ value of IDL type "type" into JS. TemplateValues is a dict
+ that should contain:
+
+ * 'jsvalRef': a C++ reference to the jsval in which to store the result of
+ the conversion
+ * 'jsvalPtr': a C++ pointer to the jsval in which to store the result of
+ the conversion
+ * 'obj' (optional): the name of the variable that contains the JSObject to
+ use as a scope when wrapping, if not supplied 'obj'
+ will be used as the name
+ * 'result' (optional): the name of the variable in which the C++ value is
+ stored, if not supplied 'result' will be used as
+ the name
+ * 'successCode' (optional): the code to run once we have successfully done
+ the conversion, if not supplied 'return true;'
+ will be used as the code
+ * 'isCreator' (optional): If true, we're wrapping for the return value of
+ a [Creator] method. Assumed false if not set.
+ """
+ wrap = getWrapTemplateForType(type, descriptorProvider,
+ templateValues.get('result', 'result'),
+ templateValues.get('successCode', None),
+ templateValues.get('isCreator', False))[0]
+
+ defaultValues = {'obj': 'obj'}
+ return string.Template(wrap).substitute(defaultValues, **templateValues)
+
+def infallibleForMember(member, type, descriptorProvider):
+ """
+ Determine the fallibility of changing a C++ value of IDL type "type" into
+ JS for the given attribute. Apart from isCreator, all the defaults are used,
+ since the fallbility does not change based on the boolean values,
+ and the template will be discarded.
+
+ CURRENT ASSUMPTIONS:
+ We assume that successCode for wrapping up return values cannot contain
+ failure conditions.
+ """
+ return getWrapTemplateForType(type, descriptorProvider, 'result', None,\
+ memberIsCreator(member))[1]
+
+def typeNeedsCx(type, retVal=False):
+ if type is None:
+ return False
+ if type.nullable():
+ type = type.inner
+ if type.isSequence() or type.isArray():
+ type = type.inner
+ if type.isUnion():
+ return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes)
+ if retVal and type.isSpiderMonkeyInterface():
+ return True
+ return type.isCallback() or type.isAny() or type.isObject()
+
+# Returns a tuple consisting of a CGThing containing the type of the return
+# value, or None if there is no need for a return value, and a boolean signaling
+# whether the return value is passed in an out parameter.
+def getRetvalDeclarationForType(returnType, descriptorProvider,
+ resultAlreadyAddRefed):
+ if returnType is None or returnType.isVoid():
+ # Nothing to declare
+ return None, False
+ if returnType.isPrimitive() and returnType.tag() in builtinNames:
+ result = CGGeneric(builtinNames[returnType.tag()])
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Nullable<", post=">")
+ return result, False
+ if returnType.isString():
+ return CGGeneric("nsString"), True
+ if returnType.isEnum():
+ if returnType.nullable():
+ raise TypeError("We don't support nullable enum return values")
+ return CGGeneric(returnType.inner.identifier.name), False
+ if returnType.isGeckoInterface():
+ result = CGGeneric(descriptorProvider.getDescriptor(
+ returnType.unroll().inner.identifier.name).nativeType)
+ if resultAlreadyAddRefed:
+ result = CGWrapper(result, pre="nsRefPtr<", post=">")
+ else:
+ result = CGWrapper(result, post="*")
+ return result, False
+ if returnType.isCallback():
+ # XXXbz we're going to assume that callback types are always
+ # nullable for now.
+ return CGGeneric("JSObject*"), False
+ if returnType.isAny():
+ return CGGeneric("JS::Value"), False
+ if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ return CGGeneric("JSObject*"), False
+ if returnType.isSequence():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ # If our result is already addrefed, use the right type in the
+ # sequence argument here.
+ (result, _) = getRetvalDeclarationForType(returnType.inner,
+ descriptorProvider,
+ resultAlreadyAddRefed)
+ result = CGWrapper(result, pre="nsTArray< ", post=" >")
+ if nullable:
+ result = CGWrapper(result, pre="Nullable< ", post=" >")
+ return result, True
+ raise TypeError("Don't know how to declare return value for %s" %
+ returnType)
+
+def isResultAlreadyAddRefed(descriptor, extendedAttributes):
+ # Default to already_AddRefed on the main thread, raw pointer in workers
+ return not descriptor.workers and not 'resultNotAddRefed' in extendedAttributes
+
+class CGCallGenerator(CGThing):
+ """
+ A class to generate an actual call to a C++ object. Assumes that the C++
+ object is stored in a variable whose name is given by the |object| argument.
+
+ errorReport should be a CGThing for an error report or None if no
+ error reporting is needed.
+ """
+ def __init__(self, errorReport, arguments, argsPre, returnType,
+ extendedAttributes, descriptorProvider, nativeMethodName,
+ static, object="self", declareResult=True):
+ CGThing.__init__(self)
+
+ assert errorReport is None or isinstance(errorReport, CGThing)
+
+ isFallible = errorReport is not None
+
+ resultAlreadyAddRefed = isResultAlreadyAddRefed(descriptorProvider,
+ extendedAttributes)
+ (result, resultOutParam) = getRetvalDeclarationForType(returnType,
+ descriptorProvider,
+ resultAlreadyAddRefed)
+
+ args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
+ for (a, name) in arguments:
+ # This is a workaround for a bug in Apple's clang.
+ if a.type.isObject() and not a.type.nullable() and not a.optional:
+ name = "(JSObject&)" + name
+ args.append(CGGeneric(name))
+
+ # Return values that go in outparams go here
+ if resultOutParam:
+ args.append(CGGeneric("result"))
+ if isFallible:
+ args.append(CGGeneric("rv"))
+
+ needsCx = (typeNeedsCx(returnType, True) or
+ any(typeNeedsCx(a.type) for (a, _) in arguments) or
+ 'implicitJSContext' in extendedAttributes)
+
+ if not "cx" in argsPre and needsCx:
+ args.prepend(CGGeneric("cx"))
+
+ # Build up our actual call
+ self.cgRoot = CGList([], "\n")
+
+ call = CGGeneric(nativeMethodName)
+ if static:
+ call = CGWrapper(call, pre="%s::" % descriptorProvider.nativeType)
+ else:
+ call = CGWrapper(call, pre="%s->" % object)
+ call = CGList([call, CGWrapper(args, pre="(", post=");")])
+ if result is not None:
+ if declareResult:
+ result = CGWrapper(result, post=" result;")
+ self.cgRoot.prepend(result)
+ if not resultOutParam:
+ call = CGWrapper(call, pre="result = ")
+
+ call = CGWrapper(call)
+ self.cgRoot.append(call)
+
+ if isFallible:
+ self.cgRoot.prepend(CGGeneric("ErrorResult rv;"))
+ self.cgRoot.append(CGGeneric("if (rv.Failed()) {"))
+ self.cgRoot.append(CGIndenter(errorReport))
+ self.cgRoot.append(CGGeneric("}"))
+
+ def define(self):
+ return self.cgRoot.define()
+
+class MethodNotCreatorError(Exception):
+ def __init__(self, typename):
+ self.typename = typename
+
+class CGPerSignatureCall(CGThing):
+ """
+ This class handles the guts of generating code for a particular
+ call signature. A call signature consists of four things:
+
+ 1) A return type, which can be None to indicate that there is no
+ actual return value (e.g. this is an attribute setter) or an
+ IDLType if there's an IDL type involved (including |void|).
+ 2) An argument list, which is allowed to be empty.
+ 3) A name of a native method to call.
+ 4) Whether or not this method is static.
+
+ We also need to know whether this is a method or a getter/setter
+ to do error reporting correctly.
+
+ The idlNode parameter can be either a method or an attr. We can query
+ |idlNode.identifier| in both cases, so we can be agnostic between the two.
+ """
+ # XXXbz For now each entry in the argument list is either an
+ # IDLArgument or a FakeArgument, but longer-term we may want to
+ # have ways of flagging things like JSContext* or optional_argc in
+ # there.
+
+ def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
+ descriptor, idlNode, argConversionStartsAt=0,
+ getter=False, setter=False):
+ CGThing.__init__(self)
+ self.returnType = returnType
+ self.descriptor = descriptor
+ self.idlNode = idlNode
+ self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
+ getter=getter,
+ setter=setter)
+ self.argsPre = argsPre
+ self.arguments = arguments
+ self.argCount = len(arguments)
+ if self.argCount > argConversionStartsAt:
+ # Insert our argv in there
+ cgThings = [CGGeneric(self.getArgvDecl())]
+ else:
+ cgThings = []
+ cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
+ self.getArgc(), self.descriptor,
+ invalidEnumValueFatal=not setter) for
+ i in range(argConversionStartsAt, self.argCount)])
+
+ cgThings.append(CGCallGenerator(
+ self.getErrorReport() if self.isFallible() else None,
+ self.getArguments(), self.argsPre, returnType,
+ self.extendedAttributes, descriptor, nativeMethodName,
+ static))
+ self.cgRoot = CGList(cgThings, "\n")
+
+ def getArgv(self):
+ return "argv" if self.argCount > 0 else ""
+ def getArgvDecl(self):
+ return "\nJS::Value* argv = JS_ARGV(cx, vp);\n"
+ def getArgc(self):
+ return "argc"
+ def getArguments(self):
+ return [(a, "arg" + str(i)) for (i, a) in enumerate(self.arguments)]
+
+ def isFallible(self):
+ return not 'infallible' in self.extendedAttributes
+
+ def wrap_return_value(self):
+ isCreator = memberIsCreator(self.idlNode)
+ if isCreator:
+ # We better be returning addrefed things!
+ assert(isResultAlreadyAddRefed(self.descriptor,
+ self.extendedAttributes) or
+ # Workers use raw pointers for new-object return
+ # values or something
+ self.descriptor.workers)
+
+ resultTemplateValues = { 'jsvalRef': '*vp', 'jsvalPtr': 'vp',
+ 'isCreator': isCreator}
+ try:
+ return wrapForType(self.returnType, self.descriptor,
+ resultTemplateValues)
+ except MethodNotCreatorError, err:
+ assert not isCreator
+ raise TypeError("%s being returned from non-creator method or property %s.%s" %
+ (err.typename,
+ self.descriptor.interface.identifier.name,
+ self.idlNode.identifier.name))
+
+ def getErrorReport(self):
+ return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");'
+ % (toStringBool(not self.descriptor.workers),
+ self.descriptor.interface.identifier.name,
+ self.idlNode.identifier.name))
+
+ def define(self):
+ return (self.cgRoot.define() + "\n" + self.wrap_return_value())
+
+class CGSwitch(CGList):
+ """
+ A class to generate code for a switch statement.
+
+ Takes three constructor arguments: an expression, a list of cases,
+ and an optional default.
+
+ Each case is a CGCase. The default is a CGThing for the body of
+ the default case, if any.
+ """
+ def __init__(self, expression, cases, default=None):
+ CGList.__init__(self, [CGIndenter(c) for c in cases], "\n")
+ self.prepend(CGWrapper(CGGeneric(expression),
+ pre="switch (", post=") {"));
+ if default is not None:
+ self.append(
+ CGIndenter(
+ CGWrapper(
+ CGIndenter(default),
+ pre="default: {\n",
+ post="\n break;\n}"
+ )
+ )
+ )
+
+ self.append(CGGeneric("}"))
+
+class CGCase(CGList):
+ """
+ A class to generate code for a case statement.
+
+ Takes three constructor arguments: an expression, a CGThing for
+ the body (allowed to be None if there is no body), and an optional
+ argument (defaulting to False) for whether to fall through.
+ """
+ def __init__(self, expression, body, fallThrough=False):
+ CGList.__init__(self, [], "\n")
+ self.append(CGWrapper(CGGeneric(expression), pre="case ", post=": {"))
+ bodyList = CGList([body], "\n")
+ if fallThrough:
+ bodyList.append(CGGeneric("/* Fall through */"))
+ else:
+ bodyList.append(CGGeneric("break;"))
+ self.append(CGIndenter(bodyList));
+ self.append(CGGeneric("}"))
+
+class CGMethodCall(CGThing):
+ """
+ A class to generate selection of a method signature from a set of
+ signatures and generation of a call to that signature.
+ """
+ def __init__(self, argsPre, nativeMethodName, static, descriptor, method):
+ CGThing.__init__(self)
+
+ methodName = '"%s.%s"' % (descriptor.interface.identifier.name, method.identifier.name)
+
+ def requiredArgCount(signature):
+ arguments = signature[1]
+ if len(arguments) == 0:
+ return 0
+ requiredArgs = len(arguments)
+ while requiredArgs and arguments[requiredArgs-1].optional:
+ requiredArgs -= 1
+ return requiredArgs
+
+ def getPerSignatureCall(signature, argConversionStartsAt=0):
+ return CGPerSignatureCall(signature[0], argsPre, signature[1],
+ nativeMethodName, static, descriptor,
+ method, argConversionStartsAt)
+
+
+ signatures = method.signatures()
+ if len(signatures) == 1:
+ # Special case: we can just do a per-signature method call
+ # here for our one signature and not worry about switching
+ # on anything.
+ signature = signatures[0]
+ self.cgRoot = CGList([ CGIndenter(getPerSignatureCall(signature)) ])
+ requiredArgs = requiredArgCount(signature)
+
+
+ if requiredArgs > 0:
+ code = (
+ "if (argc < %d) {\n"
+ " return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, %s);\n"
+ "}" % (requiredArgs, methodName))
+ self.cgRoot.prepend(
+ CGWrapper(CGIndenter(CGGeneric(code)), pre="\n", post="\n"))
+ return
+
+ # Need to find the right overload
+ maxArgCount = method.maxArgCount
+ allowedArgCounts = method.allowedArgCounts
+
+ argCountCases = []
+ for argCount in allowedArgCounts:
+ possibleSignatures = method.signaturesForArgCount(argCount)
+ if len(possibleSignatures) == 1:
+ # easy case!
+ signature = possibleSignatures[0]
+
+ # (possibly) important optimization: if signature[1] has >
+ # argCount arguments and signature[1][argCount] is optional and
+ # there is only one signature for argCount+1, then the
+ # signature for argCount+1 is just ourselves and we can fall
+ # through.
+ if (len(signature[1]) > argCount and
+ signature[1][argCount].optional and
+ (argCount+1) in allowedArgCounts and
+ len(method.signaturesForArgCount(argCount+1)) == 1):
+ argCountCases.append(
+ CGCase(str(argCount), None, True))
+ else:
+ argCountCases.append(
+ CGCase(str(argCount), getPerSignatureCall(signature)))
+ continue
+
+ distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
+
+ # We can't handle unions at the distinguishing index.
+ for (returnType, args) in possibleSignatures:
+ if args[distinguishingIndex].type.isUnion():
+ raise TypeError("No support for unions as distinguishing "
+ "arguments yet: %s",
+ args[distinguishingIndex].location)
+
+ # Convert all our arguments up to the distinguishing index.
+ # Doesn't matter which of the possible signatures we use, since
+ # they all have the same types up to that point; just use
+ # possibleSignatures[0]
+ caseBody = [CGGeneric("JS::Value* argv_start = JS_ARGV(cx, vp);")]
+ caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i],
+ i, "argv_start", "argc",
+ descriptor) for i in
+ range(0, distinguishingIndex) ])
+
+ # Select the right overload from our set.
+ distinguishingArg = "argv_start[%d]" % distinguishingIndex
+
+ def pickFirstSignature(condition, filterLambda):
+ sigs = filter(filterLambda, possibleSignatures)
+ assert len(sigs) < 2
+ if len(sigs) > 0:
+ if condition is None:
+ caseBody.append(
+ getPerSignatureCall(sigs[0], distinguishingIndex))
+ else:
+ caseBody.append(CGGeneric("if (" + condition + ") {"))
+ caseBody.append(CGIndenter(
+ getPerSignatureCall(sigs[0], distinguishingIndex)))
+ caseBody.append(CGGeneric("}"))
+ return True
+ return False
+
+ # First check for null or undefined
+ pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg,
+ lambda s: (s[1][distinguishingIndex].type.nullable() or
+ s[1][distinguishingIndex].type.isDictionary()))
+
+ # Now check for distinguishingArg being an object that implements a
+ # non-callback interface. That includes typed arrays and
+ # arraybuffers.
+ interfacesSigs = [
+ s for s in possibleSignatures
+ if (s[1][distinguishingIndex].type.isObject() or
+ s[1][distinguishingIndex].type.isNonCallbackInterface()) ]
+ # There might be more than one of these; we need to check
+ # which ones we unwrap to.
+
+ if len(interfacesSigs) > 0:
+ # The spec says that we should check for "platform objects
+ # implementing an interface", but it's enough to guard on these
+ # being an object. The code for unwrapping non-callback
+ # interfaces and typed arrays will just bail out and move on to
+ # the next overload if the object fails to unwrap correctly. We
+ # could even not do the isObject() check up front here, but in
+ # cases where we have multiple object overloads it makes sense
+ # to do it only once instead of for each overload. That will
+ # also allow the unwrapping test to skip having to do codegen
+ # for the null-or-undefined case, which we already handled
+ # above.
+ caseBody.append(CGGeneric("if (%s.isObject()) {" %
+ (distinguishingArg)))
+ for sig in interfacesSigs:
+ caseBody.append(CGIndenter(CGGeneric("do {")));
+ type = sig[1][distinguishingIndex].type
+
+ # The argument at index distinguishingIndex can't possibly
+ # be unset here, because we've already checked that argc is
+ # large enough that we can examine this argument.
+ testCode = instantiateJSToNativeConversionTemplate(
+ getJSToNativeConversionTemplate(type, descriptor,
+ failureCode="break;",
+ isDefinitelyObject=True),
+ {
+ "declName" : "arg%d" % distinguishingIndex,
+ "holderName" : ("arg%d" % distinguishingIndex) + "_holder",
+ "val" : distinguishingArg
+ })
+
+ # Indent by 4, since we need to indent further than our "do" statement
+ caseBody.append(CGIndenter(testCode, 4));
+ # If we got this far, we know we unwrapped to the right
+ # interface, so just do the call. Start conversion with
+ # distinguishingIndex + 1, since we already converted
+ # distinguishingIndex.
+ caseBody.append(CGIndenter(
+ getPerSignatureCall(sig, distinguishingIndex + 1), 4))
+ caseBody.append(CGIndenter(CGGeneric("} while (0);")))
+
+ caseBody.append(CGGeneric("}"))
+
+ # XXXbz Now we're supposed to check for distinguishingArg being
+ # an array or a platform object that supports indexed
+ # properties... skip that last for now. It's a bit of a pain.
+ pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s:
+ (s[1][distinguishingIndex].type.isArray() or
+ s[1][distinguishingIndex].type.isSequence() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # Check for Date objects
+ # XXXbz Do we need to worry about security wrappers around the Date?
+ pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s: (s[1][distinguishingIndex].type.isDate() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # Check for vanilla JS objects
+ # XXXbz Do we need to worry about security wrappers?
+ pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s: (s[1][distinguishingIndex].type.isCallback() or
+ s[1][distinguishingIndex].type.isCallbackInterface() or
+ s[1][distinguishingIndex].type.isDictionary() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # The remaining cases are mutually exclusive. The
+ # pickFirstSignature calls are what change caseBody
+ # Check for strings or enums
+ if pickFirstSignature(None,
+ lambda s: (s[1][distinguishingIndex].type.isString() or
+ s[1][distinguishingIndex].type.isEnum())):
+ pass
+ # Check for primitives
+ elif pickFirstSignature(None,
+ lambda s: s[1][distinguishingIndex].type.isPrimitive()):
+ pass
+ # Check for "any"
+ elif pickFirstSignature(None,
+ lambda s: s[1][distinguishingIndex].type.isAny()):
+ pass
+ else:
+ # Just throw; we have no idea what we're supposed to
+ # do with this.
+ caseBody.append(CGGeneric("return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);" %
+ toStringBool(not descriptor.workers)))
+
+ argCountCases.append(CGCase(str(argCount),
+ CGList(caseBody, "\n")))
+
+ overloadCGThings = []
+ overloadCGThings.append(
+ CGGeneric("unsigned argcount = NS_MIN(argc, %du);" %
+ maxArgCount))
+ overloadCGThings.append(
+ CGSwitch("argcount",
+ argCountCases,
+ CGGeneric("return ThrowErrorMessage(cx, MSG_MISSING_ARGUMENTS, %s);\n" % methodName)))
+ overloadCGThings.append(
+ CGGeneric('MOZ_NOT_REACHED("We have an always-returning default case");\n'
+ 'return false;'))
+ self.cgRoot = CGWrapper(CGIndenter(CGList(overloadCGThings, "\n")),
+ pre="\n")
+
+ def define(self):
+ return self.cgRoot.define()
+
+class CGGetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object getter call for a particular IDL
+ getter.
+ """
+ def __init__(self, returnType, nativeMethodName, descriptor, attr):
+ CGPerSignatureCall.__init__(self, returnType, [], [],
+ nativeMethodName, False, descriptor,
+ attr, getter=True)
+
+class FakeArgument():
+ """
+ A class that quacks like an IDLArgument. This is used to make
+ setters look like method calls or for special operations.
+ """
+ def __init__(self, type, interfaceMember):
+ self.type = type
+ self.optional = False
+ self.variadic = False
+ self.defaultValue = None
+ self.treatNullAs = interfaceMember.treatNullAs
+ self.treatUndefinedAs = interfaceMember.treatUndefinedAs
+ self.enforceRange = False
+ self.clamp = False
+
+class CGSetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object setter call for a particular IDL
+ setter.
+ """
+ def __init__(self, argType, nativeMethodName, descriptor, attr):
+ CGPerSignatureCall.__init__(self, None, [],
+ [FakeArgument(argType, attr)],
+ nativeMethodName, False, descriptor, attr,
+ setter=True)
+ def wrap_return_value(self):
+ # We have no return value
+ return "\nreturn true;"
+ def getArgc(self):
+ return "1"
+ def getArgvDecl(self):
+ # We just get our stuff from our last arg no matter what
+ return ""
+
+class FakeCastableDescriptor():
+ def __init__(self, descriptor):
+ self.castable = True
+ self.workers = descriptor.workers
+ self.nativeType = descriptor.nativeType
+ self.name = descriptor.name
+ self.hasXPConnectImpls = descriptor.hasXPConnectImpls
+
+class CGAbstractBindingMethod(CGAbstractStaticMethod):
+ """
+ Common class to generate the JSNatives for all our methods, getters, and
+ setters. This will generate the function declaration and unwrap the
+ |this| object. Subclasses are expected to override the generate_code
+ function to do the rest of the work. This function should return a
+ CGThing which is already properly indented.
+ """
+ def __init__(self, descriptor, name, args, unwrapFailureCode=None):
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "JSBool", args)
+
+ if unwrapFailureCode is None:
+ self.unwrapFailureCode = ("return Throw<%s>(cx, rv);" %
+ toStringBool(not descriptor.workers))
+ else:
+ self.unwrapFailureCode = unwrapFailureCode
+
+ def definition_body(self):
+ # Our descriptor might claim that we're not castable, simply because
+ # we're someone's consequential interface. But for this-unwrapping, we
+ # know that we're the real deal. So fake a descriptor here for
+ # consumption by FailureFatalCastableObjectUnwrapper.
+ unwrapThis = CGIndenter(CGGeneric(
+ str(CastableObjectUnwrapper(
+ FakeCastableDescriptor(self.descriptor),
+ "obj", "self", self.unwrapFailureCode))))
+ return CGList([ self.getThis(), unwrapThis,
+ self.generate_code() ], "\n").define()
+
+ def getThis(self):
+ return CGIndenter(
+ CGGeneric("js::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));\n"
+ "if (!obj) {\n"
+ " return false;\n"
+ "}\n"
+ "\n"
+ "%s* self;" % self.descriptor.nativeType))
+
+ def generate_code(self):
+ assert(False) # Override me
+
+def MakeNativeName(name):
+ return name[0].upper() + name[1:]
+
+class CGGenericMethod(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL method..
+ """
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
+ Argument('JS::Value*', 'vp')]
+ CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args)
+
+ def generate_code(self):
+ return CGIndenter(CGGeneric(
+ "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "JSJitMethodOp method = (JSJitMethodOp)info->op;\n"
+ "return method(cx, obj, self, argc, vp);"))
+
+class CGSpecializedMethod(CGAbstractStaticMethod):
+ """
+ A class for generating the C++ code for a specialized method that the JIT
+ can call with lower overhead.
+ """
+ def __init__(self, descriptor, method):
+ self.method = method
+ name = method.identifier.name
+ args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
+ Argument('%s*' % descriptor.nativeType, 'self'),
+ Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args)
+
+ def definition_body(self):
+ name = self.method.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
+ return CGMethodCall([], nativeName, self.method.isStatic(),
+ self.descriptor, self.method).define()
+
+class CGGenericGetter(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL attribute getter.
+ """
+ def __init__(self, descriptor, lenientThis=False):
+ args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
+ Argument('JS::Value*', 'vp')]
+ if lenientThis:
+ name = "genericLenientGetter"
+ unwrapFailureCode = (
+ "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"
+ "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n"
+ "return true;")
+ else:
+ name = "genericGetter"
+ unwrapFailureCode = None
+ CGAbstractBindingMethod.__init__(self, descriptor, name, args,
+ unwrapFailureCode)
+
+ def generate_code(self):
+ return CGIndenter(CGGeneric(
+ "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "JSJitPropertyOp getter = info->op;\n"
+ "return getter(cx, obj, self, vp);"))
+
+class CGSpecializedGetter(CGAbstractStaticMethod):
+ """
+ A class for generating the code for a specialized attribute getter
+ that the JIT can call with lower overhead.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'get_' + attr.identifier.name
+ args = [ Argument('JSContext*', 'cx'),
+ Argument('JSHandleObject', 'obj'),
+ Argument('%s*' % descriptor.nativeType, 'self'),
+ Argument('JS::Value*', 'vp') ]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
+
+ def definition_body(self):
+ name = self.attr.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
+ # resultOutParam does not depend on whether resultAlreadyAddRefed is set
+ (_, resultOutParam) = getRetvalDeclarationForType(self.attr.type,
+ self.descriptor,
+ False)
+ infallible = ('infallible' in
+ self.descriptor.getExtendedAttributes(self.attr,
+ getter=True))
+ if resultOutParam or self.attr.type.nullable() or not infallible:
+ nativeName = "Get" + nativeName
+ return CGIndenter(CGGetterCall(self.attr.type, nativeName,
+ self.descriptor, self.attr)).define()
+
+class CGGenericSetter(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL attribute setter.
+ """
+ def __init__(self, descriptor, lenientThis=False):
+ args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
+ Argument('JS::Value*', 'vp')]
+ if lenientThis:
+ name = "genericLenientSetter"
+ unwrapFailureCode = (
+ "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"
+ "return true;")
+ else:
+ name = "genericSetter"
+ unwrapFailureCode = None
+ CGAbstractBindingMethod.__init__(self, descriptor, name, args,
+ unwrapFailureCode)
+
+ def generate_code(self):
+ return CGIndenter(CGGeneric(
+ "JS::Value* argv = JS_ARGV(cx, vp);\n"
+ "JS::Value undef = JS::UndefinedValue();\n"
+ "if (argc == 0) {\n"
+ " argv = &undef;\n"
+ "}\n"
+ "const JSJitInfo *info = FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "JSJitPropertyOp setter = info->op;\n"
+ "if (!setter(cx, obj, self, argv)) {\n"
+ " return false;\n"
+ "}\n"
+ "*vp = JSVAL_VOID;\n"
+ "return true;"))
+
+class CGSpecializedSetter(CGAbstractStaticMethod):
+ """
+ A class for generating the code for a specialized attribute setter
+ that the JIT can call with lower overhead.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'set_' + attr.identifier.name
+ args = [ Argument('JSContext*', 'cx'),
+ Argument('JSHandleObject', 'obj'),
+ Argument('%s*' % descriptor.nativeType, 'self'),
+ Argument('JS::Value*', 'argv')]
+ CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args)
+
+ def definition_body(self):
+ name = self.attr.identifier.name
+ nativeName = "Set" + MakeNativeName(self.descriptor.binaryNames.get(name, name))
+ return CGIndenter(CGSetterCall(self.attr.type, nativeName,
+ self.descriptor, self.attr)).define()
+
+def memberIsCreator(member):
+ return member.getExtendedAttribute("Creator") is not None
+
+class CGMemberJITInfo(CGThing):
+ """
+ A class for generating the JITInfo for a property that points to
+ our specialized getter and setter.
+ """
+ def __init__(self, descriptor, member):
+ self.member = member
+ self.descriptor = descriptor
+
+ def declare(self):
+ return ""
+
+ def defineJitInfo(self, infoName, opName, infallible):
+ protoID = "prototypes::id::%s" % self.descriptor.name
+ depth = "PrototypeTraits<%s>::Depth" % protoID
+ failstr = "true" if infallible else "false"
+ return ("\n"
+ "const JSJitInfo %s = {\n"
+ " %s,\n"
+ " %s,\n"
+ " %s,\n"
+ " %s, /* isInfallible. False in setters. */\n"
+ " false /* isConstant. Only relevant for getters. */\n"
+ "};\n" % (infoName, opName, protoID, depth, failstr))
+
+ def define(self):
+ if self.member.isAttr():
+ getterinfo = ("%s_getterinfo" % self.member.identifier.name)
+ getter = ("(JSJitPropertyOp)get_%s" % self.member.identifier.name)
+ getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
+ getterinfal = getterinfal and infallibleForMember(self.member, self.member.type, self.descriptor)
+ result = self.defineJitInfo(getterinfo, getter, getterinfal)
+ if not self.member.readonly:
+ setterinfo = ("%s_setterinfo" % self.member.identifier.name)
+ setter = ("(JSJitPropertyOp)set_%s" % self.member.identifier.name)
+ # Setters are always fallible, since they have to do a typed unwrap.
+ result += self.defineJitInfo(setterinfo, setter, False)
+ return result
+ if self.member.isMethod():
+ methodinfo = ("%s_methodinfo" % self.member.identifier.name)
+ # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition.
+ method = ("(JSJitPropertyOp)%s" % self.member.identifier.name)
+
+ # Methods are infallible if they are infallible, have no arguments
+ # to unwrap, and have a return type that's infallible to wrap up for
+ # return.
+ methodInfal = False
+ sigs = self.member.signatures()
+ if len(sigs) == 1:
+ # Don't handle overloading. If there's more than one signature,
+ # one of them must take arguments.
+ sig = sigs[0]
+ if len(sig[1]) == 0 and infallibleForMember(self.member, sig[0], self.descriptor):
+ # No arguments and infallible return boxing
+ methodInfal = True
+
+ result = self.defineJitInfo(methodinfo, method, methodInfal)
+ return result
+ raise TypeError("Illegal member type to CGPropertyJITInfo")
+
+def getEnumValueName(value):
+ # Some enum values can be empty strings. Others might have weird
+ # characters in them. Deal with the former by returning "_empty",
+ # deal with possible name collisions from that by throwing if the
+ # enum value is actually "_empty", and throw on any value
+ # containing chars other than [a-z] or '-' for now. Replace '-' with '_'.
+ value = value.replace('-', '_')
+ if value == "_empty":
+ raise SyntaxError('"_empty" is not an IDL enum value we support yet')
+ if value == "":
+ return "_empty"
+ if not re.match("^[a-z_]+$", value):
+ raise SyntaxError('Enum value "' + value + '" contains characters '
+ 'outside [a-z_]')
+ return MakeNativeName(value)
+
+class CGEnum(CGThing):
+ def __init__(self, enum):
+ CGThing.__init__(self)
+ self.enum = enum
+
+ def declare(self):
+ return """
+ enum valuelist {
+ %s
+ };
+
+ extern const EnumEntry strings[%d];
+""" % (",\n ".join(map(getEnumValueName, self.enum.values())),
+ len(self.enum.values()) + 1)
+
+ def define(self):
+ return """
+ const EnumEntry strings[%d] = {
+ %s,
+ { NULL, 0 }
+ };
+""" % (len(self.enum.values()) + 1,
+ ",\n ".join(['{"' + val + '", ' + str(len(val)) + '}' for val in self.enum.values()]))
+
+def getUnionAccessorSignatureType(type, descriptorProvider):
+ """
+ Returns the types that are used in the getter and setter signatures for
+ union types
+ """
+ if type.isArray():
+ raise TypeError("Can't handle array arguments yet")
+
+ if type.isSequence():
+ nullable = type.nullable();
+ if nullable:
+ type = type.inner.inner
+ else:
+ type = type.inner
+ (elementTemplate, elementDeclType,
+ elementHolderType, dealWithOptional) = getJSToNativeConversionTemplate(
+ type, descriptorProvider, isSequenceMember=True)
+ typeName = CGWrapper(elementDeclType, pre="Sequence< ", post=" >&")
+ if nullable:
+ typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&")
+
+ return typeName
+
+ if type.isUnion():
+ typeName = CGGeneric(type.name)
+ if type.nullable():
+ typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&")
+
+ return typeName
+
+ if type.isGeckoInterface():
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name)
+ typeName = CGGeneric(descriptor.nativeType)
+ # Allow null pointers for nullable types and old-binding classes
+ if type.nullable() or type.unroll().inner.isExternal():
+ typeName = CGWrapper(typeName, post="*")
+ else:
+ typeName = CGWrapper(typeName, post="&")
+ return typeName
+
+ if type.isSpiderMonkeyInterface():
+ typeName = CGGeneric(type.name)
+ if type.nullable():
+ typeName = CGWrapper(typeName, post="*")
+ else:
+ typeName = CGWrapper(typeName, post="&")
+ return typeName
+
+ if type.isString():
+ return CGGeneric("const nsAString&")
+
+ if type.isEnum():
+ if type.nullable():
+ raise TypeError("We don't support nullable enumerated arguments or "
+ "union members yet")
+ return CGGeneric(type.inner.identifier.name)
+
+ if type.isCallback():
+ return CGGeneric("JSObject*")
+
+ if type.isAny():
+ return CGGeneric("JS::Value")
+
+ if type.isObject():
+ typeName = CGGeneric("JSObject")
+ if type.nullable():
+ typeName = CGWrapper(typeName, post="*")
+ else:
+ typeName = CGWrapper(typeName, post="&")
+ return typeName
+
+ if not type.isPrimitive():
+ raise TypeError("Need native type for argument type '%s'" % str(type))
+
+ typeName = CGGeneric(builtinNames[type.tag()])
+ if type.nullable():
+ typeName = CGWrapper(typeName, pre="Nullable< ", post=" >&")
+ return typeName
+
+def getUnionTypeTemplateVars(type, descriptorProvider):
+ # For dictionaries and sequences we need to pass None as the failureCode
+ # for getJSToNativeConversionTemplate.
+ # Also, for dictionaries we would need to handle conversion of
+ # null/undefined to the dictionary correctly.
+ if type.isDictionary() or type.isSequence():
+ raise TypeError("Can't handle dictionaries or sequences in unions")
+
+ if type.isGeckoInterface():
+ name = type.inner.identifier.name
+ elif type.isEnum():
+ name = type.inner.identifier.name
+ elif type.isArray() or type.isSequence():
+ name = str(type)
+ else:
+ name = type.name
+
+ tryNextCode = """tryNext = true;
+return true;"""
+ if type.isGeckoInterface():
+ tryNextCode = ("""if (mUnion.mType != mUnion.eUninitialized) {
+ mUnion.Destroy%s();
+}""" % name) + tryNextCode
+ (template, declType, holderType,
+ dealWithOptional) = getJSToNativeConversionTemplate(
+ type, descriptorProvider, failureCode=tryNextCode,
+ isDefinitelyObject=True)
+
+ # This is ugly, but UnionMember needs to call a constructor with no
+ # arguments so the type can't be const.
+ structType = declType.define()
+ if structType.startswith("const "):
+ structType = structType[6:]
+ externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
+
+ if type.isObject():
+ setter = CGGeneric("void SetToObject(JSObject* obj)\n"
+ "{\n"
+ " mUnion.mValue.mObject.SetValue() = obj;\n"
+ " mUnion.mType = mUnion.eObject;\n"
+ "}")
+ else:
+ jsConversion = string.Template(template).substitute(
+ {
+ "val": "value",
+ "valPtr": "pvalue",
+ "declName": "SetAs" + name + "()",
+ "holderName": "m" + name + "Holder"
+ }
+ )
+ jsConversion = CGWrapper(CGGeneric(jsConversion),
+ post="\n"
+ "return true;")
+ setter = CGWrapper(CGIndenter(jsConversion),
+ pre="bool TrySetTo" + name + "(JSContext* cx, const JS::Value& value, JS::Value* pvalue, bool& tryNext)\n"
+ "{\n"
+ " tryNext = false;\n",
+ post="\n"
+ "}")
+
+ return {
+ "name": name,
+ "structType": structType,
+ "externalType": externalType,
+ "setter": CGIndenter(setter).define(),
+ "holderType": holderType.define() if holderType else None
+ }
+
+def mapTemplate(template, templateVarArray):
+ return map(lambda v: string.Template(template).substitute(v),
+ templateVarArray)
+
+class CGUnionStruct(CGThing):
+ def __init__(self, type, descriptorProvider):
+ CGThing.__init__(self)
+ self.type = type.unroll()
+ self.descriptorProvider = descriptorProvider
+
+ def declare(self):
+ templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider),
+ self.type.flatMemberTypes)
+
+ callDestructors = []
+ enumValues = []
+ methods = []
+ if self.type.hasNullableType:
+ callDestructors.append(" case eNull:\n"
+ " break;")
+ enumValues.append("eNull")
+ methods.append(""" bool IsNull() const
+ {
+ return mType == eNull;
+ }""")
+
+ destructorTemplate = """ void Destroy${name}()
+ {
+ MOZ_ASSERT(Is${name}(), "Wrong type!");
+ mValue.m${name}.Destroy();
+ mType = eUninitialized;
+ }"""
+ destructors = mapTemplate(destructorTemplate, templateVars)
+ callDestructors.extend(mapTemplate(" case e${name}:\n"
+ " Destroy${name}();\n"
+ " break;", templateVars))
+ enumValues.extend(mapTemplate("e${name}", templateVars))
+ methodTemplate = """ bool Is${name}() const
+ {
+ return mType == e${name};
+ }
+ ${externalType} GetAs${name}() const
+ {
+ MOZ_ASSERT(Is${name}(), "Wrong type!");
+ // The cast to ${externalType} is needed to work around a bug in Apple's
+ // clang compiler, for some reason it doesn't call |S::operator T&| when
+ // casting S<T> to T& and T is forward declared.
+ return (${externalType})mValue.m${name}.Value();
+ }
+ ${structType}& SetAs${name}()
+ {
+ mType = e${name};
+ return mValue.m${name}.SetValue();
+ }"""
+ methods.extend(mapTemplate(methodTemplate, templateVars))
+ values = mapTemplate("UnionMember<${structType} > m${name};", templateVars)
+ return string.Template("""
+class ${structName} {
+public:
+ ${structName}() : mType(eUninitialized)
+ {
+ }
+ ~${structName}()
+ {
+ switch (mType) {
+${callDestructors}
+ case eUninitialized:
+ break;
+ }
+ }
+
+${methods}
+
+private:
+ friend class ${structName}Argument;
+
+${destructors}
+
+ enum Type {
+ eUninitialized,
+ ${enumValues}
+ };
+ union Value {
+ ${values}
+ };
+
+ Type mType;
+ Value mValue;
+};
+
+""").substitute(
+ {
+ "structName": self.type.__str__(),
+ "callDestructors": "\n".join(callDestructors),
+ "destructors": "\n".join(destructors),
+ "methods": "\n\n".join(methods),
+ "enumValues": ",\n ".join(enumValues),
+ "values": "\n ".join(values),
+ })
+
+ def define(self):
+ return """
+"""
+
+class CGUnionConversionStruct(CGThing):
+ def __init__(self, type, descriptorProvider):
+ CGThing.__init__(self)
+ self.type = type.unroll()
+ self.descriptorProvider = descriptorProvider
+
+ def declare(self):
+ setters = []
+
+ if self.type.hasNullableType:
+ setters.append(""" bool SetNull()
+ {
+ mUnion.mType = mUnion.eNull;
+ return true;
+ }""")
+
+ templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider),
+ self.type.flatMemberTypes)
+ structName = self.type.__str__()
+
+ setters.extend(mapTemplate("${setter}", templateVars))
+ private = "\n".join(mapTemplate(""" ${structType}& SetAs${name}()
+ {
+ mUnion.mType = mUnion.e${name};
+ return mUnion.mValue.m${name}.SetValue();
+ }""", templateVars))
+ private += "\n\n"
+ holders = filter(lambda v: v["holderType"] is not None, templateVars)
+ if len(holders) > 0:
+ private += "\n".join(mapTemplate(" ${holderType} m${name}Holder;", holders))
+ private += "\n\n"
+ private += " " + structName + "& mUnion;"
+ return string.Template("""
+class ${structName}Argument {
+public:
+ ${structName}Argument(const ${structName}& aUnion) : mUnion(const_cast<${structName}&>(aUnion))
+ {
+ }
+
+${setters}
+
+private:
+${private}
+};
+""").substitute({"structName": structName,
+ "setters": "\n\n".join(setters),
+ "private": private
+ })
+
+ def define(self):
+ return """
+"""
+
+class ClassItem:
+ """ Use with CGClass """
+ def __init__(self, name, visibility):
+ self.name = name
+ self.visibility = visibility
+ def declare(self, cgClass):
+ assert False
+ def define(self, cgClass):
+ assert False
+
+class ClassBase(ClassItem):
+ def __init__(self, name, visibility='public'):
+ ClassItem.__init__(self, name, visibility)
+ def declare(self, cgClass):
+ return '%s %s' % (self.visibility, self.name)
+ def define(self, cgClass):
+ # Only in the header
+ return ''
+
+class ClassMethod(ClassItem):
+ def __init__(self, name, returnType, args, inline=False, static=False,
+ virtual=False, const=False, bodyInHeader=False,
+ templateArgs=None, visibility='public', body=None):
+ self.returnType = returnType
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.static = static
+ self.virtual = virtual
+ self.const = const
+ self.bodyInHeader = bodyInHeader
+ self.templateArgs = templateArgs
+ self.body = body
+ ClassItem.__init__(self, name, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.inline:
+ decorators.append('inline')
+ if declaring:
+ if self.static:
+ decorators.append('static')
+ if self.virtual:
+ decorators.append('virtual')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getBody(self):
+ # Override me or pass a string to constructor
+ assert self.body is not None
+ return self.body
+
+ def declare(self, cgClass):
+ templateClause = 'template <%s>\n' % ', '.join(self.templateArgs) \
+ if self.bodyInHeader and self.templateArgs else ''
+ args = ', '.join([str(a) for a in self.args])
+ if self.bodyInHeader:
+ body = CGIndenter(CGGeneric(self.getBody())).define()
+ body = '\n{\n' + body + '\n}'
+ else:
+ body = ';'
+
+ return string.Template("""${templateClause}${decorators}${returnType}
+${name}(${args})${const}${body}
+""").substitute({ 'templateClause': templateClause,
+ 'decorators': self.getDecorators(True),
+ 'returnType': self.returnType,
+ 'name': self.name,
+ 'const': ' const' if self.const else '',
+ 'args': args,
+ 'body': body })
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ templateArgs = cgClass.templateArgs
+ if templateArgs:
+ if cgClass.templateSpecialization:
+ templateArgs = \
+ templateArgs[len(cgClass.templateSpecialization):]
+
+ if templateArgs:
+ templateClause = \
+ 'template <%s>\n' % ', '.join([str(a) for a in templateArgs])
+ else:
+ templateClause = ''
+
+ args = ', '.join([str(a) for a in self.args])
+
+ body = CGIndenter(CGGeneric(self.getBody())).define()
+
+ return string.Template("""${templateClause}${decorators}${returnType}
+${className}::${name}(${args})${const}
+{
+${body}
+}\n
+""").substitute({ 'templateClause': templateClause,
+ 'decorators': self.getDecorators(False),
+ 'returnType': self.returnType,
+ 'className': cgClass.getNameString(),
+ 'name': self.name,
+ 'args': args,
+ 'const': ' const' if self.const else '',
+ 'body': body })
+
+class ClassConstructor(ClassItem):
+ """
+ Used for adding a constructor to a CGClass.
+
+ args is a list of Argument objects that are the arguments taken by the
+ constructor.
+
+ inline should be True if the constructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the constructor (public,
+ protected, private), defaults to private.
+
+ baseConstructors is a list of strings containing calls to base constructors,
+ defaults to None.
+
+ body contains a string with the code for the constructor, defaults to None.
+ """
+ def __init__(self, args, inline=False, bodyInHeader=False,
+ visibility="private", baseConstructors=None, body=None):
+ self.args = args
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader
+ self.baseConstructors = baseConstructors
+ self.body = body
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.inline and declaring:
+ decorators.append('inline')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getInitializationList(self, cgClass):
+ items = [str(c) for c in self.baseConstructors]
+ for m in cgClass.members:
+ if not m.static:
+ initialize = m.getBody()
+ if initialize:
+ items.append(m.name + "(" + initialize + ")")
+
+ if len(items) > 0:
+ return '\n : ' + ',\n '.join(items)
+ return ''
+
+ def getBody(self):
+ assert self.body is not None
+ return self.body
+
+ def declare(self, cgClass):
+ args = ', '.join([str(a) for a in self.args])
+ if self.bodyInHeader:
+ body = ' ' + self.getBody();
+ body = stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+ body = self.getInitializationList(cgClass) + '\n{\n' + body + '}'
+ else:
+ body = ';'
+
+ return string.Template("""${decorators}${className}(${args})${body}
+""").substitute({ 'decorators': self.getDecorators(True),
+ 'className': cgClass.getNameString(),
+ 'args': args,
+ 'body': body })
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ args = ', '.join([str(a) for a in self.args])
+
+ body = ' ' + self.getBody()
+ body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+
+ return string.Template("""${decorators}
+${className}::${className}(${args})${initializationList}
+{${body}}\n
+""").substitute({ 'decorators': self.getDecorators(False),
+ 'className': cgClass.getNameString(),
+ 'args': args,
+ 'initializationList': self.getInitializationList(cgClass),
+ 'body': body })
+
+class ClassMember(ClassItem):
+ def __init__(self, name, type, visibility="private", static=False,
+ body=None):
+ self.type = type;
+ self.static = static
+ self.body = body
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return '%s%s %s;\n' % ('static ' if self.static else '', self.type,
+ self.name)
+
+ def define(self, cgClass):
+ if not self.static:
+ return ''
+ if self.body:
+ body = " = " + self.body
+ else:
+ body = ""
+ return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(),
+ self.name, body)
+
+class ClassTypedef(ClassItem):
+ def __init__(self, name, type, visibility="public"):
+ self.type = type
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return 'typedef %s %s;\n' % (self.type, self.name)
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+class ClassEnum(ClassItem):
+ def __init__(self, name, entries, values=None, visibility="public"):
+ self.entries = entries
+ self.values = values
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ entries = []
+ for i in range(0, len(self.entries)):
+ if i >= len(self.values):
+ entry = '%s' % self.entries[i]
+ else:
+ entry = '%s = %s' % (self.entries[i], self.values[i])
+ entries.append(entry)
+ name = '' if not self.name else ' ' + self.name
+ return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+class CGClass(CGThing):
+ def __init__(self, name, bases=[], members=[], constructors=[], methods=[],
+ typedefs = [], enums=[], templateArgs=[],
+ templateSpecialization=[], isStruct=False, indent=''):
+ CGThing.__init__(self)
+ self.name = name
+ self.bases = bases
+ self.members = members
+ self.constructors = constructors
+ self.methods = methods
+ self.typedefs = typedefs
+ self.enums = enums
+ self.templateArgs = templateArgs
+ self.templateSpecialization = templateSpecialization
+ self.isStruct = isStruct
+ self.indent = indent
+ self.defaultVisibility ='public' if isStruct else 'private'
+
+ def getNameString(self):
+ className = self.name
+ if self.templateSpecialization:
+ className = className + \
+ '<%s>' % ', '.join([str(a) for a
+ in self.templateSpecialization])
+ return className
+
+ def declare(self):
+ result = ''
+ if self.templateArgs:
+ templateArgs = [str(a) for a in self.templateArgs]
+ templateArgs = templateArgs[len(self.templateSpecialization):]
+ result = result + self.indent + 'template <%s>\n' \
+ % ','.join([str(a) for a in templateArgs])
+
+ type = 'struct' if self.isStruct else 'class'
+
+ if self.templateSpecialization:
+ specialization = \
+ '<%s>' % ', '.join([str(a) for a in self.templateSpecialization])
+ else:
+ specialization = ''
+
+ result = result + '%s%s %s%s' \
+ % (self.indent, type, self.name, specialization)
+
+ if self.bases:
+ result = result + ' : %s' % ', '.join([d.declare(self) for d in self.bases])
+
+ result = result + '\n%s{\n' % self.indent
+
+ def declareMembers(cgClass, memberList, defaultVisibility, itemCount,
+ separator=''):
+ members = { 'private': [], 'protected': [], 'public': [] }
+
+ for member in memberList:
+ members[member.visibility].append(member)
+
+
+ if defaultVisibility == 'public':
+ order = [ 'public', 'protected', 'private' ]
+ else:
+ order = [ 'private', 'protected', 'public' ]
+
+ result = ''
+
+ lastVisibility = defaultVisibility
+ for visibility in order:
+ list = members[visibility]
+ if list:
+ if visibility != lastVisibility:
+ if itemCount:
+ result = result + '\n'
+ result = result + visibility + ':\n'
+ itemCount = 0
+ for member in list:
+ if itemCount != 0:
+ result = result + separator
+ declaration = member.declare(cgClass)
+ declaration = CGIndenter(CGGeneric(declaration)).define()
+ result = result + declaration
+ itemCount = itemCount + 1
+ lastVisibility = visibility
+ return (result, lastVisibility, itemCount)
+
+ order = [(self.enums, ''), (self.typedefs, ''), (self.members, ''),
+ (self.constructors, '\n'), (self.methods, '\n')]
+
+ lastVisibility = self.defaultVisibility
+ itemCount = 0
+ for (memberList, separator) in order:
+ (memberString, lastVisibility, itemCount) = \
+ declareMembers(self, memberList, lastVisibility, itemCount,
+ separator)
+ if self.indent:
+ memberString = CGIndenter(CGGeneric(memberString),
+ len(self.indent)).define()
+ result = result + memberString
+
+ result = result + self.indent + '};\n'
+ return result
+
+ def define(self):
+ def defineMembers(cgClass, memberList, itemCount, separator=''):
+ result = ''
+ for member in memberList:
+ if itemCount != 0:
+ result = result + separator
+ result = result + member.define(cgClass)
+ itemCount = itemCount + 1
+ return (result, itemCount)
+
+ order = [(self.members, '\n'), (self.constructors, '\n'),
+ (self.methods, '\n')]
+
+ result = ''
+ itemCount = 0
+ for (memberList, separator) in order:
+ (memberString, itemCount) = defineMembers(self, memberList,
+ itemCount, separator)
+ result = result + memberString
+ return result
+
+class CGResolveOwnProperty(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'),
+ Argument('jsid', 'id'), Argument('bool', 'set'),
+ Argument('JSPropertyDescriptor*', 'desc')]
+ CGAbstractMethod.__init__(self, descriptor, "ResolveOwnProperty", "bool", args)
+ def definition_body(self):
+ return """ JSObject* obj = wrapper;
+ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ obj = js::UnwrapObject(obj);
+ }
+ // We rely on getOwnPropertyDescriptor not shadowing prototype properties by named
+ // properties. If that changes we'll need to filter here.
+ return js::GetProxyHandler(obj)->getOwnPropertyDescriptor(cx, wrapper, id, set, desc);
+"""
+
+class CGEnumerateOwnProperties(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'),
+ Argument('JS::AutoIdVector&', 'props')]
+ CGAbstractMethod.__init__(self, descriptor, "EnumerateOwnProperties", "bool", args)
+ def definition_body(self):
+ return """ JSObject* obj = wrapper;
+ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ obj = js::UnwrapObject(obj);
+ }
+ // We rely on getOwnPropertyNames not shadowing prototype properties by named
+ // properties. If that changes we'll need to filter here.
+ return js::GetProxyHandler(obj)->getOwnPropertyNames(cx, wrapper, props);
+"""
+
+class CGXrayHelper(CGAbstractMethod):
+ def __init__(self, descriptor, name, args, properties):
+ CGAbstractMethod.__init__(self, descriptor, name, "bool", args)
+ self.properties = properties
+
+ def definition_body(self):
+ varNames = self.properties.variableNames(True)
+
+ methods = self.properties.methods
+ if methods.hasNonChromeOnly() or methods.hasChromeOnly():
+ methodArgs = """// %(methods)s has an end-of-list marker at the end that we ignore
+%(methods)s, %(methods)s_ids, %(methods)s_specs, ArrayLength(%(methods)s) - 1""" % varNames
+ else:
+ methodArgs = "NULL, NULL, NULL, 0"
+ methodArgs = CGGeneric(methodArgs)
+
+ attrs = self.properties.attrs
+ if attrs.hasNonChromeOnly() or attrs.hasChromeOnly():
+ attrArgs = """// %(attrs)s has an end-of-list marker at the end that we ignore
+%(attrs)s, %(attrs)s_ids, %(attrs)s_specs, ArrayLength(%(attrs)s) - 1""" % varNames
+ else:
+ attrArgs = "NULL, NULL, NULL, 0"
+ attrArgs = CGGeneric(attrArgs)
+
+ consts = self.properties.consts
+ if consts.hasNonChromeOnly() or consts.hasChromeOnly():
+ constArgs = """// %(consts)s has an end-of-list marker at the end that we ignore
+%(consts)s, %(consts)s_ids, %(consts)s_specs, ArrayLength(%(consts)s) - 1""" % varNames
+ else:
+ constArgs = "NULL, NULL, NULL, 0"
+ constArgs = CGGeneric(constArgs)
+
+ prefixArgs = CGGeneric(self.getPrefixArgs())
+
+ return CGIndenter(
+ CGWrapper(CGList([prefixArgs, methodArgs, attrArgs, constArgs], ",\n"),
+ pre=("return Xray%s(" % self.name),
+ post=");",
+ reindent=True)).define()
+
+class CGResolveProperty(CGXrayHelper):
+ def __init__(self, descriptor, properties):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'),
+ Argument('jsid', 'id'), Argument('bool', 'set'),
+ Argument('JSPropertyDescriptor*', 'desc')]
+ CGXrayHelper.__init__(self, descriptor, "ResolveProperty", args,
+ properties)
+
+ def getPrefixArgs(self):
+ return "cx, wrapper, id, desc"
+
+
+class CGEnumerateProperties(CGXrayHelper):
+ def __init__(self, descriptor, properties):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'wrapper'),
+ Argument('JS::AutoIdVector&', 'props')]
+ CGXrayHelper.__init__(self, descriptor, "EnumerateProperties", args,
+ properties)
+
+ def getPrefixArgs(self):
+ return "props"
+
+class CGPrototypeTraitsClass(CGClass):
+ def __init__(self, descriptor, indent=''):
+ templateArgs = [Argument('prototypes::ID', 'PrototypeID')]
+ templateSpecialization = ['prototypes::id::' + descriptor.name]
+ enums = [ClassEnum('', ['Depth'],
+ [descriptor.interface.inheritanceDepth()])]
+ typedefs = [ClassTypedef('NativeType', descriptor.nativeType)]
+ CGClass.__init__(self, 'PrototypeTraits', indent=indent,
+ templateArgs=templateArgs,
+ templateSpecialization=templateSpecialization,
+ enums=enums, typedefs=typedefs, isStruct=True)
+
+class CGPrototypeIDMapClass(CGClass):
+ def __init__(self, descriptor, indent=''):
+ templateArgs = [Argument('class', 'ConcreteClass')]
+ templateSpecialization = [descriptor.nativeType]
+ enums = [ClassEnum('', ['PrototypeID'],
+ ['prototypes::id::' + descriptor.name])]
+ CGClass.__init__(self, 'PrototypeIDMap', indent=indent,
+ templateArgs=templateArgs,
+ templateSpecialization=templateSpecialization,
+ enums=enums, isStruct=True)
+
+class CGClassForwardDeclare(CGThing):
+ def __init__(self, name, isStruct=False):
+ CGThing.__init__(self)
+ self.name = name
+ self.isStruct = isStruct
+ def declare(self):
+ type = 'struct' if self.isStruct else 'class'
+ return '%s %s;\n' % (type, self.name)
+ def define(self):
+ # Header only
+ return ''
+
+class CGProxySpecialOperation(CGPerSignatureCall):
+ """
+ Base class for classes for calling an indexed or named special operation
+ (don't use this directly, use the derived classes below).
+ """
+ def __init__(self, descriptor, operation):
+ nativeName = MakeNativeName(descriptor.binaryNames.get(operation, operation))
+ operation = descriptor.operations[operation]
+ assert len(operation.signatures()) == 1
+ signature = operation.signatures()[0]
+ extendedAttributes = descriptor.getExtendedAttributes(operation)
+
+ (returnType, arguments) = signature
+
+ # We pass len(arguments) as the final argument so that the
+ # CGPerSignatureCall won't do any argument conversion of its own.
+ CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName,
+ False, descriptor, operation,
+ len(arguments))
+
+ if operation.isSetter() or operation.isCreator():
+ # arguments[0] is the index or name of the item that we're setting.
+ argument = arguments[1]
+ template = getJSToNativeConversionTemplate(argument.type, descriptor,
+ treatNullAs=argument.treatNullAs,
+ treatUndefinedAs=argument.treatUndefinedAs)
+ templateValues = {
+ "declName": argument.identifier.name,
+ "holderName": argument.identifier.name + "_holder",
+ "val": "desc->value",
+ "valPtr": "&desc->value"
+ }
+ self.cgRoot.prepend(instantiateJSToNativeConversionTemplate(template, templateValues))
+ elif operation.isGetter():
+ self.cgRoot.prepend(CGGeneric("bool found;"))
+
+ def getArguments(self):
+ args = [(a, a.identifier.name) for a in self.arguments]
+ if self.idlNode.isGetter():
+ args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
+ self.idlNode),
+ "found"))
+ return args
+
+ def wrap_return_value(self):
+ if not self.idlNode.isGetter() or self.templateValues is None:
+ return ""
+
+ wrap = CGGeneric(wrapForType(self.returnType, self.descriptor, self.templateValues))
+ wrap = CGIfWrapper(wrap, "found")
+ return "\n" + wrap.define()
+
+class CGProxyIndexedGetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+ """
+ def __init__(self, descriptor, templateValues=None):
+ self.templateValues = templateValues
+ CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter')
+
+class CGProxyIndexedSetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed setter.
+ """
+ def __init__(self, descriptor):
+ CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter')
+
+class CGProxyNamedGetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an named getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+ """
+ def __init__(self, descriptor, templateValues=None):
+ self.templateValues = templateValues
+ CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter')
+
+class CGProxyNamedSetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to a named setter.
+ """
+ def __init__(self, descriptor):
+ CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter')
+
+class CGProxyIsProxy(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSObject*', 'obj')]
+ CGAbstractMethod.__init__(self, descriptor, "IsProxy", "bool", args, alwaysInline=True)
+ def declare(self):
+ return ""
+ def definition_body(self):
+ return " return js::IsProxy(obj) && js::GetProxyHandler(obj) == DOMProxyHandler::getInstance();"
+
+class CGProxyUnwrap(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSObject*', 'obj')]
+ CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", descriptor.nativeType + '*', args, alwaysInline=True)
+ def declare(self):
+ return ""
+ def definition_body(self):
+ return """ if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ obj = js::UnwrapObject(obj);
+ }
+ MOZ_ASSERT(IsProxy(obj));
+ return static_cast<%s*>(js::GetProxyPrivate(obj).toPrivate());""" % (self.descriptor.nativeType)
+
+class CGDOMJSProxyHandlerDOMClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ def declare(self):
+ return "extern const DOMClass Class;\n"
+ def define(self):
+ return """
+const DOMClass Class = """ + DOMClass(self.descriptor) + """;
+
+"""
+
+class CGDOMJSProxyHandler_CGDOMJSProxyHandler(ClassConstructor):
+ def __init__(self):
+ ClassConstructor.__init__(self, [], inline=True, visibility="private",
+ baseConstructors=["mozilla::dom::DOMProxyHandler(Class)"],
+ body="")
+
+class CGDOMJSProxyHandler_getOwnPropertyDescriptor(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('jsid', 'id'), Argument('bool', 'set'),
+ Argument('JSPropertyDescriptor*', 'desc')]
+ ClassMethod.__init__(self, "getOwnPropertyDescriptor", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ indexedSetter = self.descriptor.operations['IndexedSetter']
+
+ setOrIndexedGet = ""
+ if indexedGetter or indexedSetter:
+ setOrIndexedGet += "int32_t index = GetArrayIndexFromId(cx, id);\n"
+
+ if indexedGetter:
+ readonly = toStringBool(self.descriptor.operations['IndexedSetter'] is None)
+ fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;" % readonly
+ templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value',
+ 'obj': 'proxy', 'successCode': fillDescriptor}
+ get = ("if (index >= 0) {\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" +
+ "}\n") % (self.descriptor.nativeType)
+
+ if indexedSetter or self.descriptor.operations['NamedSetter']:
+ setOrIndexedGet += "if (set) {\n"
+ if indexedSetter:
+ setOrIndexedGet += (" if (index >= 0) {\n")
+ if not 'IndexedCreator' in self.descriptor.operations:
+ # FIXME need to check that this is a 'supported property index'
+ assert False
+ setOrIndexedGet += (" FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n" +
+ " return true;\n" +
+ " }\n")
+ if self.descriptor.operations['NamedSetter']:
+ setOrIndexedGet += " if (JSID_IS_STRING(id)) {\n"
+ if not 'NamedCreator' in self.descriptor.operations:
+ # FIXME need to check that this is a 'supported property name'
+ assert False
+ setOrIndexedGet += (" FillPropertyDescriptor(desc, proxy, JSVAL_VOID, false);\n" +
+ " return true;\n" +
+ " }\n")
+ setOrIndexedGet += "}"
+ if indexedGetter:
+ setOrIndexedGet += (" else {\n" +
+ CGIndenter(CGGeneric(get)).define() +
+ "}")
+ setOrIndexedGet += "\n\n"
+ elif indexedGetter:
+ setOrIndexedGet += ("if (!set) {\n" +
+ CGIndenter(CGGeneric(get)).define() +
+ "}\n\n")
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter:
+ readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None)
+ fillDescriptor = "FillPropertyDescriptor(desc, proxy, %s);\nreturn true;" % readonly
+ templateValues = {'jsvalRef': 'desc->value', 'jsvalPtr': '&desc->value',
+ 'obj': 'proxy', 'successCode': fillDescriptor}
+ # Once we start supporting OverrideBuiltins we need to make
+ # ResolveOwnProperty or EnumerateOwnProperties filter out named
+ # properties that shadow prototype properties.
+ namedGet = ("\n" +
+ "if (!set && JSID_IS_STRING(id) && !HasPropertyOnPrototype(cx, proxy, this, id)) {\n" +
+ " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" +
+ " FakeDependentString name;\n"
+ " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" +
+ " eStringify, eStringify, name)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ "\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" +
+ "}\n") % (self.descriptor.nativeType)
+ else:
+ namedGet = ""
+
+ return setOrIndexedGet + """JSObject* expando;
+if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
+ unsigned flags = (set ? JSRESOLVE_ASSIGNING : 0) | JSRESOLVE_QUALIFIED;
+ if (!JS_GetPropertyDescriptorById(cx, expando, id, flags, desc)) {
+ return false;
+ }
+ if (desc->obj) {
+ // Pretend the property lives on the wrapper.
+ desc->obj = proxy;
+ return true;
+ }
+}
+""" + namedGet + """
+desc->obj = NULL;
+return true;"""
+
+class CGDOMJSProxyHandler_defineProperty(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('jsid', 'id'),
+ Argument('JSPropertyDescriptor*', 'desc')]
+ ClassMethod.__init__(self, "defineProperty", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ set = ""
+
+ indexedSetter = self.descriptor.operations['IndexedSetter']
+ if indexedSetter:
+ if not (self.descriptor.operations['IndexedCreator'] is indexedSetter):
+ raise TypeError("Can't handle creator that's different from the setter")
+ set += ("int32_t index = GetArrayIndexFromId(cx, id);\n" +
+ "if (index >= 0) {\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() +
+ " return true;\n" +
+ "}\n") % (self.descriptor.nativeType)
+ elif self.descriptor.operations['IndexedGetter']:
+ set += ("if (GetArrayIndexFromId(cx, id) >= 0) {\n" +
+ " return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" +
+ "}\n") % self.descriptor.name
+
+ namedSetter = self.descriptor.operations['NamedSetter']
+ if namedSetter:
+ if not self.descriptor.operations['NamedCreator'] is namedSetter:
+ raise TypeError("Can't handle creator that's different from the setter")
+ set += ("if (JSID_IS_STRING(id)) {\n" +
+ " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" +
+ " FakeDependentString name;\n"
+ " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" +
+ " eStringify, eStringify, name)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ "\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" +
+ "}\n") % (self.descriptor.nativeType)
+ elif self.descriptor.operations['NamedGetter']:
+ set += ("if (JSID_IS_STRING(id)) {\n" +
+ " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" +
+ " FakeDependentString name;\n"
+ " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" +
+ " eStringify, eStringify, name)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor)).define() +
+ " if (found) {\n"
+ " return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" +
+ " }\n" +
+ " return true;\n"
+ "}\n") % (self.descriptor.nativeType, self.descriptor.name)
+ return set + """return mozilla::dom::DOMProxyHandler::defineProperty(%s);""" % ", ".join(a.name for a in self.args)
+
+class CGDOMJSProxyHandler_getOwnPropertyNames(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('JS::AutoIdVector&', 'props')]
+ ClassMethod.__init__(self, "getOwnPropertyNames", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ addIndices = """uint32_t length = UnwrapProxy(proxy)->Length();
+MOZ_ASSERT(int32_t(length) >= 0);
+for (int32_t i = 0; i < int32_t(length); ++i) {
+ if (!props.append(INT_TO_JSID(i))) {
+ return false;
+ }
+}
+
+"""
+ else:
+ addIndices = ""
+
+ return addIndices + """JSObject* expando;
+if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = DOMProxyHandler::GetExpandoObject(proxy)) &&
+ !js::GetPropertyNames(cx, expando, JSITER_OWNONLY | JSITER_HIDDEN, &props)) {
+ return false;
+}
+
+// FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=772869 Add named items
+return true;"""
+
+class CGDOMJSProxyHandler_hasOwn(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('jsid', 'id'), Argument('bool*', 'bp')]
+ ClassMethod.__init__(self, "hasOwn", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ indexed = ("int32_t index = GetArrayIndexFromId(cx, id);\n" +
+ "if (index >= 0) {\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" +
+ " *bp = found;\n" +
+ " return true;\n" +
+ "}\n\n") % (self.descriptor.nativeType)
+ else:
+ indexed = ""
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter:
+ named = ("if (JSID_IS_STRING(id) && !HasPropertyOnPrototype(cx, proxy, this, id)) {\n" +
+ " jsval nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" +
+ " FakeDependentString name;\n"
+ " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" +
+ " eStringify, eStringify, name)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ "\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" +
+ " *bp = found;\n"
+ " return true;\n"
+ "}\n" +
+ "\n") % (self.descriptor.nativeType)
+ else:
+ named = ""
+
+ return indexed + """JSObject* expando = GetExpandoObject(proxy);
+if (expando) {
+ JSBool b = true;
+ JSBool ok = JS_HasPropertyById(cx, expando, id, &b);
+ *bp = !!b;
+ if (!ok || *bp) {
+ return ok;
+ }
+}
+
+""" + named + """*bp = false;
+return true;"""
+
+class CGDOMJSProxyHandler_get(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('JSObject*', 'receiver'), Argument('jsid', 'id'),
+ Argument('JS::Value*', 'vp')]
+ ClassMethod.__init__(self, "get", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ getFromExpando = """JSObject* expando = DOMProxyHandler::GetExpandoObject(proxy);
+if (expando) {
+ JSBool hasProp;
+ if (!JS_HasPropertyById(cx, expando, id, &hasProp)) {
+ return false;
+ }
+
+ if (hasProp) {
+ return JS_GetPropertyById(cx, expando, id, vp);
+ }
+}"""
+
+ templateValues = {'jsvalRef': '*vp', 'jsvalPtr': 'vp', 'obj': 'proxy'}
+
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ getIndexedOrExpando = ("int32_t index = GetArrayIndexFromId(cx, id);\n" +
+ "if (index >= 0) {\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define()) % (self.descriptor.nativeType)
+ getIndexedOrExpando += """
+ // Even if we don't have this index, we don't forward the
+ // get on to our expando object.
+} else {
+ %s
+}
+""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n ')))
+ else:
+ getIndexedOrExpando = getFromExpando + "\n"
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter:
+ getNamed = ("if (JSID_IS_STRING(id)) {\n" +
+ " JS::Value nameVal = STRING_TO_JSVAL(JSID_TO_STRING(id));\n" +
+ " FakeDependentString name;\n"
+ " if (!ConvertJSValueToString(cx, nameVal, &nameVal,\n" +
+ " eStringify, eStringify, name)) {\n" +
+ " return false;\n" +
+ " }\n" +
+ "\n" +
+ " %s* self = UnwrapProxy(proxy);\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() +
+ "}\n") % (self.descriptor.nativeType)
+ else:
+ getNamed = ""
+
+ return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+%s
+bool found;
+if (!GetPropertyOnPrototype(cx, proxy, id, &found, vp)) {
+ return false;
+}
+
+if (found) {
+ return true;
+}
+%s
+vp->setUndefined();
+return true;""" % (getIndexedOrExpando, getNamed)
+
+class CGDOMJSProxyHandler_obj_toString(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy')]
+ ClassMethod.__init__(self, "obj_toString", "JSString*", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ stringifier = self.descriptor.operations['Stringifier']
+ if stringifier:
+ name = stringifier.identifier.name
+ nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
+ signature = stringifier.signatures()[0]
+ returnType = signature[0]
+ extendedAttributes = self.descriptor.getExtendedAttributes(stringifier)
+ infallible = 'infallible' in extendedAttributes
+ if not infallible:
+ error = CGGeneric(
+ ('ThrowMethodFailedWithDetails(cx, rv, "%s", "toString");\n' +
+ "return NULL;") % self.descriptor.interface.identifier.name)
+ else:
+ error = None
+ call = CGCallGenerator(error, [], "", returnType, extendedAttributes, self.descriptor, nativeName, False, object="UnwrapProxy(proxy)")
+ return call.define() + """
+
+JSString* jsresult;
+return xpc_qsStringToJsstring(cx, result, &jsresult) ? jsresult : NULL;"""
+
+ return "return mozilla::dom::DOMProxyHandler::obj_toString(cx, \"%s\");" % self.descriptor.name
+
+class CGDOMJSProxyHandler_finalize(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'proxy')]
+ ClassMethod.__init__(self, "finalize", "void", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ return ("%s self = UnwrapProxy(proxy);\n\n" % (self.descriptor.nativeType + "*") +
+ finalizeHook(self.descriptor, FINALIZE_HOOK_NAME, self.args[0].name))
+
+class CGDOMJSProxyHandler_getElementIfPresent(ClassMethod):
+ def __init__(self, descriptor):
+ args = [Argument('JSContext*', 'cx'), Argument('JSObject*', 'proxy'),
+ Argument('JSObject*', 'receiver'),
+ Argument('uint32_t', 'index'),
+ Argument('JS::Value*', 'vp'), Argument('bool*', 'present')]
+ ClassMethod.__init__(self, "getElementIfPresent", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ successCode = """*present = found;
+return true;"""
+ templateValues = {'jsvalRef': '*vp', 'jsvalPtr': 'vp',
+ 'obj': 'proxy', 'successCode': successCode}
+ get = ("%s* self = UnwrapProxy(proxy);\n" +
+ CGProxyIndexedGetter(self.descriptor, templateValues).define() + "\n"
+ "// We skip the expando object if there is an indexed getter.\n" +
+ "\n") % (self.descriptor.nativeType)
+ else:
+ get = """
+
+JSObject* expando = GetExpandoObject(proxy);
+if (expando) {
+ JSBool isPresent;
+ if (!JS_GetElementIfPresent(cx, expando, index, expando, vp, &isPresent)) {
+ return false;
+ }
+ if (isPresent) {
+ *present = true;
+ return true;
+ }
+}
+"""
+
+ return """MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ "Should not have a XrayWrapper here");
+
+""" + get + """
+// No need to worry about name getters here, so just check the proto.
+
+JSObject *proto;
+if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+}
+if (proto) {
+ JSBool isPresent;
+ if (!JS_GetElementIfPresent(cx, proto, index, proxy, vp, &isPresent)) {
+ return false;
+ }
+ *present = isPresent;
+ return true;
+}
+
+*present = false;
+// Can't Debug_SetValueRangeToCrashOnTouch because it's not public
+return true;"""
+
+class CGDOMJSProxyHandler_getInstance(ClassMethod):
+ def __init__(self):
+ ClassMethod.__init__(self, "getInstance", "DOMProxyHandler*", [], static=True)
+ def getBody(self):
+ return """static DOMProxyHandler instance;
+return &instance;"""
+
+class CGDOMJSProxyHandler(CGClass):
+ def __init__(self, descriptor):
+ constructors = [CGDOMJSProxyHandler_CGDOMJSProxyHandler()]
+ methods = [CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor)]
+ if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']:
+ methods.append(CGDOMJSProxyHandler_defineProperty(descriptor))
+ methods.extend([CGDOMJSProxyHandler_getOwnPropertyNames(descriptor),
+ CGDOMJSProxyHandler_hasOwn(descriptor),
+ CGDOMJSProxyHandler_get(descriptor),
+ CGDOMJSProxyHandler_obj_toString(descriptor),
+ CGDOMJSProxyHandler_finalize(descriptor),
+ CGDOMJSProxyHandler_getElementIfPresent(descriptor),
+ CGDOMJSProxyHandler_getInstance()])
+ CGClass.__init__(self, 'DOMProxyHandler',
+ bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
+ constructors=constructors,
+ methods=methods)
+
+def stripTrailingWhitespace(text):
+ tail = '\n' if text.endswith('\n') else ''
+ lines = text.splitlines()
+ for i in range(len(lines)):
+ lines[i] = lines[i].rstrip()
+ return '\n'.join(lines) + tail
+
+class CGDescriptor(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+
+ assert not descriptor.concrete or descriptor.interface.hasInterfacePrototypeObject()
+
+ cgThings = []
+ if descriptor.interface.hasInterfacePrototypeObject():
+ (hasMethod, hasGetter, hasLenientGetter,
+ hasSetter, hasLenientSetter) = False, False, False, False, False
+ for m in descriptor.interface.members:
+ if m.isMethod() and not m.isStatic() and not m.isIdentifierLess():
+ cgThings.append(CGSpecializedMethod(descriptor, m))
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ hasMethod = True
+ elif m.isAttr():
+ cgThings.append(CGSpecializedGetter(descriptor, m))
+ if m.hasLenientThis():
+ hasLenientGetter = True
+ else:
+ hasGetter = True
+ if not m.readonly:
+ cgThings.append(CGSpecializedSetter(descriptor, m))
+ if m.hasLenientThis():
+ hasLenientSetter = True
+ else:
+ hasSetter = True
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ if hasMethod: cgThings.append(CGGenericMethod(descriptor))
+ if hasGetter: cgThings.append(CGGenericGetter(descriptor))
+ if hasLenientGetter: cgThings.append(CGGenericGetter(descriptor,
+ lenientThis=True))
+ if hasSetter: cgThings.append(CGGenericSetter(descriptor))
+ if hasLenientSetter: cgThings.append(CGGenericSetter(descriptor,
+ lenientThis=True))
+
+ if descriptor.concrete and not descriptor.proxy:
+ if not descriptor.workers and descriptor.wrapperCache:
+ cgThings.append(CGAddPropertyHook(descriptor))
+
+ # Always have a finalize hook, regardless of whether the class wants a
+ # custom hook.
+ cgThings.append(CGClassFinalizeHook(descriptor))
+
+ # Only generate a trace hook if the class wants a custom hook.
+ if (descriptor.customTrace):
+ cgThings.append(CGClassTraceHook(descriptor))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGClassConstructHook(descriptor))
+ cgThings.append(CGClassHasInstanceHook(descriptor))
+ cgThings.append(CGInterfaceObjectJSClass(descriptor))
+
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGPrototypeJSClass(descriptor))
+
+ properties = PropertyArrays(descriptor)
+ cgThings.append(CGGeneric(define=str(properties)))
+ cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGGetProtoObjectMethod(descriptor))
+ else:
+ cgThings.append(CGGetConstructorObjectMethod(descriptor))
+
+ # Set up our Xray callbacks as needed. Note that we don't need to do
+ # it in workers.
+ if (descriptor.interface.hasInterfacePrototypeObject() and
+ not descriptor.workers):
+ if descriptor.concrete and descriptor.proxy:
+ cgThings.append(CGResolveOwnProperty(descriptor))
+ cgThings.append(CGEnumerateOwnProperties(descriptor))
+ cgThings.append(CGResolveProperty(descriptor, properties))
+ cgThings.append(CGEnumerateProperties(descriptor, properties))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
+ if (not descriptor.interface.isExternal() and
+ # Workers stuff is never pref-controlled
+ not descriptor.workers and
+ descriptor.interface.getExtendedAttribute("PrefControlled") is not None):
+ cgThings.append(CGPrefEnabled(descriptor))
+
+ if descriptor.interface.hasInterfacePrototypeObject():
+ cgThings.append(CGNativePropertyHooks(descriptor))
+
+ if descriptor.concrete:
+ if descriptor.proxy:
+ cgThings.append(CGProxyIsProxy(descriptor))
+ cgThings.append(CGProxyUnwrap(descriptor))
+ cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
+ cgThings.append(CGDOMJSProxyHandler(descriptor))
+ cgThings.append(CGIsMethod(descriptor))
+ else:
+ cgThings.append(CGDOMJSClass(descriptor))
+
+ if descriptor.wrapperCache:
+ cgThings.append(CGWrapWithCacheMethod(descriptor))
+ cgThings.append(CGWrapMethod(descriptor))
+ else:
+ cgThings.append(CGWrapNonWrapperCacheMethod(descriptor))
+
+ cgThings = CGList((CGIndenter(t, declareOnly=True) for t in cgThings), "\n")
+ cgThings = CGWrapper(cgThings, pre='\n', post='\n')
+ self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
+ cgThings),
+ post='\n')
+
+ def declare(self):
+ return self.cgRoot.declare()
+ def define(self):
+ return self.cgRoot.define()
+
+class CGNamespacedEnum(CGThing):
+ def __init__(self, namespace, enumName, names, values, comment=""):
+
+ if not values:
+ values = []
+
+ # Account for explicit enum values.
+ entries = []
+ for i in range(0, len(names)):
+ if len(values) > i and values[i] is not None:
+ entry = "%s = %s" % (names[i], values[i])
+ else:
+ entry = names[i]
+ entries.append(entry)
+
+ # Append a Count.
+ entries.append('_' + enumName + '_Count')
+
+ # Indent.
+ entries = [' ' + e for e in entries]
+
+ # Build the enum body.
+ enumstr = comment + 'enum %s\n{\n%s\n};\n' % (enumName, ',\n'.join(entries))
+ curr = CGGeneric(declare=enumstr)
+
+ # Add some whitespace padding.
+ curr = CGWrapper(curr, pre='\n',post='\n')
+
+ # Add the namespace.
+ curr = CGNamespace(namespace, curr)
+
+ # Add the typedef
+ typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName)
+ curr = CGList([curr, CGGeneric(declare=typedef)])
+
+ # Save the result.
+ self.node = curr
+
+ def declare(self):
+ return self.node.declare()
+ def define(self):
+ assert False # Only for headers.
+
+class CGDictionary(CGThing):
+ def __init__(self, dictionary, descriptorProvider):
+ self.dictionary = dictionary;
+ self.workers = descriptorProvider.workers
+ if all(CGDictionary(d, descriptorProvider).generatable for
+ d in CGDictionary.getDictionaryDependencies(dictionary)):
+ self.generatable = True
+ else:
+ self.generatable = False
+ # Nothing else to do here
+ return
+ # Getting a conversion template for interface types can fail
+ # if we don't have a relevant descriptor when self.workers is True.
+ # If that happens, just mark ourselves as not being
+ # generatable and move on.
+ try:
+ self.memberInfo = [
+ (member,
+ getJSToNativeConversionTemplate(member.type,
+ descriptorProvider,
+ isMember=True,
+ isOptional=(not member.defaultValue),
+ defaultValue=member.defaultValue))
+ for member in dictionary.members ]
+ except NoSuchDescriptorError, err:
+ if not self.workers:
+ raise err
+ self.generatable = False
+
+ def declare(self):
+ if not self.generatable:
+ return ""
+ d = self.dictionary
+ if d.parent:
+ inheritance = ": public %s " % self.makeClassName(d.parent)
+ else:
+ inheritance = ""
+ memberDecls = [" %s %s;" %
+ (self.getMemberType(m), m[0].identifier.name)
+ for m in self.memberInfo]
+
+ return (string.Template(
+ "struct ${selfName} ${inheritance}{\n"
+ " ${selfName}() {}\n"
+ " bool Init(JSContext* cx, const JS::Value& val);\n"
+ "\n" +
+ "\n".join(memberDecls) + "\n"
+ "private:\n"
+ " // Disallow copy-construction\n"
+ " ${selfName}(const ${selfName}&) MOZ_DELETE;\n" +
+ # NOTE: jsids are per-runtime, so don't use them in workers
+ (" static bool InitIds(JSContext* cx);\n"
+ " static bool initedIds;\n" if not self.workers else "") +
+ "\n".join(" static jsid " +
+ self.makeIdName(m.identifier.name) + ";" for
+ m in d.members) + "\n"
+ "};").substitute( { "selfName": self.makeClassName(d),
+ "inheritance": inheritance }))
+
+ def define(self):
+ if not self.generatable:
+ return ""
+ d = self.dictionary
+ if d.parent:
+ initParent = ("// Per spec, we init the parent's members first\n"
+ "if (!%s::Init(cx, val)) {\n"
+ " return false;\n"
+ "}\n" % self.makeClassName(d.parent))
+ else:
+ initParent = ""
+
+ memberInits = [CGIndenter(self.getMemberConversion(m)).define()
+ for m in self.memberInfo]
+ idinit = [CGGeneric('!InternJSString(cx, %s, "%s")' %
+ (m.identifier.name + "_id", m.identifier.name))
+ for m in d.members]
+ idinit = CGList(idinit, " ||\n")
+ idinit = CGWrapper(idinit, pre="if (",
+ post=(") {\n"
+ " return false;\n"
+ "}"),
+ reindent=True)
+
+ return string.Template(
+ # NOTE: jsids are per-runtime, so don't use them in workers
+ ("bool ${selfName}::initedIds = false;\n" +
+ "\n".join("jsid ${selfName}::%s = JSID_VOID;" %
+ self.makeIdName(m.identifier.name)
+ for m in d.members) + "\n"
+ "\n"
+ "bool\n"
+ "${selfName}::InitIds(JSContext* cx)\n"
+ "{\n"
+ " MOZ_ASSERT(!initedIds);\n"
+ "${idInit}\n"
+ " initedIds = true;\n"
+ " return true;\n"
+ "}\n"
+ "\n" if not self.workers else "") +
+ "bool\n"
+ "${selfName}::Init(JSContext* cx, const JS::Value& val)\n"
+ "{\n" +
+ # NOTE: jsids are per-runtime, so don't use them in workers
+ (" if (!initedIds && !InitIds(cx)) {\n"
+ " return false;\n"
+ " }\n" if not self.workers else "") +
+ "${initParent}"
+ " JSBool found;\n"
+ " JS::Value temp;\n"
+ " bool isNull = val.isNullOrUndefined();\n"
+ " if (!isNull && !val.isObject()) {\n"
+ " return Throw<${isMainThread}>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);\n"
+ " }\n"
+ "\n"
+ "${initMembers}\n"
+ " return true;\n"
+ "}").substitute({
+ "selfName": self.makeClassName(d),
+ "initParent": CGIndenter(CGGeneric(initParent)).define(),
+ "initMembers": "\n\n".join(memberInits),
+ "idInit": CGIndenter(idinit).define(),
+ "isMainThread": toStringBool(not self.workers)
+ })
+
+ @staticmethod
+ def makeDictionaryName(dictionary, workers):
+ suffix = "Workers" if workers else ""
+ return dictionary.identifier.name + suffix
+
+ def makeClassName(self, dictionary):
+ return self.makeDictionaryName(dictionary, self.workers)
+
+ def getMemberType(self, memberInfo):
+ (member, (templateBody, declType,
+ holderType, dealWithOptional)) = memberInfo
+ # We can't handle having a holderType here
+ assert holderType is None
+ if dealWithOptional:
+ declType = CGWrapper(declType, pre="Optional< ", post=" >")
+ return declType.define()
+
+ def getMemberConversion(self, memberInfo):
+ (member, (templateBody, declType,
+ holderType, dealWithOptional)) = memberInfo
+ replacements = { "val": "temp",
+ "valPtr": "&temp",
+ # Use this->%s to refer to members, because we don't
+ # control the member names and want to make sure we're
+ # talking about the member, not some local that
+ # shadows the member. Another option would be to move
+ # the guts of init to a static method which is passed
+ # an explicit reference to our dictionary object, so
+ # we couldn't screw this up even if we wanted to....
+ "declName": ("(this->%s)" % member.identifier.name),
+ # We need a holder name for external interfaces, but
+ # it's scoped down to the conversion so we can just use
+ # anything we want.
+ "holderName": "holder"}
+ # We can't handle having a holderType here
+ assert holderType is None
+ if dealWithOptional:
+ replacements["declName"] = "(" + replacements["declName"] + ".Value())"
+ if member.defaultValue:
+ replacements["haveValue"] = "found"
+
+ # NOTE: jsids are per-runtime, so don't use them in workers
+ if self.workers:
+ propName = member.identifier.name
+ propCheck = ('JS_HasProperty(cx, &val.toObject(), "%s", &found)' %
+ propName)
+ propGet = ('JS_GetProperty(cx, &val.toObject(), "%s", &temp)' %
+ propName)
+ else:
+ propId = self.makeIdName(member.identifier.name);
+ propCheck = ("JS_HasPropertyById(cx, &val.toObject(), %s, &found)" %
+ propId)
+ propGet = ("JS_GetPropertyById(cx, &val.toObject(), %s, &temp)" %
+ propId)
+
+ conversionReplacements = {
+ "prop": "(this->%s)" % member.identifier.name,
+ "convert": string.Template(templateBody).substitute(replacements),
+ "propCheck": propCheck,
+ "propGet": propGet
+ }
+ conversion = ("if (isNull) {\n"
+ " found = false;\n"
+ "} else if (!${propCheck}) {\n"
+ " return false;\n"
+ "}\n")
+ if member.defaultValue:
+ conversion += (
+ "if (found) {\n"
+ " if (!${propGet}) {\n"
+ " return false;\n"
+ " }\n"
+ "}\n"
+ "${convert}")
+ else:
+ conversion += (
+ "if (found) {\n"
+ " ${prop}.Construct();\n"
+ " if (!${propGet}) {\n"
+ " return false;\n"
+ " }\n"
+ "${convert}\n"
+ "}")
+ conversionReplacements["convert"] = CGIndenter(
+ CGGeneric(conversionReplacements["convert"])).define()
+
+ return CGGeneric(
+ string.Template(conversion).substitute(conversionReplacements)
+ )
+
+ @staticmethod
+ def makeIdName(name):
+ return name + "_id"
+
+ @staticmethod
+ def getDictionaryDependencies(dictionary):
+ deps = set();
+ if dictionary.parent:
+ deps.add(dictionary.parent)
+ for member in dictionary.members:
+ if member.type.isDictionary():
+ deps.add(member.type.unroll().inner)
+ return deps
+
+
+class CGRegisterProtos(CGAbstractMethod):
+ def __init__(self, config):
+ CGAbstractMethod.__init__(self, None, 'Register', 'void',
+ [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
+ self.config = config
+
+ def _defineMacro(self):
+ return """
+#define REGISTER_PROTO(_dom_class, _pref_check) \\
+ aNameSpaceManager->RegisterDefineDOMInterface(NS_LITERAL_STRING(#_dom_class), _dom_class##Binding::DefineDOMInterface, _pref_check);\n\n"""
+ def _undefineMacro(self):
+ return "\n#undef REGISTER_PROTO"
+ def _registerProtos(self):
+ def getPrefCheck(desc):
+ if desc.interface.getExtendedAttribute("PrefControlled") is None:
+ return "nullptr"
+ return "%sBinding::PrefEnabled" % desc.name
+ lines = ["REGISTER_PROTO(%s, %s);" % (desc.name, getPrefCheck(desc))
+ for desc in self.config.getDescriptors(hasInterfaceObject=True,
+ isExternal=False,
+ workers=False,
+ register=True)]
+ return '\n'.join(lines) + '\n'
+ def definition_body(self):
+ return self._defineMacro() + self._registerProtos() + self._undefineMacro()
+
+class CGBindingRoot(CGThing):
+ """
+ Root codegen class for binding generation. Instantiate the class, and call
+ declare or define to generate header or cpp code (respectively).
+ """
+ def __init__(self, config, prefix, webIDLFile):
+ descriptors = config.getDescriptors(webIDLFile=webIDLFile,
+ hasInterfaceOrInterfacePrototypeObject=True)
+ dictionaries = config.getDictionaries(webIDLFile)
+
+ forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')]
+
+ descriptorsForForwardDeclaration = list(descriptors)
+ for dictionary in dictionaries:
+ curDict = dictionary
+ ifacemembers = []
+ while curDict:
+ ifacemembers.extend([m.type.unroll().inner for m
+ in curDict.members
+ if m.type.unroll().isInterface()])
+ curDict = curDict.parent
+ # Put in all the non-worker descriptors
+ descriptorsForForwardDeclaration.extend(
+ [config.getDescriptor(iface.identifier.name, False) for
+ iface in ifacemembers])
+ # And now the worker ones. But these may not exist, so we
+ # have to be more careful.
+ for iface in ifacemembers:
+ try:
+ descriptorsForForwardDeclaration.append(
+ config.getDescriptor(iface.identifier.name, True))
+ except NoSuchDescriptorError:
+ # just move along
+ pass
+
+ for x in descriptorsForForwardDeclaration:
+ nativeType = x.nativeType
+ components = x.nativeType.split('::')
+ className = components[-1]
+ # JSObject is a struct, not a class
+ declare = CGClassForwardDeclare(className, className is "JSObject")
+ if len(components) > 1:
+ declare = CGNamespace.build(components[:-1],
+ CGWrapper(declare, declarePre='\n',
+ declarePost='\n'),
+ declareOnly=True)
+ forwardDeclares.append(CGWrapper(declare, declarePost='\n'))
+
+ forwardDeclares = CGList(forwardDeclares)
+
+ descriptorsWithPrototype = filter(lambda d: d.interface.hasInterfacePrototypeObject(),
+ descriptors)
+ traitsClasses = [CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype]
+
+ # We must have a 1:1 mapping here, skip for prototypes that have more
+ # than one concrete class implementation.
+ traitsClasses.extend([CGPrototypeIDMapClass(d) for d in descriptorsWithPrototype
+ if d.uniqueImplementation])
+
+ # Wrap all of that in our namespaces.
+ if len(traitsClasses) > 0:
+ traitsClasses = CGNamespace.build(['mozilla', 'dom'],
+ CGWrapper(CGList(traitsClasses),
+ declarePre='\n'),
+ declareOnly=True)
+ traitsClasses = CGWrapper(traitsClasses, declarePost='\n')
+ else:
+ traitsClasses = None
+
+ # Do codegen for all the enums
+ def makeEnum(e):
+ return CGNamespace.build([e.identifier.name + "Values"],
+ CGEnum(e))
+ def makeEnumTypedef(e):
+ return CGGeneric(declare=("typedef %sValues::valuelist %s;\n" %
+ (e.identifier.name, e.identifier.name)))
+ cgthings = [ fun(e) for e in config.getEnums(webIDLFile)
+ for fun in [makeEnum, makeEnumTypedef] ]
+
+ # Do codegen for all the dictionaries. We have to be a bit careful
+ # here, because we have to generate these in order from least derived
+ # to most derived so that class inheritance works out. We also have to
+ # generate members before the dictionary that contains them.
+ #
+ # XXXbz this will fail if we have two webidl files A and B such that A
+ # declares a dictionary which inherits from a dictionary in B and B
+ # declares a dictionary (possibly a different one!) that inherits from a
+ # dictionary in A. The good news is that I expect this to never happen.
+ reSortedDictionaries = []
+ dictionaries = set(dictionaries)
+ while len(dictionaries) != 0:
+ # Find the dictionaries that don't depend on anything else anymore
+ # and move them over.
+ toMove = [d for d in dictionaries if
+ len(CGDictionary.getDictionaryDependencies(d) &
+ dictionaries) == 0]
+ if len(toMove) == 0:
+ raise TypeError("Loop in dictionary dependency graph")
+ dictionaries = dictionaries - set(toMove)
+ reSortedDictionaries.extend(toMove)
+
+ dictionaries = reSortedDictionaries
+ cgthings.extend([CGDictionary(d, config.getDescriptorProvider(True))
+ for d in dictionaries])
+ cgthings.extend([CGDictionary(d, config.getDescriptorProvider(False))
+ for d in dictionaries])
+
+ # Do codegen for all the descriptors
+ cgthings.extend([CGDescriptor(x) for x in descriptors])
+
+ # And make sure we have the right number of newlines at the end
+ curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'],
+ CGWrapper(curr, pre="\n"))
+
+ curr = CGList([forwardDeclares,
+ CGWrapper(CGGeneric("using namespace mozilla::dom;"),
+ defineOnly=True),
+ traitsClasses, curr],
+ "\n")
+
+ # Add header includes.
+ curr = CGHeaders(descriptors,
+ dictionaries,
+ ['mozilla/dom/BindingUtils.h',
+ 'mozilla/dom/DOMJSClass.h',
+ 'mozilla/dom/DOMJSProxyHandler.h'],
+ ['mozilla/dom/Nullable.h',
+ 'PrimitiveConversions.h',
+ 'XPCQuickStubs.h',
+ 'nsDOMQS.h',
+ 'AccessCheck.h',
+ 'WorkerPrivate.h',
+ 'nsContentUtils.h',
+ 'mozilla/Preferences.h',
+ # Have to include nsDOMQS.h to get fast arg unwrapping
+ # for old-binding things with castability.
+ 'nsDOMQS.h'
+ ],
+ curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard(prefix, curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Store the final result.
+ self.root = curr
+
+ def declare(self):
+ return stripTrailingWhitespace(self.root.declare())
+ def define(self):
+ return stripTrailingWhitespace(self.root.define())
+
+
+class GlobalGenRoots():
+ """
+ Roots for global codegen.
+
+ To generate code, call the method associated with the target, and then
+ call the appropriate define/declare method.
+ """
+
+ @staticmethod
+ def PrototypeList(config):
+
+ # Prototype ID enum.
+ protos = [d.name for d in config.getDescriptors(hasInterfacePrototypeObject=True)]
+ idEnum = CGNamespacedEnum('id', 'ID', protos, [0])
+ idEnum = CGList([idEnum])
+ idEnum.append(CGGeneric(declare="const unsigned MaxProtoChainLength = " +
+ str(config.maxProtoChainLength) + ";\n\n"))
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'],
+ CGWrapper(idEnum, pre='\n'))
+ idEnum = CGWrapper(idEnum, post='\n')
+
+ curr = CGList([idEnum])
+
+ # Constructor ID enum.
+ constructors = [d.name for d in config.getDescriptors(hasInterfaceObject=True,
+ hasInterfacePrototypeObject=False)]
+ idEnum = CGNamespacedEnum('id', 'ID', constructors, [0])
+
+ # Wrap all of that in our namespaces.
+ idEnum = CGNamespace.build(['mozilla', 'dom', 'constructors'],
+ CGWrapper(idEnum, pre='\n'))
+ idEnum = CGWrapper(idEnum, post='\n')
+
+ curr.append(idEnum)
+
+ traitsDecl = CGGeneric(declare="""
+template <prototypes::ID PrototypeID>
+struct PrototypeTraits;
+
+template <class ConcreteClass>
+struct PrototypeIDMap;
+""")
+
+ traitsDecl = CGNamespace.build(['mozilla', 'dom'],
+ CGWrapper(traitsDecl, post='\n'))
+
+ curr.append(traitsDecl)
+
+ # Add include guards.
+ curr = CGIncludeGuard('PrototypeList', curr)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def RegisterBindings(config):
+
+ # TODO - Generate the methods we want
+ curr = CGRegisterProtos(config)
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'],
+ CGWrapper(curr, post='\n'))
+ curr = CGWrapper(curr, post='\n')
+
+ # Add the includes
+ defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
+ for desc in config.getDescriptors(hasInterfaceObject=True,
+ workers=False,
+ register=True)]
+ defineIncludes.append('nsScriptNameSpaceManager.h')
+ curr = CGHeaders([], [], [], defineIncludes, curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('RegisterBindings', curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionTypes(config):
+
+ (includes, declarations, unions) = UnionTypes(config.getDescriptors())
+ includes.add("mozilla/dom/BindingUtils.h")
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'], unions)
+
+ curr = CGWrapper(curr, post='\n')
+
+ namespaces = []
+ stack = [CGList([])]
+ for (clazz, isStruct) in SortedTuples(declarations):
+ elements = clazz.split("::")
+ clazz = CGClassForwardDeclare(elements.pop(), isStruct=isStruct)
+ i = 0
+ if len(elements) > 0:
+ common = min(len(namespaces), len(elements))
+ while i < common and namespaces[i] == elements[i]:
+ i += 1
+
+ # pop all the namespaces that should be closed
+ namespaces = namespaces[:i]
+
+ # add all the namespaces that should be opened
+ for j, namespace in enumerate(elements[i:]):
+ namespaces.append(namespace)
+ # every CGNamespace that we add holds a CGList
+ list = CGList([])
+ # add the new namespace to the list on top of the stack
+ stack[i + j].append(CGNamespace(namespace, list))
+ # set the top of the namespace stack to the list of the new
+ # namespace
+ stack[i + j + 1:] = [list]
+
+ stack[len(elements)].append(clazz)
+
+ curr = CGList([stack[0], curr], "\n")
+
+ curr = CGHeaders([], [], includes, [], curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('UnionTypes', curr)
+
+ # Done.
+ return curr
+
+ @staticmethod
+ def UnionConversions(config):
+
+ unions = UnionConversions(config.getDescriptors())
+
+ # Wrap all of that in our namespaces.
+ curr = CGNamespace.build(['mozilla', 'dom'], unions)
+
+ curr = CGWrapper(curr, post='\n')
+
+ curr = CGHeaders([], [], ["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h"], [], curr)
+
+ # Add include guards.
+ curr = CGIncludeGuard('UnionConversions', curr)
+
+ # Done.
+ return curr
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py
new file mode 100644
index 00000000000..1666589940e
--- /dev/null
+++ b/components/script/dom/bindings/codegen/CodegenRust.py
@@ -0,0 +1,5534 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Common codegen classes.
+
+import operator
+import os
+import re
+import string
+
+from WebIDL import (
+ BuiltinTypes,
+ IDLBuiltinType,
+ IDLNullValue,
+ IDLType,
+ IDLUndefinedValue,
+)
+
+from Configuration import getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback
+
+AUTOGENERATED_WARNING_COMMENT = \
+ "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
+ADDPROPERTY_HOOK_NAME = '_addProperty'
+FINALIZE_HOOK_NAME = '_finalize'
+TRACE_HOOK_NAME = '_trace'
+CONSTRUCT_HOOK_NAME = '_constructor'
+HASINSTANCE_HOOK_NAME = '_hasInstance'
+
+def replaceFileIfChanged(filename, newContents):
+ """
+ Read a copy of the old file, so that we don't touch it if it hasn't changed.
+ Returns True if the file was updated, false otherwise.
+ """
+ #XXXjdm This doesn't play well with make right now.
+ # Force the file to always be updated, or else changing CodegenRust.py
+ # will cause many autogenerated bindings to be regenerated perpetually
+ # until the result is actually different.
+
+ #oldFileContents = ""
+ #try:
+ # oldFile = open(filename, 'rb')
+ # oldFileContents = ''.join(oldFile.readlines())
+ # oldFile.close()
+ #except:
+ # pass
+
+ #if newContents == oldFileContents:
+ # return False
+
+ f = open(filename, 'wb')
+ f.write(newContents)
+ f.close()
+
+ return True
+
+def toStringBool(arg):
+ return str(not not arg).lower()
+
+def toBindingNamespace(arg):
+ return re.sub("((_workers)?$)", "Binding\\1", arg);
+
+def stripTrailingWhitespace(text):
+ tail = '\n' if text.endswith('\n') else ''
+ lines = text.splitlines()
+ for i in range(len(lines)):
+ lines[i] = lines[i].rstrip()
+ return '\n'.join(lines) + tail
+
+def MakeNativeName(name):
+ return name[0].upper() + name[1:]
+
+builtinNames = {
+ IDLType.Tags.bool: 'bool',
+ IDLType.Tags.int8: 'i8',
+ IDLType.Tags.int16: 'i16',
+ IDLType.Tags.int32: 'i32',
+ IDLType.Tags.int64: 'i64',
+ IDLType.Tags.uint8: 'u8',
+ IDLType.Tags.uint16: 'u16',
+ IDLType.Tags.uint32: 'u32',
+ IDLType.Tags.uint64: 'u64',
+ IDLType.Tags.float: 'f32',
+ IDLType.Tags.double: 'f64'
+}
+
+numericTags = [
+ IDLType.Tags.int8, IDLType.Tags.uint8,
+ IDLType.Tags.int16, IDLType.Tags.uint16,
+ IDLType.Tags.int32, IDLType.Tags.uint32,
+ IDLType.Tags.int64, IDLType.Tags.uint64,
+ IDLType.Tags.float, IDLType.Tags.double
+ ]
+
+class CastableObjectUnwrapper():
+ """
+ A class for unwrapping an object named by the "source" argument
+ based on the passed-in descriptor. Stringifies to a Rust expression of
+ the appropriate type.
+
+ codeOnFailure is the code to run if unwrapping fails.
+ """
+ def __init__(self, descriptor, source, codeOnFailure):
+ self.substitution = {
+ "type": descriptor.nativeType,
+ "depth": descriptor.interface.inheritanceDepth(),
+ "prototype": "PrototypeList::id::" + descriptor.name,
+ "protoID": "PrototypeList::id::" + descriptor.name + " as uint",
+ "source": source,
+ "codeOnFailure": CGIndenter(CGGeneric(codeOnFailure), 4).define(),
+ }
+
+ def __str__(self):
+ return string.Template(
+"""match unwrap_jsmanaged(${source}, ${prototype}, ${depth}) {
+ Ok(val) => val,
+ Err(()) => {
+${codeOnFailure}
+ }
+}""").substitute(self.substitution)
+
+
+class CGThing():
+ """
+ Abstract base class for things that spit out code.
+ """
+ def __init__(self):
+ pass # Nothing for now
+
+ def define(self):
+ """Produce code for a Rust file."""
+ assert(False) # Override me!
+
+
+class CGNativePropertyHooks(CGThing):
+ """
+ Generate a NativePropertyHooks for a given descriptor
+ """
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.properties = properties
+
+ def define(self):
+ parent = self.descriptor.interface.parent
+ if parent:
+ parentHooks = "Some(&::dom::bindings::codegen::Bindings::%sBinding::sNativePropertyHooks)" % parent.identifier.name
+ else:
+ parentHooks = "None"
+
+ substitutions = {
+ "parentHooks": parentHooks
+ }
+
+ return string.Template(
+ "pub static sNativePropertyHooks: NativePropertyHooks = NativePropertyHooks {\n"
+ " native_properties: &sNativeProperties,\n"
+ " proto_hooks: ${parentHooks},\n"
+ "};\n").substitute(substitutions)
+
+
+class CGMethodCall(CGThing):
+ """
+ A class to generate selection of a method signature from a set of
+ signatures and generation of a call to that signature.
+ """
+ def __init__(self, argsPre, nativeMethodName, static, descriptor, method):
+ CGThing.__init__(self)
+
+ methodName = '\\"%s.%s\\"' % (descriptor.interface.identifier.name, method.identifier.name)
+
+ def requiredArgCount(signature):
+ arguments = signature[1]
+ if len(arguments) == 0:
+ return 0
+ requiredArgs = len(arguments)
+ while requiredArgs and arguments[requiredArgs-1].optional:
+ requiredArgs -= 1
+ return requiredArgs
+
+ def getPerSignatureCall(signature, argConversionStartsAt=0, signatureIndex=0):
+ return CGPerSignatureCall(signature[0], argsPre, signature[1],
+ nativeMethodName + '_'*signatureIndex,
+ static, descriptor,
+ method, argConversionStartsAt)
+
+
+ signatures = method.signatures()
+ if len(signatures) == 1:
+ # Special case: we can just do a per-signature method call
+ # here for our one signature and not worry about switching
+ # on anything.
+ signature = signatures[0]
+ self.cgRoot = CGList([getPerSignatureCall(signature)])
+ requiredArgs = requiredArgCount(signature)
+
+
+ if requiredArgs > 0:
+ code = (
+ "if argc < %d {\n"
+ " throw_type_error(cx, \"Not enough arguments to %s.\");\n"
+ " return 0;\n"
+ "}" % (requiredArgs, methodName))
+ self.cgRoot.prepend(
+ CGWrapper(CGGeneric(code), pre="\n", post="\n"))
+
+ return
+
+ # Need to find the right overload
+ maxArgCount = method.maxArgCount
+ allowedArgCounts = method.allowedArgCounts
+
+ argCountCases = []
+ for argCount in allowedArgCounts:
+ possibleSignatures = method.signaturesForArgCount(argCount)
+ if len(possibleSignatures) == 1:
+ # easy case!
+ signature = possibleSignatures[0]
+
+
+ sigIndex = signatures.index(signature)
+ argCountCases.append(
+ CGCase(str(argCount), getPerSignatureCall(signature,
+ signatureIndex=sigIndex)))
+ continue
+
+ distinguishingIndex = method.distinguishingIndexForArgCount(argCount)
+
+ # We can't handle unions at the distinguishing index.
+ for (returnType, args) in possibleSignatures:
+ if args[distinguishingIndex].type.isUnion():
+ raise TypeError("No support for unions as distinguishing "
+ "arguments yet: %s",
+ args[distinguishingIndex].location)
+
+ # Convert all our arguments up to the distinguishing index.
+ # Doesn't matter which of the possible signatures we use, since
+ # they all have the same types up to that point; just use
+ # possibleSignatures[0]
+ caseBody = [CGGeneric("let argv_start = JS_ARGV(cx, vp);")]
+ caseBody.extend([ CGArgumentConverter(possibleSignatures[0][1][i],
+ i, "argv_start", "argc",
+ descriptor) for i in
+ range(0, distinguishingIndex) ])
+
+ # Select the right overload from our set.
+ distinguishingArg = "(*argv_start.offset(%d))" % distinguishingIndex
+
+ def pickFirstSignature(condition, filterLambda):
+ sigs = filter(filterLambda, possibleSignatures)
+ assert len(sigs) < 2
+ if len(sigs) > 0:
+ if condition is None:
+ caseBody.append(
+ getPerSignatureCall(sigs[0], distinguishingIndex,
+ possibleSignatures.index(sigs[0])))
+ else:
+ caseBody.append(CGGeneric("if " + condition + " {"))
+ caseBody.append(CGIndenter(
+ getPerSignatureCall(sigs[0], distinguishingIndex,
+ possibleSignatures.index(sigs[0]))))
+ caseBody.append(CGGeneric("}"))
+ return True
+ return False
+
+ # First check for null or undefined
+ pickFirstSignature("%s.isNullOrUndefined()" % distinguishingArg,
+ lambda s: (s[1][distinguishingIndex].type.nullable() or
+ s[1][distinguishingIndex].type.isDictionary()))
+
+ # Now check for distinguishingArg being an object that implements a
+ # non-callback interface. That includes typed arrays and
+ # arraybuffers.
+ interfacesSigs = [
+ s for s in possibleSignatures
+ if (s[1][distinguishingIndex].type.isObject() or
+ s[1][distinguishingIndex].type.isNonCallbackInterface()) ]
+ # There might be more than one of these; we need to check
+ # which ones we unwrap to.
+
+ if len(interfacesSigs) > 0:
+ # The spec says that we should check for "platform objects
+ # implementing an interface", but it's enough to guard on these
+ # being an object. The code for unwrapping non-callback
+ # interfaces and typed arrays will just bail out and move on to
+ # the next overload if the object fails to unwrap correctly. We
+ # could even not do the isObject() check up front here, but in
+ # cases where we have multiple object overloads it makes sense
+ # to do it only once instead of for each overload. That will
+ # also allow the unwrapping test to skip having to do codegen
+ # for the null-or-undefined case, which we already handled
+ # above.
+ caseBody.append(CGGeneric("if (%s).is_object() {" %
+ (distinguishingArg)))
+ for idx, sig in enumerate(interfacesSigs):
+ caseBody.append(CGIndenter(CGGeneric("loop {")));
+ type = sig[1][distinguishingIndex].type
+
+ # The argument at index distinguishingIndex can't possibly
+ # be unset here, because we've already checked that argc is
+ # large enough that we can examine this argument.
+ template, _, declType, needsRooting = getJSToNativeConversionTemplate(
+ type, descriptor, failureCode="break;", isDefinitelyObject=True)
+
+ testCode = instantiateJSToNativeConversionTemplate(
+ template,
+ {"val": distinguishingArg},
+ declType,
+ "arg%d" % distinguishingIndex,
+ needsRooting)
+
+ # Indent by 4, since we need to indent further than our "do" statement
+ caseBody.append(CGIndenter(testCode, 4));
+ # If we got this far, we know we unwrapped to the right
+ # interface, so just do the call. Start conversion with
+ # distinguishingIndex + 1, since we already converted
+ # distinguishingIndex.
+ caseBody.append(CGIndenter(
+ getPerSignatureCall(sig, distinguishingIndex + 1, idx), 4))
+ caseBody.append(CGIndenter(CGGeneric("}")))
+
+ caseBody.append(CGGeneric("}"))
+
+ # XXXbz Now we're supposed to check for distinguishingArg being
+ # an array or a platform object that supports indexed
+ # properties... skip that last for now. It's a bit of a pain.
+ pickFirstSignature("%s.isObject() && IsArrayLike(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s:
+ (s[1][distinguishingIndex].type.isArray() or
+ s[1][distinguishingIndex].type.isSequence() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # Check for Date objects
+ # XXXbz Do we need to worry about security wrappers around the Date?
+ pickFirstSignature("%s.isObject() && JS_ObjectIsDate(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s: (s[1][distinguishingIndex].type.isDate() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # Check for vanilla JS objects
+ # XXXbz Do we need to worry about security wrappers?
+ pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" %
+ (distinguishingArg, distinguishingArg),
+ lambda s: (s[1][distinguishingIndex].type.isCallback() or
+ s[1][distinguishingIndex].type.isCallbackInterface() or
+ s[1][distinguishingIndex].type.isDictionary() or
+ s[1][distinguishingIndex].type.isObject()))
+
+ # The remaining cases are mutually exclusive. The
+ # pickFirstSignature calls are what change caseBody
+ # Check for strings or enums
+ if pickFirstSignature(None,
+ lambda s: (s[1][distinguishingIndex].type.isString() or
+ s[1][distinguishingIndex].type.isEnum())):
+ pass
+ # Check for primitives
+ elif pickFirstSignature(None,
+ lambda s: s[1][distinguishingIndex].type.isPrimitive()):
+ pass
+ # Check for "any"
+ elif pickFirstSignature(None,
+ lambda s: s[1][distinguishingIndex].type.isAny()):
+ pass
+ else:
+ # Just throw; we have no idea what we're supposed to
+ # do with this.
+ caseBody.append(CGGeneric("return Throw(cx, NS_ERROR_XPC_BAD_CONVERT_JS);"))
+
+ argCountCases.append(CGCase(str(argCount),
+ CGList(caseBody, "\n")))
+
+ overloadCGThings = []
+ overloadCGThings.append(
+ CGGeneric("let argcount = cmp::min(argc, %d);" %
+ maxArgCount))
+ overloadCGThings.append(
+ CGSwitch("argcount",
+ argCountCases,
+ CGGeneric("throw_type_error(cx, \"Not enough arguments to %s.\");\n"
+ "return 0;\n" % methodName)))
+ #XXXjdm Avoid unreachable statement warnings
+ #overloadCGThings.append(
+ # CGGeneric('fail!("We have an always-returning default case");\n'
+ # 'return 0;'))
+ self.cgRoot = CGWrapper(CGList(overloadCGThings, "\n"),
+ pre="\n")
+
+ def define(self):
+ return self.cgRoot.define()
+
+class FakeCastableDescriptor():
+ def __init__(self, descriptor):
+ self.nativeType = "*const %s" % descriptor.concreteType
+ self.name = descriptor.name
+ class FakeInterface:
+ def inheritanceDepth(self):
+ return descriptor.interface.inheritanceDepth()
+ self.interface = FakeInterface()
+
+def dictionaryHasSequenceMember(dictionary):
+ return (any(typeIsSequenceOrHasSequenceMember(m.type) for m in
+ dictionary.members) or
+ (dictionary.parent and
+ dictionaryHasSequenceMember(dictionary.parent)))
+
+def typeIsSequenceOrHasSequenceMember(type):
+ if type.nullable():
+ type = type.inner
+ if type.isSequence():
+ return True
+ if type.isArray():
+ elementType = type.inner
+ return typeIsSequenceOrHasSequenceMember(elementType)
+ if type.isDictionary():
+ return dictionaryHasSequenceMember(type.inner)
+ if type.isUnion():
+ return any(typeIsSequenceOrHasSequenceMember(m.type) for m in
+ type.flatMemberTypes)
+ return False
+
+def typeNeedsRooting(type, descriptorProvider):
+ return type.isGeckoInterface() and descriptorProvider.getDescriptor(type.name).needsRooting
+
+def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
+ isDefinitelyObject=False,
+ isMember=False,
+ isArgument=False,
+ invalidEnumValueFatal=True,
+ defaultValue=None,
+ treatNullAs="Default",
+ isEnforceRange=False,
+ isClamp=False,
+ exceptionCode=None,
+ allowTreatNonObjectAsNull=False,
+ isCallbackReturnValue=False,
+ sourceDescription="value"):
+ """
+ Get a template for converting a JS value to a native object based on the
+ given type and descriptor. If failureCode is given, then we're actually
+ testing whether we can convert the argument to the desired type. That
+ means that failures to convert due to the JS value being the wrong type of
+ value need to use failureCode instead of throwing exceptions. Failures to
+ convert that are due to JS exceptions (from toString or valueOf methods) or
+ out of memory conditions need to throw exceptions no matter what
+ failureCode is.
+
+ If isDefinitelyObject is True, that means we know the value
+ isObject() and we have no need to recheck that.
+
+ if isMember is True, we're being converted from a property of some
+ JS object, not from an actual method argument, so we can't rely on
+ our jsval being rooted or outliving us in any way. Any caller
+ passing true needs to ensure that it is handled correctly in
+ typeIsSequenceOrHasSequenceMember.
+
+ invalidEnumValueFatal controls whether an invalid enum value conversion
+ attempt will throw (if true) or simply return without doing anything (if
+ false).
+
+ If defaultValue is not None, it's the IDL default value for this conversion
+
+ If isEnforceRange is true, we're converting an integer and throwing if the
+ value is out of range.
+
+ If isClamp is true, we're converting an integer and clamping if the
+ value is out of range.
+
+ If allowTreatNonObjectAsNull is true, then [TreatNonObjectAsNull]
+ extended attributes on nullable callback functions will be honored.
+
+ The return value from this function is a tuple consisting of four things:
+
+ 1) A string representing the conversion code. This will have template
+ substitution performed on it as follows:
+
+ ${val} replaced by an expression for the JS::Value in question
+
+ 2) A string or None representing Rust code for the default value (if any).
+
+ 3) A CGThing representing the native C++ type we're converting to
+ (declType). This is allowed to be None if the conversion code is
+ supposed to be used as-is.
+
+ 4) A boolean indicating whether the caller has to root the result.
+
+ """
+ # We should not have a defaultValue if we know we're an object
+ assert(not isDefinitelyObject or defaultValue is None)
+
+ # If exceptionCode is not set, we'll just rethrow the exception we got.
+ # Note that we can't just set failureCode to exceptionCode, because setting
+ # failureCode will prevent pending exceptions from being set in cases when
+ # they really should be!
+ if exceptionCode is None:
+ exceptionCode = "return 0;"
+
+ needsRooting = typeNeedsRooting(type, descriptorProvider)
+
+ def handleOptional(template, declType, default):
+ assert (defaultValue is None) == (default is None)
+ return (template, default, declType, needsRooting)
+
+ # Unfortunately, .capitalize() on a string will lowercase things inside the
+ # string, which we do not want.
+ def firstCap(string):
+ return string[0].upper() + string[1:]
+
+ # Helper functions for dealing with failures due to the JS value being the
+ # wrong type of value
+ # Helper functions for dealing with failures due to the JS value being the
+ # wrong type of value
+ def onFailureNotAnObject(failureCode):
+ return CGWrapper(
+ CGGeneric(
+ failureCode or
+ ('throw_type_error(cx, "%s is not an object.");\n'
+ '%s' % (firstCap(sourceDescription), exceptionCode))),
+ post="\n")
+ def onFailureBadType(failureCode, typeName):
+ return CGWrapper(
+ CGGeneric(
+ failureCode or
+ ('throw_type_error(cx, \"%s does not implement interface %s.\");\n'
+ '%s' % (firstCap(sourceDescription), typeName,
+ exceptionCode))),
+ post="\n")
+ def onFailureNotCallable(failureCode):
+ return CGWrapper(
+ CGGeneric(
+ failureCode or
+ ('throw_type_error(cx, \"%s is not callable.\");\n'
+ '%s' % (firstCap(sourceDescription), exceptionCode))),
+ post="\n")
+
+
+ # A helper function for handling null default values. Checks that the
+ # default value, if it exists, is null.
+ def handleDefaultNull(nullValue):
+ if defaultValue is None:
+ return None
+
+ if not isinstance(defaultValue, IDLNullValue):
+ raise TypeError("Can't handle non-null default value here")
+
+ assert type.nullable() or type.isDictionary()
+ return nullValue
+
+ # A helper function for wrapping up the template body for
+ # possibly-nullable objecty stuff
+ def wrapObjectTemplate(templateBody, isDefinitelyObject, type,
+ failureCode=None):
+ if not isDefinitelyObject:
+ # Handle the non-object cases by wrapping up the whole
+ # thing in an if cascade.
+ templateBody = (
+ "if (${val}).is_object() {\n" +
+ CGIndenter(CGGeneric(templateBody)).define() + "\n")
+ if type.nullable():
+ templateBody += (
+ "} else if (${val}).is_null_or_undefined() {\n"
+ " None\n")
+ templateBody += (
+ "} else {\n" +
+ CGIndenter(onFailureNotAnObject(failureCode)).define() +
+ "}\n")
+
+ return templateBody
+
+ assert not (isEnforceRange and isClamp) # These are mutually exclusive
+
+ if type.isArray():
+ raise TypeError("Can't handle array arguments yet")
+
+ if type.isSequence():
+ raise TypeError("Can't handle sequence arguments yet")
+
+ if type.isUnion():
+ declType = CGGeneric(type.name + "::" + type.name)
+ if type.nullable():
+ declType = CGWrapper(declType, pre="Option<", post=" >")
+
+ templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
+ " Ok(value) => value,\n"
+ " Err(()) => { %s },\n"
+ "}" % exceptionCode)
+
+ return handleOptional(templateBody, declType, handleDefaultNull("None"))
+
+ if type.isGeckoInterface():
+ assert not isEnforceRange and not isClamp
+
+ descriptor = descriptorProvider.getDescriptor(
+ type.unroll().inner.identifier.name)
+
+ if descriptor.interface.isCallback():
+ name = descriptor.nativeType
+ declType = CGGeneric("Option<%s>" % name);
+ conversion = ("Some(%s::new((${val}).to_object()))" % name)
+
+ template = wrapObjectTemplate(conversion, isDefinitelyObject, type,
+ failureCode)
+ return handleOptional(template, declType, handleDefaultNull("None"))
+
+ if isMember:
+ descriptorType = descriptor.memberType
+ elif isArgument:
+ descriptorType = descriptor.argumentType
+ else:
+ descriptorType = descriptor.nativeType
+
+ templateBody = ""
+ if descriptor.interface.isConsequential():
+ raise TypeError("Consequential interface %s being used as an "
+ "argument" % descriptor.interface.identifier.name)
+
+ if failureCode is None:
+ substitutions = {
+ "sourceDescription": sourceDescription,
+ "interface": descriptor.interface.identifier.name,
+ "exceptionCode": exceptionCode,
+ }
+ unwrapFailureCode = string.Template(
+ 'throw_type_error(cx, "${sourceDescription} does not '
+ 'implement interface ${interface}.");\n'
+ '${exceptionCode}').substitute(substitutions)
+ else:
+ unwrapFailureCode = failureCode
+
+ templateBody = str(CastableObjectUnwrapper(
+ descriptor,
+ "(${val}).to_object()",
+ unwrapFailureCode))
+
+ declType = CGGeneric(descriptorType)
+ if type.nullable():
+ templateBody = "Some(%s)" % templateBody
+ declType = CGWrapper(declType, pre="Option<", post=">")
+
+ if isMember:
+ templateBody += ".root()"
+
+ templateBody = wrapObjectTemplate(templateBody, isDefinitelyObject,
+ type, failureCode)
+
+ return handleOptional(templateBody, declType, handleDefaultNull("None"))
+
+ if type.isSpiderMonkeyInterface():
+ raise TypeError("Can't handle SpiderMonkey interface arguments yet")
+
+ if type.isDOMString():
+ assert not isEnforceRange and not isClamp
+
+ treatAs = {
+ "Default": "Default",
+ "EmptyString": "Empty",
+ }
+ if treatNullAs not in treatAs:
+ raise TypeError("We don't support [TreatNullAs=%s]" % treatNullAs)
+ if type.nullable():
+ nullBehavior = "()"
+ else:
+ nullBehavior = treatAs[treatNullAs]
+
+ conversionCode = (
+ "match FromJSValConvertible::from_jsval(cx, ${val}, %s) {\n"
+ " Ok(strval) => strval,\n"
+ " Err(_) => { %s },\n"
+ "}" % (nullBehavior, exceptionCode))
+
+ if defaultValue is None:
+ default = None
+ elif isinstance(defaultValue, IDLNullValue):
+ assert type.nullable()
+ default = "None"
+ else:
+ assert defaultValue.type.tag() == IDLType.Tags.domstring
+ value = "str::from_utf8(data).unwrap().to_string()"
+ if type.nullable():
+ value = "Some(%s)" % value
+
+ default = (
+ "static data: [u8, ..%s] = [ %s ];\n"
+ "%s" %
+ (len(defaultValue.value) + 1,
+ ", ".join(["'" + char + "' as u8" for char in defaultValue.value] + ["0"]),
+ value))
+
+ declType = "DOMString"
+ if type.nullable():
+ declType = "Option<%s>" % declType
+
+ return handleOptional(conversionCode, CGGeneric(declType), default)
+
+ if type.isByteString():
+ assert not isEnforceRange and not isClamp
+
+ conversionCode = (
+ "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
+ " Ok(strval) => strval,\n"
+ " Err(_) => { %s },\n"
+ "}" % exceptionCode)
+
+ declType = CGGeneric("ByteString")
+ if type.nullable():
+ declType = CGWrapper(declType, pre="Option<", post=">")
+
+ return handleOptional(conversionCode, declType, handleDefaultNull("None"))
+
+ if type.isEnum():
+ assert not isEnforceRange and not isClamp
+
+ if type.nullable():
+ raise TypeError("We don't support nullable enumerated arguments "
+ "yet")
+ enum = type.inner.identifier.name
+ if invalidEnumValueFatal:
+ handleInvalidEnumValueCode = exceptionCode
+ else:
+ handleInvalidEnumValueCode = "return 1;"
+
+ template = (
+ "match FindEnumStringIndex(cx, ${val}, %(values)s) {\n"
+ " Err(_) => { %(exceptionCode)s },\n"
+ " Ok(None) => { %(handleInvalidEnumValueCode)s },\n"
+ " Ok(Some(index)) => {\n"
+ " //XXXjdm need some range checks up in here.\n"
+ " unsafe { mem::transmute(index) }\n"
+ " },\n"
+ "}" % { "values" : enum + "Values::strings",
+ "exceptionCode" : exceptionCode,
+"handleInvalidEnumValueCode" : handleInvalidEnumValueCode })
+
+ if defaultValue is not None:
+ assert(defaultValue.type.tag() == IDLType.Tags.domstring)
+ default = "%sValues::%s" % (enum, getEnumValueName(defaultValue.value))
+ else:
+ default = None
+
+ return handleOptional(template, CGGeneric(enum), default)
+
+ if type.isCallback():
+ assert not isEnforceRange and not isClamp
+ assert not type.treatNonCallableAsNull()
+ assert not type.treatNonObjectAsNull() or type.nullable()
+ assert not type.treatNonObjectAsNull() or not type.treatNonCallableAsNull()
+
+ declType = CGGeneric('%s::%s' % (type.unroll().module(), type.unroll().identifier.name))
+
+ conversion = CGCallbackTempRoot(declType.define())
+
+ if type.nullable():
+ declType = CGTemplatedType("Option", declType)
+ conversion = CGWrapper(conversion, pre="Some(", post=")")
+
+ if allowTreatNonObjectAsNull and type.treatNonObjectAsNull():
+ if not isDefinitelyObject:
+ haveObject = "${val}.is_object()"
+ template = CGIfElseWrapper(haveObject,
+ conversion,
+ CGGeneric("None")).define()
+ else:
+ template = conversion
+ else:
+ template = CGIfElseWrapper("JS_ObjectIsCallable(cx, ${val}.to_object()) != 0",
+ conversion,
+ onFailureNotCallable(failureCode)).define()
+ template = wrapObjectTemplate(
+ template,
+ isDefinitelyObject,
+ type,
+ failureCode)
+
+ if defaultValue is not None:
+ assert allowTreatNonObjectAsNull
+ assert type.treatNonObjectAsNull()
+ assert type.nullable()
+ assert isinstance(defaultValue, IDLNullValue)
+ default = "None"
+ else:
+ default = None
+
+ return (template, default, declType, needsRooting)
+
+ if type.isAny():
+ assert not isEnforceRange and not isClamp
+
+ declType = CGGeneric("JSVal")
+
+ if defaultValue is None:
+ default = None
+ elif isinstance(defaultValue, IDLNullValue):
+ default = "NullValue()"
+ elif isinstance(defaultValue, IDLUndefinedValue):
+ default = "UndefinedValue()"
+ else:
+ raise TypeError("Can't handle non-null, non-undefined default value here")
+
+ return handleOptional("${val}", declType, default)
+
+ if type.isObject():
+ raise TypeError("Can't handle object arguments yet")
+
+ if type.isDictionary():
+ if failureCode is not None:
+ raise TypeError("Can't handle dictionaries when failureCode is not None")
+ # There are no nullable dictionaries
+ assert not type.nullable()
+
+ typeName = CGDictionary.makeDictionaryName(type.inner)
+ declType = CGGeneric(typeName)
+ template = ("match %s::new(cx, ${val}) {\n"
+ " Ok(dictionary) => dictionary,\n"
+ " Err(_) => return 0,\n"
+ "}" % typeName)
+
+ return handleOptional(template, declType, handleDefaultNull("%s::empty()" % typeName))
+
+ if type.isVoid():
+ # This one only happens for return values, and its easy: Just
+ # ignore the jsval.
+ return ("", None, None, False)
+
+ if not type.isPrimitive():
+ raise TypeError("Need conversion for argument type '%s'" % str(type))
+
+ assert not isEnforceRange and not isClamp
+
+ if failureCode is None:
+ failureCode = 'return 0'
+
+ declType = CGGeneric(builtinNames[type.tag()])
+ if type.nullable():
+ declType = CGWrapper(declType, pre="Option<", post=">")
+
+ #XXXjdm support conversionBehavior here
+ template = (
+ "match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
+ " Ok(v) => v,\n"
+ " Err(_) => { %s }\n"
+ "}" % exceptionCode)
+
+ if defaultValue is not None:
+ if isinstance(defaultValue, IDLNullValue):
+ assert type.nullable()
+ defaultStr = "None"
+ else:
+ tag = defaultValue.type.tag()
+ if tag in numericTags:
+ defaultStr = str(defaultValue.value)
+ else:
+ assert(tag == IDLType.Tags.bool)
+ defaultStr = toStringBool(defaultValue.value)
+
+ if type.nullable():
+ defaultStr = "Some(%s)" % defaultStr
+ else:
+ defaultStr = None
+
+ return handleOptional(template, declType, defaultStr)
+
+def instantiateJSToNativeConversionTemplate(templateBody, replacements,
+ declType, declName, needsRooting):
+ """
+ Take the templateBody and declType as returned by
+ getJSToNativeConversionTemplate, a set of replacements as required by the
+ strings in such a templateBody, and a declName, and generate code to
+ convert into a stack Rust binding with that name.
+ """
+ result = CGList([], "\n")
+
+ conversion = CGGeneric(
+ string.Template(templateBody).substitute(replacements)
+ )
+
+ if declType is not None:
+ newDecl = [
+ CGGeneric("let "),
+ CGGeneric(declName),
+ CGGeneric(": "),
+ declType,
+ CGGeneric(" = "),
+ conversion,
+ CGGeneric(";"),
+ ]
+ result.append(CGList(newDecl))
+ else:
+ result.append(conversion)
+
+ # Add an empty CGGeneric to get an extra newline after the argument
+ # conversion.
+ result.append(CGGeneric(""))
+
+ if needsRooting:
+ rootBody = "let %s = %s.root();" % (declName, declName)
+ result.append(CGGeneric(rootBody))
+ result.append(CGGeneric(""))
+
+ return result;
+
+def convertConstIDLValueToJSVal(value):
+ if isinstance(value, IDLNullValue):
+ return "NullVal"
+ tag = value.type.tag()
+ if tag in [IDLType.Tags.int8, IDLType.Tags.uint8, IDLType.Tags.int16,
+ IDLType.Tags.uint16, IDLType.Tags.int32]:
+ return "IntVal(%s)" % (value.value)
+ if tag == IDLType.Tags.uint32:
+ return "UintVal(%s)" % (value.value)
+ if tag in [IDLType.Tags.int64, IDLType.Tags.uint64]:
+ return "DoubleVal(%s)" % (value.value)
+ if tag == IDLType.Tags.bool:
+ return "BoolVal(true)" if value.value else "BoolVal(false)"
+ if tag in [IDLType.Tags.float, IDLType.Tags.double]:
+ return "DoubleVal(%s)" % (value.value)
+ raise TypeError("Const value of unhandled type: " + value.type)
+
+class CGArgumentConverter(CGThing):
+ """
+ A class that takes an IDL argument object, its index in the
+ argument list, and the argv and argc strings and generates code to
+ unwrap the argument to the right native type.
+ """
+ def __init__(self, argument, index, argv, argc, descriptorProvider,
+ invalidEnumValueFatal=True):
+ CGThing.__init__(self)
+ assert(not argument.defaultValue or argument.optional)
+
+ replacer = {
+ "index": index,
+ "argc": argc,
+ "argv": argv
+ }
+ condition = string.Template("${index} < ${argc}").substitute(replacer)
+
+ replacementVariables = {
+ "val": string.Template("(*${argv}.offset(${index}))").substitute(replacer),
+ }
+
+ template, default, declType, needsRooting = getJSToNativeConversionTemplate(
+ argument.type,
+ descriptorProvider,
+ invalidEnumValueFatal=invalidEnumValueFatal,
+ defaultValue=argument.defaultValue,
+ treatNullAs=argument.treatNullAs,
+ isEnforceRange=argument.enforceRange,
+ isClamp=argument.clamp,
+ isMember="Variadic" if argument.variadic else False,
+ allowTreatNonObjectAsNull=argument.allowTreatNonCallableAsNull())
+
+ if not argument.variadic:
+ if argument.optional:
+ if argument.defaultValue:
+ assert default
+ template = CGIfElseWrapper(condition,
+ CGGeneric(template),
+ CGGeneric(default)).define()
+ else:
+ assert not default
+ declType = CGWrapper(declType, pre="Option<", post=">")
+ template = CGIfElseWrapper(condition,
+ CGGeneric("Some(%s)" % template),
+ CGGeneric("None")).define()
+ else:
+ assert not default
+
+ self.converter = instantiateJSToNativeConversionTemplate(
+ template, replacementVariables, declType, "arg%d" % index,
+ needsRooting)
+ else:
+ assert argument.optional
+ variadicConversion = {
+ "val": string.Template("(*${argv}.offset(variadicArg as int))").substitute(replacer),
+ }
+ innerConverter = instantiateJSToNativeConversionTemplate(
+ template, variadicConversion, declType, "slot",
+ needsRooting)
+
+ seqType = CGTemplatedType("Vec", declType)
+ variadicConversion = string.Template(
+ "{\n"
+ " let mut vector: ${seqType} = Vec::with_capacity((${argc} - ${index}) as uint);\n"
+ " for variadicArg in range(${index}, ${argc}) {\n"
+ "${inner}\n"
+ " vector.push(slot);\n"
+ " }\n"
+ " vector\n"
+ "}"
+ ).substitute({
+ "index": index,
+ "argc": argc,
+ "seqType": seqType.define(),
+ "inner": CGIndenter(innerConverter, 4).define(),
+ })
+
+ self.converter = instantiateJSToNativeConversionTemplate(
+ variadicConversion, replacementVariables, seqType, "arg%d" % index,
+ False)
+
+ def define(self):
+ return self.converter.define()
+
+
+def wrapForType(jsvalRef, result='result', successCode='return 1;'):
+ """
+ Reflect a Rust value into JS.
+
+ * 'jsvalRef': a Rust reference to the JSVal in which to store the result
+ of the conversion;
+ * 'result': the name of the variable in which the Rust value is stored;
+ * 'successCode': the code to run once we have done the conversion.
+ """
+ return "%s = (%s).to_jsval(cx);\n%s" % (jsvalRef, result, successCode)
+
+
+def typeNeedsCx(type, retVal=False):
+ if type is None:
+ return False
+ if type.nullable():
+ type = type.inner
+ if type.isSequence() or type.isArray():
+ type = type.inner
+ if type.isUnion():
+ return any(typeNeedsCx(t) for t in type.unroll().flatMemberTypes)
+ if retVal and type.isSpiderMonkeyInterface():
+ return True
+ return type.isAny() or type.isObject()
+
+def typeRetValNeedsRooting(type):
+ if type is None:
+ return False
+ if type.nullable():
+ type = type.inner
+ return type.isGeckoInterface() and not type.isCallback() and not type.isCallbackInterface()
+
+def memberIsCreator(member):
+ return member.getExtendedAttribute("Creator") is not None
+
+# Returns a CGThing containing the type of the return value.
+def getRetvalDeclarationForType(returnType, descriptorProvider):
+ if returnType is None or returnType.isVoid():
+ # Nothing to declare
+ return CGGeneric("()")
+ if returnType.isPrimitive() and returnType.tag() in builtinNames:
+ result = CGGeneric(builtinNames[returnType.tag()])
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isDOMString():
+ result = CGGeneric("DOMString")
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isByteString():
+ result = CGGeneric("ByteString")
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isEnum():
+ result = CGGeneric(returnType.unroll().inner.identifier.name)
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isGeckoInterface():
+ descriptor = descriptorProvider.getDescriptor(
+ returnType.unroll().inner.identifier.name)
+ result = CGGeneric(descriptor.returnType)
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isCallback():
+ result = CGGeneric('%s::%s' % (returnType.unroll().module(),
+ returnType.unroll().identifier.name))
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isUnion():
+ result = CGGeneric('%s::%s' % (returnType.unroll().name, returnType.unroll().name))
+ if returnType.nullable():
+ result = CGWrapper(result, pre="Option<", post=">")
+ return result
+ if returnType.isAny():
+ return CGGeneric("JSVal")
+ if returnType.isObject() or returnType.isSpiderMonkeyInterface():
+ return CGGeneric("*mut JSObject")
+ if returnType.isSequence():
+ raise TypeError("We don't support sequence return values")
+
+ raise TypeError("Don't know how to declare return value for %s" %
+ returnType)
+
+class PropertyDefiner:
+ """
+ A common superclass for defining things on prototype objects.
+
+ Subclasses should implement generateArray to generate the actual arrays of
+ things we're defining. They should also set self.regular to the list of
+ things exposed to web pages.
+ """
+ def __init__(self, descriptor, name):
+ self.descriptor = descriptor
+ self.name = name
+
+ def variableName(self):
+ return "s" + self.name
+
+ def length(self):
+ return len(self.regular)
+
+ def __str__(self):
+ # We only need to generate id arrays for things that will end
+ # up used via ResolveProperty or EnumerateProperties.
+ return self.generateArray(self.regular, self.variableName())
+
+ def generatePrefableArray(self, array, name, specTemplate, specTerminator,
+ specType, getDataTuple):
+ """
+ This method generates our various arrays.
+
+ array is an array of interface members as passed to generateArray
+
+ name is the name as passed to generateArray
+
+ specTemplate is a template for each entry of the spec array
+
+ specTerminator is a terminator for the spec array (inserted at the end
+ of the array), or None
+
+ specType is the actual typename of our spec
+
+ getDataTuple is a callback function that takes an array entry and
+ returns a tuple suitable for substitution into specTemplate.
+ """
+
+ assert(len(array) is not 0)
+ specs = []
+
+ for member in array:
+ specs.append(specTemplate % getDataTuple(member))
+ if specTerminator:
+ specs.append(specTerminator)
+
+ return (("static %s: &'static [%s] = &[\n" +
+ ",\n".join(specs) + "\n" +
+ "];\n\n") % (name, specType))
+
+# The length of a method is the maximum of the lengths of the
+# argument lists of all its overloads.
+def methodLength(method):
+ signatures = method.signatures()
+ return max([len(arguments) for (retType, arguments) in signatures])
+
+class MethodDefiner(PropertyDefiner):
+ """
+ A class for defining methods on a prototype object.
+ """
+ def __init__(self, descriptor, name, static):
+ PropertyDefiner.__init__(self, descriptor, name)
+
+ # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=772822
+ # We should be able to check for special operations without an
+ # identifier. For now we check if the name starts with __
+ methods = [m for m in descriptor.interface.members if
+ m.isMethod() and m.isStatic() == static and
+ not m.isIdentifierLess()]
+ self.regular = [{"name": m.identifier.name,
+ "methodInfo": not m.isStatic(),
+ "length": methodLength(m),
+ "flags": "JSPROP_ENUMERATE" }
+ for m in methods]
+
+ # FIXME Check for an existing iterator on the interface first.
+ if any(m.isGetter() and m.isIndexed() for m in methods):
+ self.regular.append({"name": 'iterator',
+ "methodInfo": False,
+ "nativeName": "JS_ArrayIterator",
+ "length": 0,
+ "flags": "JSPROP_ENUMERATE" })
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ def specData(m):
+ if m.get("methodInfo", True):
+ jitinfo = ("&%s_methodinfo" % m["name"])
+ accessor = "genericMethod"
+ else:
+ jitinfo = "0 as *const JSJitInfo"
+ accessor = m.get("nativeName", m["name"])
+ return (m["name"], accessor, jitinfo, m["length"], m["flags"])
+
+ def stringDecl(m):
+ return "static %s_name: [u8, ..%i] = %s;\n" % (m["name"], len(m["name"]) + 1,
+ str_to_const_array(m["name"]))
+
+ decls = ''.join([stringDecl(m) for m in array])
+ return decls + self.generatePrefableArray(
+ array, name,
+ ' JSFunctionSpec {name: &%s_name as *const u8 as *const libc::c_char, call: JSNativeWrapper {op: Some(%s), info: %s}, nargs: %s, flags: %s as u16, selfHostedName: 0 as *const libc::c_char }',
+ ' JSFunctionSpec {name: 0 as *const libc::c_char, call: JSNativeWrapper {op: None, info: 0 as *const JSJitInfo}, nargs: 0, flags: 0, selfHostedName: 0 as *const libc::c_char }',
+ 'JSFunctionSpec',
+ specData)
+
+class AttrDefiner(PropertyDefiner):
+ def __init__(self, descriptor, name, static):
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ self.regular = [
+ m
+ for m in descriptor.interface.members
+ if m.isAttr() and m.isStatic() == static
+ ]
+ self.static = static
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ def flags(attr):
+ return "JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_NATIVE_ACCESSORS"
+
+ def getter(attr):
+ if self.static:
+ accessor = 'get_' + attr.identifier.name
+ jitinfo = "0"
+ else:
+ if attr.hasLenientThis():
+ accessor = "genericLenientGetter"
+ else:
+ accessor = "genericGetter"
+ jitinfo = "&%s_getterinfo" % attr.identifier.name
+
+ return ("JSPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}"
+ % {"info" : jitinfo,
+ "native" : accessor})
+
+ def setter(attr):
+ if attr.readonly:
+ return "JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}"
+
+ if self.static:
+ accessor = 'set_' + attr.identifier.name
+ jitinfo = "0"
+ else:
+ if attr.hasLenientThis():
+ accessor = "genericLenientSetter"
+ else:
+ accessor = "genericSetter"
+ jitinfo = "&%s_setterinfo" % attr.identifier.name
+
+ return ("JSStrictPropertyOpWrapper {op: Some(%(native)s), info: %(info)s as *const JSJitInfo}"
+ % {"info" : jitinfo,
+ "native" : accessor})
+
+ def specData(attr):
+ return (attr.identifier.name, flags(attr), getter(attr),
+ setter(attr))
+
+ def stringDecl(attr):
+ name = attr.identifier.name
+ return "static %s_name: [u8, ..%i] = %s;\n" % (name, len(name) + 1,
+ str_to_const_array(name))
+
+ decls = ''.join([stringDecl(m) for m in array])
+
+ return decls + self.generatePrefableArray(
+ array, name,
+ ' JSPropertySpec { name: &%s_name as *const u8 as *const libc::c_char, tinyid: 0, flags: ((%s) & 0xFF) as u8, getter: %s, setter: %s }',
+ ' JSPropertySpec { name: 0 as *const libc::c_char, tinyid: 0, flags: 0, getter: JSPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo}, setter: JSStrictPropertyOpWrapper {op: None, info: 0 as *const JSJitInfo} }',
+ 'JSPropertySpec',
+ specData)
+
+class ConstDefiner(PropertyDefiner):
+ """
+ A class for definining constants on the interface object
+ """
+ def __init__(self, descriptor, name):
+ PropertyDefiner.__init__(self, descriptor, name)
+ self.name = name
+ self.regular = [m for m in descriptor.interface.members if m.isConst()]
+
+ def generateArray(self, array, name):
+ if len(array) == 0:
+ return ""
+
+ def specData(const):
+ return (const.identifier.name,
+ convertConstIDLValueToJSVal(const.value))
+
+ def stringDecl(const):
+ name = const.identifier.name
+ return "static %s_name: &'static [u8] = &%s;\n" % (name, str_to_const_array(name))
+
+ decls = ''.join([stringDecl(m) for m in array])
+
+ return decls + self.generatePrefableArray(
+ array, name,
+ ' ConstantSpec { name: %s_name, value: %s }',
+ None,
+ 'ConstantSpec',
+ specData)
+
+# We'll want to insert the indent at the beginnings of lines, but we
+# don't want to indent empty lines. So only indent lines that have a
+# non-newline character on them.
+lineStartDetector = re.compile("^(?=[^\n])", re.MULTILINE)
+class CGIndenter(CGThing):
+ """
+ A class that takes another CGThing and generates code that indents that
+ CGThing by some number of spaces. The default indent is two spaces.
+ """
+ def __init__(self, child, indentLevel=2):
+ CGThing.__init__(self)
+ self.child = child
+ self.indent = " " * indentLevel
+
+ def define(self):
+ defn = self.child.define()
+ if defn is not "":
+ return re.sub(lineStartDetector, self.indent, defn)
+ else:
+ return defn
+
+class CGWrapper(CGThing):
+ """
+ Generic CGThing that wraps other CGThings with pre and post text.
+ """
+ def __init__(self, child, pre="", post="", reindent=False):
+ CGThing.__init__(self)
+ self.child = child
+ self.pre = pre
+ self.post = post
+ self.reindent = reindent
+
+ def define(self):
+ defn = self.child.define()
+ if self.reindent:
+ # We don't use lineStartDetector because we don't want to
+ # insert whitespace at the beginning of our _first_ line.
+ defn = stripTrailingWhitespace(
+ defn.replace("\n", "\n" + (" " * len(self.pre))))
+ return self.pre + defn + self.post
+
+class CGImports(CGWrapper):
+ """
+ Generates the appropriate import/use statements.
+ """
+ def __init__(self, child, descriptors, imports):
+ """
+ Adds a set of imports.
+ """
+ ignored_warnings = [
+ # Allow unreachable_code because we use 'break' in a way that
+ # sometimes produces two 'break's in a row. See for example
+ # CallbackMember.getArgConversions.
+ 'unreachable_code',
+ 'non_camel_case_types',
+ 'non_uppercase_statics',
+ 'unnecessary_parens',
+ 'unused_imports',
+ 'unused_variable',
+ 'unused_unsafe',
+ 'unused_mut',
+ 'dead_assignment',
+ 'dead_code',
+ ]
+
+ statements = ['#![allow(%s)]' % ','.join(ignored_warnings)]
+ statements.extend('use %s;' % i for i in sorted(imports))
+
+ CGWrapper.__init__(self, child,
+ pre='\n'.join(statements) + '\n\n')
+
+ @staticmethod
+ def getDeclarationFilename(decl):
+ # Use our local version of the header, not the exported one, so that
+ # test bindings, which don't export, will work correctly.
+ basename = os.path.basename(decl.filename())
+ return basename.replace('.webidl', 'Binding.rs')
+
+class CGIfWrapper(CGWrapper):
+ def __init__(self, child, condition):
+ pre = CGWrapper(CGGeneric(condition), pre="if ", post=" {\n",
+ reindent=True)
+ CGWrapper.__init__(self, CGIndenter(child), pre=pre.define(),
+ post="\n}")
+
+class CGTemplatedType(CGWrapper):
+ def __init__(self, templateName, child):
+ CGWrapper.__init__(self, child, pre=templateName + "<", post=">")
+
+class CGNamespace(CGWrapper):
+ def __init__(self, namespace, child, public=False):
+ pre = "%smod %s {\n" % ("pub " if public else "", namespace)
+ post = "} // mod %s\n" % namespace
+ CGWrapper.__init__(self, child, pre=pre, post=post)
+
+ @staticmethod
+ def build(namespaces, child, public=False):
+ """
+ Static helper method to build multiple wrapped namespaces.
+ """
+ if not namespaces:
+ return child
+ inner = CGNamespace.build(namespaces[1:], child, public=public)
+ return CGNamespace(namespaces[0], inner, public=public)
+
+def DOMClass(descriptor):
+ protoList = ['PrototypeList::id::' + proto for proto in descriptor.prototypeChain]
+ # Pad out the list to the right length with IDCount so we
+ # guarantee that all the lists are the same length. IDCount
+ # is never the ID of any prototype, so it's safe to use as
+ # padding.
+ protoList.extend(['PrototypeList::id::IDCount'] * (descriptor.config.maxProtoChainLength - len(protoList)))
+ prototypeChainString = ', '.join(protoList)
+ return """DOMClass {
+ interface_chain: [ %s ],
+ native_hooks: &sNativePropertyHooks,
+}""" % prototypeChainString
+
+class CGDOMJSClass(CGThing):
+ """
+ Generate a DOMJSClass for a given descriptor
+ """
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def define(self):
+ traceHook = "Some(%s)" % TRACE_HOOK_NAME
+ if self.descriptor.isGlobal():
+ flags = "JSCLASS_IS_GLOBAL | JSCLASS_DOM_GLOBAL"
+ slots = "JSCLASS_GLOBAL_SLOT_COUNT + 1"
+ else:
+ flags = "0"
+ slots = "1"
+ return """
+static Class_name: [u8, ..%i] = %s;
+static Class: DOMJSClass = DOMJSClass {
+ base: js::Class {
+ name: &Class_name as *const u8 as *const libc::c_char,
+ flags: JSCLASS_IS_DOMJSCLASS | %s | (((%s) & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint), //JSCLASS_HAS_RESERVED_SLOTS(%s),
+ addProperty: Some(JS_PropertyStub),
+ delProperty: Some(JS_PropertyStub),
+ getProperty: Some(JS_PropertyStub),
+ setProperty: Some(JS_StrictPropertyStub),
+ enumerate: Some(JS_EnumerateStub),
+ resolve: Some(JS_ResolveStub),
+ convert: Some(JS_ConvertStub),
+ finalize: Some(%s),
+ checkAccess: None,
+ call: None,
+ hasInstance: None,
+ construct: None,
+ trace: %s,
+
+ ext: js::ClassExtension {
+ equality: 0 as *const u8,
+ outerObject: %s,
+ innerObject: None,
+ iteratorObject: 0 as *const u8,
+ unused: 0 as *const u8,
+ isWrappedNative: 0 as *const u8,
+ },
+
+ ops: js::ObjectOps {
+ lookupGeneric: 0 as *const u8,
+ lookupProperty: 0 as *const u8,
+ lookupElement: 0 as *const u8,
+ lookupSpecial: 0 as *const u8,
+ defineGeneric: 0 as *const u8,
+ defineProperty: 0 as *const u8,
+ defineElement: 0 as *const u8,
+ defineSpecial: 0 as *const u8,
+ getGeneric: 0 as *const u8,
+ getProperty: 0 as *const u8,
+ getElement: 0 as *const u8,
+ getElementIfPresent: 0 as *const u8,
+ getSpecial: 0 as *const u8,
+ setGeneric: 0 as *const u8,
+ setProperty: 0 as *const u8,
+ setElement: 0 as *const u8,
+ setSpecial: 0 as *const u8,
+ getGenericAttributes: 0 as *const u8,
+ getPropertyAttributes: 0 as *const u8,
+ getElementAttributes: 0 as *const u8,
+ getSpecialAttributes: 0 as *const u8,
+ setGenericAttributes: 0 as *const u8,
+ setPropertyAttributes: 0 as *const u8,
+ setElementAttributes: 0 as *const u8,
+ setSpecialAttributes: 0 as *const u8,
+ deleteProperty: 0 as *const u8,
+ deleteElement: 0 as *const u8,
+ deleteSpecial: 0 as *const u8,
+
+ enumerate: 0 as *const u8,
+ typeOf: 0 as *const u8,
+ thisObject: %s,
+ clear: 0 as *const u8,
+ },
+ },
+ dom_class: %s
+};
+""" % (len(self.descriptor.interface.identifier.name) + 1,
+ str_to_const_array(self.descriptor.interface.identifier.name),
+ flags, slots, slots,
+ FINALIZE_HOOK_NAME, traceHook,
+ self.descriptor.outerObjectHook,
+ self.descriptor.outerObjectHook,
+ CGIndenter(CGGeneric(DOMClass(self.descriptor))).define())
+
+def str_to_const_array(s):
+ return "[" + (", ".join(map(lambda x: "'" + x + "' as u8", list(s)) + ['0 as u8'])) + "]"
+
+class CGPrototypeJSClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def define(self):
+ return """
+static PrototypeClassName__: [u8, ..%s] = %s;
+static PrototypeClass: JSClass = JSClass {
+ name: &PrototypeClassName__ as *const u8 as *const libc::c_char,
+ flags: (1 & JSCLASS_RESERVED_SLOTS_MASK) << JSCLASS_RESERVED_SLOTS_SHIFT as uint, //JSCLASS_HAS_RESERVED_SLOTS(1)
+ addProperty: Some(JS_PropertyStub),
+ delProperty: Some(JS_PropertyStub),
+ getProperty: Some(JS_PropertyStub),
+ setProperty: Some(JS_StrictPropertyStub),
+ enumerate: Some(JS_EnumerateStub),
+ resolve: Some(JS_ResolveStub),
+ convert: Some(JS_ConvertStub),
+ finalize: None,
+ checkAccess: None,
+ call: None,
+ hasInstance: None,
+ construct: None,
+ trace: None,
+ reserved: [0 as *mut libc::c_void, ..40]
+};
+""" % (len(self.descriptor.interface.identifier.name + "Prototype") + 1,
+ str_to_const_array(self.descriptor.interface.identifier.name + "Prototype"))
+
+class CGInterfaceObjectJSClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def define(self):
+ if True:
+ return ""
+ ctorname = "0 as *const u8" if not self.descriptor.interface.ctor() else CONSTRUCT_HOOK_NAME
+ hasinstance = HASINSTANCE_HOOK_NAME
+ return """
+static InterfaceObjectClass: JSClass = {
+ %s, 0,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_StrictPropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ 0 as *const u8,
+ 0 as *const u8,
+ %s,
+ %s,
+ %s,
+ 0 as *const u8,
+ JSCLASS_NO_INTERNAL_MEMBERS
+};
+""" % (str_to_const_array("Function"), ctorname, hasinstance, ctorname)
+
+class CGList(CGThing):
+ """
+ Generate code for a list of GCThings. Just concatenates them together, with
+ an optional joiner string. "\n" is a common joiner.
+ """
+ def __init__(self, children, joiner=""):
+ CGThing.__init__(self)
+ self.children = children
+ self.joiner = joiner
+ def append(self, child):
+ self.children.append(child)
+ def prepend(self, child):
+ self.children.insert(0, child)
+ def join(self, generator):
+ return self.joiner.join(filter(lambda s: len(s) > 0, (child for child in generator)))
+
+ def define(self):
+ return self.join(child.define() for child in self.children if child is not None)
+
+
+class CGIfElseWrapper(CGList):
+ def __init__(self, condition, ifTrue, ifFalse):
+ kids = [ CGIfWrapper(ifTrue, condition),
+ CGWrapper(CGIndenter(ifFalse), pre=" else {\n", post="\n}") ]
+ CGList.__init__(self, kids)
+
+
+class CGGeneric(CGThing):
+ """
+ A class that spits out a fixed string into the codegen. Can spit out a
+ separate string for the declaration too.
+ """
+ def __init__(self, text):
+ self.text = text
+
+ def define(self):
+ return self.text
+
+class CGCallbackTempRoot(CGGeneric):
+ def __init__(self, name):
+ val = "%s::new(tempRoot)" % name
+ define = """{
+ let tempRoot = ${val}.to_object();
+ %s
+}""" % val
+ CGGeneric.__init__(self, define)
+
+
+def getAllTypes(descriptors, dictionaries, callbacks):
+ """
+ Generate all the types we're dealing with. For each type, a tuple
+ containing type, descriptor, dictionary is yielded. The
+ descriptor and dictionary can be None if the type does not come
+ from a descriptor or dictionary; they will never both be non-None.
+ """
+ for d in descriptors:
+ for t in getTypesFromDescriptor(d):
+ yield (t, d, None)
+ for dictionary in dictionaries:
+ for t in getTypesFromDictionary(dictionary):
+ yield (t, None, dictionary)
+ for callback in callbacks:
+ for t in getTypesFromCallback(callback):
+ yield (t, None, None)
+
+def SortedTuples(l):
+ """
+ Sort a list of tuples based on the first item in the tuple
+ """
+ return sorted(l, key=operator.itemgetter(0))
+
+def SortedDictValues(d):
+ """
+ Returns a list of values from the dict sorted by key.
+ """
+ # Create a list of tuples containing key and value, sorted on key.
+ d = SortedTuples(d.items())
+ # We're only interested in the values.
+ return (i[1] for i in d)
+
+def UnionTypes(descriptors, dictionaries, callbacks, config):
+ """
+ Returns a CGList containing CGUnionStructs for every union.
+ """
+
+ imports = [
+ 'dom::bindings::utils::unwrap_jsmanaged',
+ 'dom::bindings::codegen::PrototypeList',
+ 'dom::bindings::conversions::FromJSValConvertible',
+ 'dom::bindings::conversions::ToJSValConvertible',
+ 'dom::bindings::conversions::Default',
+ 'dom::bindings::error::throw_not_in_union',
+ 'dom::bindings::js::JS',
+ 'dom::types::*',
+ 'js::jsapi::JSContext',
+ 'js::jsval::JSVal',
+ 'servo_util::str::DOMString',
+ ]
+
+ # Now find all the things we'll need as arguments and return values because
+ # we need to wrap or unwrap them.
+ unionStructs = dict()
+ for (t, descriptor, dictionary) in getAllTypes(descriptors, dictionaries, callbacks):
+ assert not descriptor or not dictionary
+ t = t.unroll()
+ if not t.isUnion():
+ continue
+ name = str(t)
+ if not name in unionStructs:
+ provider = descriptor or config.getDescriptorProvider()
+ unionStructs[name] = CGNamespace(name,
+ CGImports(CGList([
+ CGUnionStruct(t, provider),
+ CGUnionConversionStruct(t, provider)
+ ]), [], imports),
+ public=True)
+
+ return CGList(SortedDictValues(unionStructs), "\n\n")
+
+
+class Argument():
+ """
+ A class for outputting the type and name of an argument
+ """
+ def __init__(self, argType, name, default=None, mutable=False):
+ self.argType = argType
+ self.name = name
+ self.default = default
+ self.mutable = mutable
+ def declare(self):
+ string = ('mut ' if self.mutable else '') + self.name + ((': ' + self.argType) if self.argType else '')
+ #XXXjdm Support default arguments somehow :/
+ #if self.default is not None:
+ # string += " = " + self.default
+ return string
+ def define(self):
+ return self.argType + ' ' + self.name
+
+class CGAbstractMethod(CGThing):
+ """
+ An abstract class for generating code for a method. Subclasses
+ should override definition_body to create the actual code.
+
+ descriptor is the descriptor for the interface the method is associated with
+
+ name is the name of the method as a string
+
+ returnType is the IDLType of the return value
+
+ args is a list of Argument objects
+
+ inline should be True to generate an inline method, whose body is
+ part of the declaration.
+
+ alwaysInline should be True to generate an inline method annotated with
+ MOZ_ALWAYS_INLINE.
+
+ If templateArgs is not None it should be a list of strings containing
+ template arguments, and the function will be templatized using those
+ arguments.
+ """
+ def __init__(self, descriptor, name, returnType, args, inline=False, alwaysInline=False, extern=False, pub=False, templateArgs=None, unsafe=True):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+ self.name = name
+ self.returnType = returnType
+ self.args = args
+ self.alwaysInline = alwaysInline
+ self.extern = extern
+ self.templateArgs = templateArgs
+ self.pub = pub;
+ self.unsafe = unsafe
+ def _argstring(self):
+ return ', '.join([a.declare() for a in self.args])
+ def _template(self):
+ if self.templateArgs is None:
+ return ''
+ return '<%s>\n' % ', '.join(self.templateArgs)
+
+ def _decorators(self):
+ decorators = []
+ if self.alwaysInline:
+ decorators.append('#[inline(always)]')
+
+ if self.extern:
+ decorators.append('extern')
+
+ if self.pub:
+ decorators.append('pub')
+
+ if not decorators:
+ return ''
+ return ' '.join(decorators) + ' '
+
+ def _returnType(self):
+ return (" -> %s" % self.returnType) if self.returnType != "void" else ""
+
+ def define(self):
+ body = self.definition_body()
+ if self.unsafe:
+ body = CGWrapper(body, pre="unsafe {\n", post="\n}")
+
+ return CGWrapper(CGIndenter(body),
+ pre=self.definition_prologue(),
+ post=self.definition_epilogue()).define()
+
+ def definition_prologue(self):
+ return "%sfn %s%s(%s)%s {\n" % (self._decorators(), self.name, self._template(),
+ self._argstring(), self._returnType())
+ def definition_epilogue(self):
+ return "\n}\n"
+ def definition_body(self):
+ assert(False) # Override me!
+
+def CreateBindingJSObject(descriptor, parent=None):
+ create = "let mut raw: JS<%s> = JS::from_raw(&*aObject);\n" % descriptor.concreteType
+ if descriptor.proxy:
+ assert not descriptor.isGlobal()
+ create += """
+let handler = RegisterBindings::proxy_handlers[PrototypeList::proxies::%s as uint];
+let mut private = PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void);
+let obj = with_compartment(aCx, proto, || {
+ NewProxyObject(aCx, handler,
+ &private,
+ proto, %s,
+ ptr::mut_null(), ptr::mut_null())
+});
+assert!(obj.is_not_null());
+
+""" % (descriptor.name, parent)
+ else:
+ if descriptor.isGlobal():
+ create += "let obj = CreateDOMGlobal(aCx, &Class.base as *const js::Class as *const JSClass);\n"
+ else:
+ create += ("let obj = with_compartment(aCx, proto, || {\n"
+ " JS_NewObject(aCx, &Class.base as *const js::Class as *const JSClass, &*proto, &*%s)\n"
+ "});\n" % parent)
+ create += """assert!(obj.is_not_null());
+
+JS_SetReservedSlot(obj, DOM_OBJECT_SLOT as u32,
+ PrivateValue(squirrel_away_unique(aObject) as *const libc::c_void));
+"""
+ return create
+
+class CGWrapMethod(CGAbstractMethod):
+ """
+ Class that generates the FooBinding::Wrap function for non-callback
+ interfaces.
+ """
+ def __init__(self, descriptor):
+ assert not descriptor.interface.isCallback()
+ if not descriptor.isGlobal():
+ args = [Argument('*mut JSContext', 'aCx'), Argument('&GlobalRef', 'aScope'),
+ Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)]
+ else:
+ args = [Argument('*mut JSContext', 'aCx'),
+ Argument("Box<%s>" % descriptor.concreteType, 'aObject', mutable=True)]
+ retval = 'Temporary<%s>' % descriptor.concreteType
+ CGAbstractMethod.__init__(self, descriptor, 'Wrap', retval, args, pub=True)
+
+ def definition_body(self):
+ if not self.descriptor.isGlobal():
+ return CGGeneric("""\
+let scope = aScope.reflector().get_jsobject();
+assert!(scope.is_not_null());
+assert!(((*JS_GetClass(scope)).flags & JSCLASS_IS_GLOBAL) != 0);
+
+let proto = with_compartment(aCx, scope, || GetProtoObject(aCx, scope, scope));
+assert!(proto.is_not_null());
+
+%s
+
+raw.reflector().set_jsobject(obj);
+
+Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor, "scope"))
+ else:
+ return CGGeneric("""\
+%s
+with_compartment(aCx, obj, || {
+ let proto = GetProtoObject(aCx, obj, obj);
+ JS_SetPrototype(aCx, obj, proto);
+
+ raw.reflector().set_jsobject(obj);
+
+ RegisterBindings::Register(aCx, obj);
+});
+
+Temporary::new(raw)""" % CreateBindingJSObject(self.descriptor))
+
+
+class CGIDLInterface(CGThing):
+ """
+ Class for codegen of an implementation of the IDLInterface trait.
+ """
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def define(self):
+ replacer = {
+ 'type': self.descriptor.name,
+ 'depth': self.descriptor.interface.inheritanceDepth(),
+ }
+ return string.Template("""
+impl IDLInterface for ${type} {
+ fn get_prototype_id(_: Option<${type}>) -> PrototypeList::id::ID {
+ PrototypeList::id::${type}
+ }
+ fn get_prototype_depth(_: Option<${type}>) -> uint {
+ ${depth}
+ }
+}
+""").substitute(replacer)
+
+
+class CGAbstractExternMethod(CGAbstractMethod):
+ """
+ Abstract base class for codegen of implementation-only (no
+ declaration) static methods.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractMethod.__init__(self, descriptor, name, returnType, args,
+ inline=False, extern=True)
+
+class PropertyArrays():
+ def __init__(self, descriptor):
+ self.staticMethods = MethodDefiner(descriptor, "StaticMethods",
+ static=True)
+ self.staticAttrs = AttrDefiner(descriptor, "StaticAttributes",
+ static=True)
+ self.methods = MethodDefiner(descriptor, "Methods", static=False)
+ self.attrs = AttrDefiner(descriptor, "Attributes", static=False)
+ self.consts = ConstDefiner(descriptor, "Constants")
+ pass
+
+ @staticmethod
+ def arrayNames():
+ return [ "staticMethods", "staticAttrs", "methods", "attrs", "consts" ]
+
+ def variableNames(self):
+ names = {}
+ for array in self.arrayNames():
+ names[array] = getattr(self, array).variableName()
+ return names
+ def __str__(self):
+ define = ""
+ for array in self.arrayNames():
+ define += str(getattr(self, array))
+ return define
+
+
+class CGNativeProperties(CGThing):
+ def __init__(self, descriptor, properties):
+ CGThing.__init__(self)
+ self.properties = properties
+
+ def define(self):
+ def getField(array):
+ propertyArray = getattr(self.properties, array)
+ if propertyArray.length() > 0:
+ value = "Some(%s)" % propertyArray.variableName()
+ else:
+ value = "None"
+
+ return CGGeneric(string.Template('${name}: ${value},').substitute({
+ 'name': array,
+ 'value': value,
+ }))
+
+ nativeProps = CGList([getField(array) for array in self.properties.arrayNames()], '\n')
+ return CGWrapper(CGIndenter(nativeProps),
+ pre="static sNativeProperties: NativeProperties = NativeProperties {\n",
+ post="\n};\n").define()
+
+
+class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
+ """
+ Generate the CreateInterfaceObjects method for an interface descriptor.
+
+ properties should be a PropertyArrays instance.
+ """
+ def __init__(self, descriptor, properties):
+ assert not descriptor.interface.isCallback()
+ args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'),
+ Argument('*mut JSObject', 'aReceiver')]
+ CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', '*mut JSObject', args)
+ self.properties = properties
+ def definition_body(self):
+ protoChain = self.descriptor.prototypeChain
+ if len(protoChain) == 1:
+ getParentProto = "JS_GetObjectPrototype(aCx, aGlobal)"
+ else:
+ parentProtoName = self.descriptor.prototypeChain[-2]
+ getParentProto = ("%s::GetProtoObject(aCx, aGlobal, aReceiver)" %
+ toBindingNamespace(parentProtoName))
+
+ getParentProto = ("let parentProto: *mut JSObject = %s;\n"
+ "assert!(parentProto.is_not_null());\n") % getParentProto
+
+ if self.descriptor.concrete:
+ if self.descriptor.proxy:
+ domClass = "&Class"
+ else:
+ domClass = "&Class.dom_class"
+ else:
+ domClass = "ptr::null()"
+
+ if self.descriptor.interface.hasInterfaceObject():
+ if self.descriptor.interface.ctor():
+ constructHook = CONSTRUCT_HOOK_NAME
+ constructArgs = methodLength(self.descriptor.interface.ctor())
+ else:
+ constructHook = "ThrowingConstructor"
+ constructArgs = 0
+
+ constructor = 'Some((%s, "%s", %d))' % (
+ constructHook, self.descriptor.interface.identifier.name,
+ constructArgs)
+ else:
+ constructor = 'None'
+
+ call = """return CreateInterfaceObjects2(aCx, aGlobal, aReceiver, parentProto,
+ &PrototypeClass, %s,
+ %s,
+ &sNativeProperties);""" % (constructor, domClass)
+
+ return CGList([
+ CGGeneric(getParentProto),
+ CGGeneric(call % self.properties.variableNames())
+ ], "\n")
+
+class CGGetPerInterfaceObject(CGAbstractMethod):
+ """
+ A method for getting a per-interface object (a prototype object or interface
+ constructor object).
+ """
+ def __init__(self, descriptor, name, idPrefix="", pub=False):
+ args = [Argument('*mut JSContext', 'aCx'), Argument('*mut JSObject', 'aGlobal'),
+ Argument('*mut JSObject', 'aReceiver')]
+ CGAbstractMethod.__init__(self, descriptor, name,
+ '*mut JSObject', args, pub=pub)
+ self.id = idPrefix + "id::" + self.descriptor.name
+ def definition_body(self):
+ return CGGeneric("""
+
+/* aGlobal and aReceiver are usually the same, but they can be different
+ too. For example a sandbox often has an xray wrapper for a window as the
+ prototype of the sandbox's global. In that case aReceiver is the xray
+ wrapper and aGlobal is the sandbox's global.
+ */
+
+assert!(((*JS_GetClass(aGlobal)).flags & JSCLASS_DOM_GLOBAL) != 0);
+
+/* Check to see whether the interface objects are already installed */
+let protoOrIfaceArray = GetProtoOrIfaceArray(aGlobal);
+let cachedObject: *mut JSObject = *protoOrIfaceArray.offset(%s as int);
+if cachedObject.is_null() {
+ let tmp: *mut JSObject = CreateInterfaceObjects(aCx, aGlobal, aReceiver);
+ assert!(tmp.is_not_null());
+ *protoOrIfaceArray.offset(%s as int) = tmp;
+ tmp
+} else {
+ cachedObject
+}""" % (self.id, self.id))
+
+class CGGetProtoObjectMethod(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface prototype object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(self, descriptor, "GetProtoObject",
+ "PrototypeList::", pub=True)
+ def definition_body(self):
+ return CGList([
+ CGGeneric("""\
+/* Get the interface prototype object for this class. This will create the
+ object as needed. */"""),
+ CGGetPerInterfaceObject.definition_body(self),
+ ])
+
+class CGGetConstructorObjectMethod(CGGetPerInterfaceObject):
+ """
+ A method for getting the interface constructor object.
+ """
+ def __init__(self, descriptor):
+ CGGetPerInterfaceObject.__init__(self, descriptor, "GetConstructorObject",
+ "constructors::")
+ def definition_body(self):
+ return CGList([
+ CGGeneric("""\
+/* Get the interface object for this class. This will create the object as
+ needed. */"""),
+ CGGetPerInterfaceObject.definition_body(self),
+ ])
+
+
+class CGDefineProxyHandler(CGAbstractMethod):
+ """
+ A method to create and cache the proxy trap for a given interface.
+ """
+ def __init__(self, descriptor):
+ assert descriptor.proxy
+ CGAbstractMethod.__init__(self, descriptor, 'DefineProxyHandler', '*const libc::c_void', [], pub=True)
+
+ def define(self):
+ return CGAbstractMethod.define(self)
+
+ def definition_body(self):
+ body = """\
+let traps = ProxyTraps {
+ getPropertyDescriptor: Some(getPropertyDescriptor),
+ getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor),
+ defineProperty: Some(defineProperty),
+ getOwnPropertyNames: ptr::null(),
+ delete_: Some(delete_),
+ enumerate: ptr::null(),
+
+ has: None,
+ hasOwn: Some(hasOwn),
+ get: Some(get),
+ set: None,
+ keys: ptr::null(),
+ iterate: None,
+
+ call: None,
+ construct: None,
+ nativeCall: ptr::null(),
+ hasInstance: None,
+ typeOf: None,
+ objectClassIs: None,
+ obj_toString: Some(obj_toString),
+ fun_toString: None,
+ //regexp_toShared: ptr::null(),
+ defaultValue: None,
+ iteratorNext: None,
+ finalize: Some(%s),
+ getElementIfPresent: None,
+ getPrototypeOf: None,
+ trace: Some(%s)
+};
+
+CreateProxyHandler(&traps, &Class as *const _ as *const _)
+""" % (FINALIZE_HOOK_NAME,
+ TRACE_HOOK_NAME)
+ return CGGeneric(body)
+
+
+
+class CGDefineDOMInterfaceMethod(CGAbstractMethod):
+ """
+ A method for resolve hooks to try to lazily define the interface object for
+ a given interface.
+ """
+ def __init__(self, descriptor):
+ assert descriptor.interface.hasInterfaceObject()
+ args = [
+ Argument('*mut JSContext', 'cx'),
+ Argument('*mut JSObject', 'global'),
+ ]
+ CGAbstractMethod.__init__(self, descriptor, 'DefineDOMInterface', 'void', args, pub=True)
+
+ def define(self):
+ return CGAbstractMethod.define(self)
+
+ def definition_body(self):
+ return CGGeneric("""\
+assert!(global.is_not_null());
+assert!(GetProtoObject(cx, global, global).is_not_null());""")
+
+def needCx(returnType, arguments, considerTypes):
+ return (considerTypes and
+ (typeNeedsCx(returnType, True) or
+ any(typeNeedsCx(a.type) for a in arguments)))
+
+class CGCallGenerator(CGThing):
+ """
+ A class to generate an actual call to a C++ object. Assumes that the C++
+ object is stored in a variable whose name is given by the |object| argument.
+
+ errorResult should be a string for the value to return in case of an
+ exception from the native code, or None if no error reporting is needed.
+ """
+ def __init__(self, errorResult, arguments, argsPre, returnType,
+ extendedAttributes, descriptorProvider, nativeMethodName,
+ static, object="this"):
+ CGThing.__init__(self)
+
+ assert errorResult is None or isinstance(errorResult, str)
+
+ isFallible = errorResult is not None
+
+ result = getRetvalDeclarationForType(returnType, descriptorProvider)
+ if isFallible:
+ result = CGWrapper(result, pre="Result<", post=", Error>")
+
+ args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
+ for (a, name) in arguments:
+ #XXXjdm Perhaps we should pass all nontrivial types by borrowed pointer
+ if a.type.isGeckoInterface():
+ if not (a.type.nullable() or a.optional):
+ name = "&" + name
+ elif a.type.isDictionary():
+ name = "&" + name
+ args.append(CGGeneric(name))
+
+ needsCx = needCx(returnType, (a for (a, _) in arguments), True)
+
+ if not "cx" in argsPre and needsCx:
+ args.prepend(CGGeneric("cx"))
+
+ # Build up our actual call
+ self.cgRoot = CGList([], "\n")
+
+ call = CGGeneric(nativeMethodName)
+ if static:
+ call = CGWrapper(call, pre="%s::" % descriptorProvider.interface.identifier.name)
+ else:
+ call = CGWrapper(call, pre="(*%s)." % object)
+ call = CGList([call, CGWrapper(args, pre="(", post=")")])
+
+ self.cgRoot.append(CGList([
+ CGGeneric("let result: "),
+ result,
+ CGGeneric(" = "),
+ call,
+ CGGeneric(";"),
+ ]))
+
+ if isFallible:
+ if static:
+ glob = ""
+ else:
+ glob = " let global = global_object_for_js_object(this.reflector().get_jsobject());\n"\
+ " let global = global.root();\n"
+
+ self.cgRoot.append(CGGeneric(
+ "let result = match result {\n"
+ " Ok(result) => result,\n"
+ " Err(e) => {\n"
+ "%s"
+ " throw_dom_exception(cx, &global.root_ref(), e);\n"
+ " return%s;\n"
+ " },\n"
+ "};\n" % (glob, errorResult)))
+
+ if typeRetValNeedsRooting(returnType):
+ self.cgRoot.append(CGGeneric("let result = result.root();"))
+
+ def define(self):
+ return self.cgRoot.define()
+
+class MethodNotCreatorError(Exception):
+ def __init__(self, typename):
+ self.typename = typename
+
+class CGPerSignatureCall(CGThing):
+ """
+ This class handles the guts of generating code for a particular
+ call signature. A call signature consists of four things:
+
+ 1) A return type, which can be None to indicate that there is no
+ actual return value (e.g. this is an attribute setter) or an
+ IDLType if there's an IDL type involved (including |void|).
+ 2) An argument list, which is allowed to be empty.
+ 3) A name of a native method to call.
+ 4) Whether or not this method is static.
+
+ We also need to know whether this is a method or a getter/setter
+ to do error reporting correctly.
+
+ The idlNode parameter can be either a method or an attr. We can query
+ |idlNode.identifier| in both cases, so we can be agnostic between the two.
+ """
+ # XXXbz For now each entry in the argument list is either an
+ # IDLArgument or a FakeArgument, but longer-term we may want to
+ # have ways of flagging things like JSContext* or optional_argc in
+ # there.
+
+ def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
+ descriptor, idlNode, argConversionStartsAt=0,
+ getter=False, setter=False):
+ CGThing.__init__(self)
+ self.returnType = returnType
+ self.descriptor = descriptor
+ self.idlNode = idlNode
+ self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
+ getter=getter,
+ setter=setter)
+ self.argsPre = argsPre
+ self.arguments = arguments
+ self.argCount = len(arguments)
+ if self.argCount > argConversionStartsAt:
+ # Insert our argv in there
+ cgThings = [CGGeneric(self.getArgvDecl())]
+ else:
+ cgThings = []
+ cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
+ self.getArgc(), self.descriptor,
+ invalidEnumValueFatal=not setter) for
+ i in range(argConversionStartsAt, self.argCount)])
+
+ cgThings.append(CGCallGenerator(
+ ' false as JSBool' if self.isFallible() else None,
+ self.getArguments(), self.argsPre, returnType,
+ self.extendedAttributes, descriptor, nativeMethodName,
+ static))
+ self.cgRoot = CGList(cgThings, "\n")
+
+ def getArgv(self):
+ return "argv" if self.argCount > 0 else ""
+ def getArgvDecl(self):
+ return "\nlet argv = JS_ARGV(cx, vp);\n"
+ def getArgc(self):
+ return "argc"
+ def getArguments(self):
+ def process(arg, i):
+ argVal = "arg" + str(i)
+ if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback():
+ argVal += ".root_ref()"
+ return argVal
+ return [(a, process(a, i)) for (i, a) in enumerate(self.arguments)]
+
+ def isFallible(self):
+ return not 'infallible' in self.extendedAttributes
+
+ def wrap_return_value(self):
+ return wrapForType('*vp')
+
+ def define(self):
+ return (self.cgRoot.define() + "\n" + self.wrap_return_value())
+
+class CGSwitch(CGList):
+ """
+ A class to generate code for a switch statement.
+
+ Takes three constructor arguments: an expression, a list of cases,
+ and an optional default.
+
+ Each case is a CGCase. The default is a CGThing for the body of
+ the default case, if any.
+ """
+ def __init__(self, expression, cases, default=None):
+ CGList.__init__(self, [CGIndenter(c) for c in cases], "\n")
+ self.prepend(CGWrapper(CGGeneric(expression),
+ pre="match ", post=" {"));
+ if default is not None:
+ self.append(
+ CGIndenter(
+ CGWrapper(
+ CGIndenter(default),
+ pre="_ => {\n",
+ post="\n}"
+ )
+ )
+ )
+
+ self.append(CGGeneric("}"))
+
+class CGCase(CGList):
+ """
+ A class to generate code for a case statement.
+
+ Takes three constructor arguments: an expression, a CGThing for
+ the body (allowed to be None if there is no body), and an optional
+ argument (defaulting to False) for whether to fall through.
+ """
+ def __init__(self, expression, body, fallThrough=False):
+ CGList.__init__(self, [], "\n")
+ self.append(CGWrapper(CGGeneric(expression), post=" => {"))
+ bodyList = CGList([body], "\n")
+ if fallThrough:
+ raise TypeError("fall through required but unsupported")
+ #bodyList.append(CGGeneric('fail!("fall through unsupported"); /* Fall through */'))
+ self.append(CGIndenter(bodyList));
+ self.append(CGGeneric("}"))
+
+class CGGetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object getter call for a particular IDL
+ getter.
+ """
+ def __init__(self, argsPre, returnType, nativeMethodName, descriptor, attr):
+ CGPerSignatureCall.__init__(self, returnType, argsPre, [],
+ nativeMethodName, attr.isStatic(), descriptor,
+ attr, getter=True)
+
+class FakeArgument():
+ """
+ A class that quacks like an IDLArgument. This is used to make
+ setters look like method calls or for special operations.
+ """
+ def __init__(self, type, interfaceMember, allowTreatNonObjectAsNull=False):
+ self.type = type
+ self.optional = False
+ self.variadic = False
+ self.defaultValue = None
+ self._allowTreatNonObjectAsNull = allowTreatNonObjectAsNull
+ self.treatNullAs = interfaceMember.treatNullAs
+ self.enforceRange = False
+ self.clamp = False
+
+ def allowTreatNonCallableAsNull(self):
+ return self._allowTreatNonObjectAsNull
+
+class CGSetterCall(CGPerSignatureCall):
+ """
+ A class to generate a native object setter call for a particular IDL
+ setter.
+ """
+ def __init__(self, argsPre, argType, nativeMethodName, descriptor, attr):
+ CGPerSignatureCall.__init__(self, None, argsPre,
+ [FakeArgument(argType, attr, allowTreatNonObjectAsNull=True)],
+ nativeMethodName, attr.isStatic(), descriptor, attr,
+ setter=True)
+ def wrap_return_value(self):
+ # We have no return value
+ return "\nreturn 1;"
+ def getArgc(self):
+ return "1"
+ def getArgvDecl(self):
+ # We just get our stuff from our last arg no matter what
+ return ""
+
+class CGAbstractBindingMethod(CGAbstractExternMethod):
+ """
+ Common class to generate the JSNatives for all our methods, getters, and
+ setters. This will generate the function declaration and unwrap the
+ |this| object. Subclasses are expected to override the generate_code
+ function to do the rest of the work. This function should return a
+ CGThing which is already properly indented.
+ """
+ def __init__(self, descriptor, name, args, unwrapFailureCode=None):
+ CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args)
+
+ if unwrapFailureCode is None:
+ self.unwrapFailureCode = (
+ 'throw_type_error(cx, "\\"this\\" object does not '
+ 'implement interface %s.");\n'
+ 'return 0;' % descriptor.interface.identifier.name)
+ else:
+ self.unwrapFailureCode = unwrapFailureCode
+
+ def definition_body(self):
+ # Our descriptor might claim that we're not castable, simply because
+ # we're someone's consequential interface. But for this-unwrapping, we
+ # know that we're the real deal. So fake a descriptor here for
+ # consumption by FailureFatalCastableObjectUnwrapper.
+ unwrapThis = str(CastableObjectUnwrapper(
+ FakeCastableDescriptor(self.descriptor),
+ "obj", self.unwrapFailureCode))
+ unwrapThis = CGGeneric(
+ "let obj: *mut JSObject = JS_THIS_OBJECT(cx, vp as *mut JSVal);\n"
+ "if obj.is_null() {\n"
+ " return false as JSBool;\n"
+ "}\n"
+ "\n"
+ "let this: JS<%s> = %s;\n" % (self.descriptor.concreteType, unwrapThis))
+ return CGList([ unwrapThis, self.generate_code() ], "\n")
+
+ def generate_code(self):
+ assert(False) # Override me
+
+
+class CGAbstractStaticBindingMethod(CGAbstractMethod):
+ """
+ Common class to generate the JSNatives for all our static methods, getters
+ and setters. This will generate the function declaration and unwrap the
+ global object. Subclasses are expected to override the generate_code
+ function to do the rest of the work. This function should return a
+ CGThing which is already properly indented.
+ """
+ def __init__(self, descriptor, name):
+ args = [
+ Argument('*mut JSContext', 'cx'),
+ Argument('libc::c_uint', 'argc'),
+ Argument('*mut JSVal', 'vp'),
+ ]
+ CGAbstractMethod.__init__(self, descriptor, name, "JSBool", args, extern=True)
+
+ def definition_body(self):
+ return self.generate_code()
+
+ def generate_code(self):
+ assert False # Override me
+
+
+class CGGenericMethod(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL method..
+ """
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'),
+ Argument('*mut JSVal', 'vp')]
+ CGAbstractBindingMethod.__init__(self, descriptor, 'genericMethod', args)
+
+ def generate_code(self):
+ return CGGeneric(
+ "let _info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "return CallJitMethodOp(_info, cx, obj, this.unsafe_get() as *mut libc::c_void, argc, vp);")
+
+class CGSpecializedMethod(CGAbstractExternMethod):
+ """
+ A class for generating the C++ code for a specialized method that the JIT
+ can call with lower overhead.
+ """
+ def __init__(self, descriptor, method):
+ self.method = method
+ name = method.identifier.name
+ args = [Argument('*mut JSContext', 'cx'), Argument('JSHandleObject', '_obj'),
+ Argument('*const %s' % descriptor.concreteType, 'this'),
+ Argument('libc::c_uint', 'argc'), Argument('*mut JSVal', 'vp')]
+ CGAbstractExternMethod.__init__(self, descriptor, name, 'JSBool', args)
+
+ def definition_body(self):
+ nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
+ self.method)
+ return CGWrapper(CGMethodCall([], nativeName, self.method.isStatic(),
+ self.descriptor, self.method),
+ pre="let this = JS::from_raw(this);\n"
+ "let this = this.root();\n")
+
+ @staticmethod
+ def makeNativeName(descriptor, method):
+ return MakeNativeName(method.identifier.name)
+
+class CGStaticMethod(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the Rust code for an IDL static method.
+ """
+ def __init__(self, descriptor, method):
+ self.method = method
+ name = method.identifier.name
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
+ self.method)
+ return CGMethodCall([], nativeName, True, self.descriptor, self.method)
+
+
+class CGGenericGetter(CGAbstractBindingMethod):
+ """
+ A class for generating the C++ code for an IDL attribute getter.
+ """
+ def __init__(self, descriptor, lenientThis=False):
+ args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'),
+ Argument('*mut JSVal', 'vp')]
+ if lenientThis:
+ name = "genericLenientGetter"
+ unwrapFailureCode = (
+ "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"
+ "JS_SET_RVAL(cx, vp, JS::UndefinedValue());\n"
+ "return true;")
+ else:
+ name = "genericGetter"
+ unwrapFailureCode = None
+ CGAbstractBindingMethod.__init__(self, descriptor, name, args,
+ unwrapFailureCode)
+
+ def generate_code(self):
+ return CGGeneric(
+ "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "return CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, vp);\n")
+
+class CGSpecializedGetter(CGAbstractExternMethod):
+ """
+ A class for generating the code for a specialized attribute getter
+ that the JIT can call with lower overhead.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'get_' + attr.identifier.name
+ args = [ Argument('*mut JSContext', 'cx'),
+ Argument('JSHandleObject', '_obj'),
+ Argument('*const %s' % descriptor.concreteType, 'this'),
+ Argument('*mut JSVal', 'vp') ]
+ CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args)
+
+ def definition_body(self):
+ nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
+ self.attr)
+
+ return CGWrapper(CGGetterCall([], self.attr.type, nativeName,
+ self.descriptor, self.attr),
+ pre="let this = JS::from_raw(this);\n"
+ "let this = this.root();\n")
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ nativeName = MakeNativeName(attr.identifier.name)
+ infallible = ('infallible' in
+ descriptor.getExtendedAttributes(attr, getter=True))
+ if attr.type.nullable() or not infallible:
+ return "Get" + nativeName
+
+ return nativeName
+
+
+class CGStaticGetter(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static attribute getter.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'get_' + attr.identifier.name
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedGetter.makeNativeName(self.descriptor,
+ self.attr)
+ return CGGetterCall([], self.attr.type, nativeName, self.descriptor,
+ self.attr)
+
+
+class CGGenericSetter(CGAbstractBindingMethod):
+ """
+ A class for generating the Rust code for an IDL attribute setter.
+ """
+ def __init__(self, descriptor, lenientThis=False):
+ args = [Argument('*mut JSContext', 'cx'), Argument('libc::c_uint', 'argc'),
+ Argument('*mut JSVal', 'vp')]
+ if lenientThis:
+ name = "genericLenientSetter"
+ unwrapFailureCode = (
+ "MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"
+ "return true;")
+ else:
+ name = "genericSetter"
+ unwrapFailureCode = None
+ CGAbstractBindingMethod.__init__(self, descriptor, name, args,
+ unwrapFailureCode)
+
+ def generate_code(self):
+ return CGGeneric(
+ "let mut undef = UndefinedValue();\n"
+ "let argv: *mut JSVal = if argc != 0 { JS_ARGV(cx, vp) } else { &mut undef as *mut JSVal };\n"
+ "let info: *const JSJitInfo = RUST_FUNCTION_VALUE_TO_JITINFO(JS_CALLEE(cx, vp));\n"
+ "if CallJitPropertyOp(info, cx, obj, this.unsafe_get() as *mut libc::c_void, argv) == 0 {\n"
+ " return 0;\n"
+ "}\n"
+ "*vp = UndefinedValue();\n"
+ "return 1;")
+
+class CGSpecializedSetter(CGAbstractExternMethod):
+ """
+ A class for generating the code for a specialized attribute setter
+ that the JIT can call with lower overhead.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'set_' + attr.identifier.name
+ args = [ Argument('*mut JSContext', 'cx'),
+ Argument('JSHandleObject', '_obj'),
+ Argument('*const %s' % descriptor.concreteType, 'this'),
+ Argument('*mut JSVal', 'argv')]
+ CGAbstractExternMethod.__init__(self, descriptor, name, "JSBool", args)
+
+ def definition_body(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
+ self.attr)
+ return CGWrapper(CGSetterCall([], self.attr.type, nativeName,
+ self.descriptor, self.attr),
+ pre="let this = JS::from_raw(this);\n"
+ "let this = this.root();\n")
+
+ @staticmethod
+ def makeNativeName(descriptor, attr):
+ return "Set" + MakeNativeName(attr.identifier.name)
+
+
+class CGStaticSetter(CGAbstractStaticBindingMethod):
+ """
+ A class for generating the C++ code for an IDL static attribute setter.
+ """
+ def __init__(self, descriptor, attr):
+ self.attr = attr
+ name = 'set_' + attr.identifier.name
+ CGAbstractStaticBindingMethod.__init__(self, descriptor, name)
+
+ def generate_code(self):
+ nativeName = CGSpecializedSetter.makeNativeName(self.descriptor,
+ self.attr)
+ checkForArg = CGGeneric(
+ "let argv = JS_ARGV(cx, vp);\n"
+ "if (argc == 0) {\n"
+ " throw_type_error(cx, \"Not enough arguments to %s setter.\");\n"
+ " return 0;\n"
+ "}\n" % self.attr.identifier.name)
+ call = CGSetterCall([], self.attr.type, nativeName, self.descriptor,
+ self.attr)
+ return CGList([checkForArg, call])
+
+
+class CGMemberJITInfo(CGThing):
+ """
+ A class for generating the JITInfo for a property that points to
+ our specialized getter and setter.
+ """
+ def __init__(self, descriptor, member):
+ self.member = member
+ self.descriptor = descriptor
+
+ def defineJitInfo(self, infoName, opName, infallible):
+ protoID = "PrototypeList::id::%s as u32" % self.descriptor.name
+ depth = self.descriptor.interface.inheritanceDepth()
+ failstr = "true" if infallible else "false"
+ return ("\n"
+ "static %s: JSJitInfo = JSJitInfo {\n"
+ " op: %s as *const u8,\n"
+ " protoID: %s,\n"
+ " depth: %s,\n"
+ " isInfallible: %s, /* False in setters. */\n"
+ " isConstant: false /* Only relevant for getters. */\n"
+ "};\n" % (infoName, opName, protoID, depth, failstr))
+
+ def define(self):
+ if self.member.isAttr():
+ getterinfo = ("%s_getterinfo" % self.member.identifier.name)
+ getter = ("get_%s" % self.member.identifier.name)
+ getterinfal = "infallible" in self.descriptor.getExtendedAttributes(self.member, getter=True)
+ result = self.defineJitInfo(getterinfo, getter, getterinfal)
+ if not self.member.readonly:
+ setterinfo = ("%s_setterinfo" % self.member.identifier.name)
+ setter = ("set_%s" % self.member.identifier.name)
+ # Setters are always fallible, since they have to do a typed unwrap.
+ result += self.defineJitInfo(setterinfo, setter, False)
+ return result
+ if self.member.isMethod():
+ methodinfo = ("%s_methodinfo" % self.member.identifier.name)
+ # Actually a JSJitMethodOp, but JSJitPropertyOp by struct definition.
+ method = ("%s" % self.member.identifier.name)
+
+ # Methods are infallible if they are infallible, have no arguments
+ # to unwrap, and have a return type that's infallible to wrap up for
+ # return.
+ methodInfal = False
+ sigs = self.member.signatures()
+ if len(sigs) == 1:
+ # Don't handle overloading. If there's more than one signature,
+ # one of them must take arguments.
+ sig = sigs[0]
+ if len(sig[1]) == 0:
+ # No arguments and infallible return boxing
+ methodInfal = True
+
+ result = self.defineJitInfo(methodinfo, method, methodInfal)
+ return result
+ raise TypeError("Illegal member type to CGPropertyJITInfo")
+
+def getEnumValueName(value):
+ # Some enum values can be empty strings. Others might have weird
+ # characters in them. Deal with the former by returning "_empty",
+ # deal with possible name collisions from that by throwing if the
+ # enum value is actually "_empty", and throw on any value
+ # containing non-ASCII chars for now. Replace all chars other than
+ # [0-9A-Za-z_] with '_'.
+ if re.match("[^\x20-\x7E]", value):
+ raise SyntaxError('Enum value "' + value + '" contains non-ASCII characters')
+ if re.match("^[0-9]", value):
+ raise SyntaxError('Enum value "' + value + '" starts with a digit')
+ value = re.sub(r'[^0-9A-Za-z_]', '_', value)
+ if re.match("^_[A-Z]|__", value):
+ raise SyntaxError('Enum value "' + value + '" is reserved by the C++ spec')
+ if value == "_empty":
+ raise SyntaxError('"_empty" is not an IDL enum value we support yet')
+ if value == "":
+ return "_empty"
+ return MakeNativeName(value)
+
+class CGEnum(CGThing):
+ def __init__(self, enum):
+ CGThing.__init__(self)
+ inner = """
+use dom::bindings::conversions::ToJSValConvertible;
+use js::jsapi::JSContext;
+use js::jsval::JSVal;
+
+#[repr(uint)]
+#[deriving(Encodable, PartialEq)]
+pub enum valuelist {
+ %s
+}
+
+pub static strings: &'static [&'static str] = &[
+ %s,
+];
+
+impl ToJSValConvertible for valuelist {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ strings[*self as uint].to_string().to_jsval(cx)
+ }
+}
+""" % (",\n ".join(map(getEnumValueName, enum.values())),
+ ",\n ".join(['"%s"' % val for val in enum.values()]))
+
+ self.cgRoot = CGList([
+ CGNamespace.build([enum.identifier.name + "Values"],
+ CGIndenter(CGGeneric(inner)), public=True),
+ CGGeneric("pub type %s = self::%sValues::valuelist;\n" %
+ (enum.identifier.name, enum.identifier.name)),
+ ])
+
+ def define(self):
+ return self.cgRoot.define()
+
+
+def convertConstIDLValueToRust(value):
+ tag = value.type.tag()
+ if tag in [IDLType.Tags.int8, IDLType.Tags.uint8,
+ IDLType.Tags.int16, IDLType.Tags.uint16,
+ IDLType.Tags.int32, IDLType.Tags.uint32,
+ IDLType.Tags.int64, IDLType.Tags.uint64,
+ IDLType.Tags.float, IDLType.Tags.double]:
+ return str(value.value)
+
+ if tag == IDLType.Tags.bool:
+ return toStringBool(value.value)
+
+ raise TypeError("Const value of unhandled type: " + value.type)
+
+class CGConstant(CGThing):
+ def __init__(self, constants):
+ CGThing.__init__(self)
+ self.constants = constants
+
+ def define(self):
+ def stringDecl(const):
+ name = const.identifier.name
+ value = convertConstIDLValueToRust(const.value)
+ return CGGeneric("pub static %s: %s = %s;\n" % (name, builtinNames[const.value.type.tag()], value))
+
+ return CGIndenter(CGList(stringDecl(m) for m in self.constants)).define()
+
+def getUnionTypeTemplateVars(type, descriptorProvider):
+ # For dictionaries and sequences we need to pass None as the failureCode
+ # for getJSToNativeConversionTemplate.
+ # Also, for dictionaries we would need to handle conversion of
+ # null/undefined to the dictionary correctly.
+ if type.isDictionary() or type.isSequence():
+ raise TypeError("Can't handle dictionaries or sequences in unions")
+
+ if type.isGeckoInterface():
+ name = type.inner.identifier.name
+ typeName = descriptorProvider.getDescriptor(name).nativeType
+ elif type.isEnum():
+ name = type.inner.identifier.name
+ typeName = name
+ elif type.isArray() or type.isSequence():
+ name = str(type)
+ #XXXjdm dunno about typeName here
+ typeName = "/*" + type.name + "*/"
+ elif type.isDOMString():
+ name = type.name
+ typeName = "DOMString"
+ elif type.isPrimitive():
+ name = type.name
+ typeName = builtinNames[type.tag()]
+ else:
+ name = type.name
+ typeName = "/*" + type.name + "*/"
+
+ template, _, _, _ = getJSToNativeConversionTemplate(
+ type, descriptorProvider, failureCode="return Ok(None);",
+ exceptionCode='return Err(());',
+ isDefinitelyObject=True)
+
+ assert not type.isObject()
+ jsConversion = string.Template(template).substitute({
+ "val": "value",
+ })
+ jsConversion = CGWrapper(CGGeneric(jsConversion), pre="Ok(Some(", post="))")
+
+ return {
+ "name": name,
+ "typeName": typeName,
+ "jsConversion": jsConversion,
+ }
+
+class CGUnionStruct(CGThing):
+ def __init__(self, type, descriptorProvider):
+ assert not type.nullable()
+ assert not type.hasNullableType
+
+ CGThing.__init__(self)
+ self.type = type
+ self.descriptorProvider = descriptorProvider
+
+ def define(self):
+ templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider),
+ self.type.flatMemberTypes)
+ enumValues = [
+ " e%s(%s)," % (v["name"], v["typeName"]) for v in templateVars
+ ]
+ enumConversions = [
+ " e%s(ref inner) => inner.to_jsval(cx)," % v["name"] for v in templateVars
+ ]
+ return ("""pub enum %s {
+%s
+}
+
+impl ToJSValConvertible for %s {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ match *self {
+%s
+ }
+ }
+}
+""") % (self.type, "\n".join(enumValues),
+ self.type, "\n".join(enumConversions))
+
+
+class CGUnionConversionStruct(CGThing):
+ def __init__(self, type, descriptorProvider):
+ assert not type.nullable()
+ assert not type.hasNullableType
+
+ CGThing.__init__(self)
+ self.type = type
+ self.descriptorProvider = descriptorProvider
+
+ def from_jsval(self):
+ memberTypes = self.type.flatMemberTypes
+ names = []
+ conversions = []
+
+ interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes)
+ if len(interfaceMemberTypes) > 0:
+ def get_name(memberType):
+ if self.type.isGeckoInterface():
+ return memberType.inner.identifier.name
+
+ return memberType.name
+
+ def get_match(name):
+ return (
+ "match %s::TryConvertTo%s(cx, value) {\n"
+ " Err(_) => return Err(()),\n"
+ " Ok(Some(value)) => return Ok(e%s(value)),\n"
+ " Ok(None) => (),\n"
+ "}\n") % (self.type, name, name)
+
+ typeNames = [get_name(memberType) for memberType in interfaceMemberTypes]
+ interfaceObject = CGList(CGGeneric(get_match(typeName)) for typeName in typeNames)
+ names.extend(typeNames)
+ else:
+ interfaceObject = None
+
+ arrayObjectMemberTypes = filter(lambda t: t.isArray() or t.isSequence(), memberTypes)
+ if len(arrayObjectMemberTypes) > 0:
+ assert len(arrayObjectMemberTypes) == 1
+ raise TypeError("Can't handle arrays or sequences in unions.")
+ else:
+ arrayObject = None
+
+ dateObjectMemberTypes = filter(lambda t: t.isDate(), memberTypes)
+ if len(dateObjectMemberTypes) > 0:
+ assert len(dateObjectMemberTypes) == 1
+ raise TypeError("Can't handle dates in unions.")
+ else:
+ dateObject = None
+
+ callbackMemberTypes = filter(lambda t: t.isCallback() or t.isCallbackInterface(), memberTypes)
+ if len(callbackMemberTypes) > 0:
+ assert len(callbackMemberTypes) == 1
+ raise TypeError("Can't handle callbacks in unions.")
+ else:
+ callbackObject = None
+
+ dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes)
+ if len(dictionaryMemberTypes) > 0:
+ raise TypeError("No support for unwrapping dictionaries as member "
+ "of a union")
+ else:
+ dictionaryObject = None
+
+ if callbackObject or dictionaryObject:
+ assert False, "Not currently supported"
+ else:
+ nonPlatformObject = None
+
+ objectMemberTypes = filter(lambda t: t.isObject(), memberTypes)
+ if len(objectMemberTypes) > 0:
+ raise TypeError("Can't handle objects in unions.")
+ else:
+ object = None
+
+ hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object
+ if hasObjectTypes:
+ assert interfaceObject
+ templateBody = CGList([interfaceObject], "\n")
+ conversions.append(CGIfWrapper(templateBody, "value.is_object()"))
+
+ otherMemberTypes = [
+ t for t in memberTypes if t.isPrimitive() or t.isString() or t.isEnum()
+ ]
+ if len(otherMemberTypes) > 0:
+ assert len(otherMemberTypes) == 1
+ memberType = otherMemberTypes[0]
+ if memberType.isEnum():
+ name = memberType.inner.identifier.name
+ else:
+ name = memberType.name
+ match = (
+ "match %s::TryConvertTo%s(cx, value) {\n"
+ " Err(_) => return Err(()),\n"
+ " Ok(Some(value)) => return Ok(e%s(value)),\n"
+ " Ok(None) => (),\n"
+ "}\n") % (self.type, name, name)
+ conversions.append(CGGeneric(match))
+ names.append(name)
+
+ conversions.append(CGGeneric(
+ "throw_not_in_union(cx, \"%s\");\n"
+ "Err(())" % ", ".join(names)))
+ method = CGWrapper(
+ CGIndenter(CGList(conversions, "\n\n")),
+ pre="fn from_jsval(cx: *mut JSContext, value: JSVal, _option: ()) -> Result<%s, ()> {\n" % self.type,
+ post="\n}")
+ return CGWrapper(
+ CGIndenter(method),
+ pre="impl FromJSValConvertible<()> for %s {\n" % self.type,
+ post="\n}")
+
+ def try_method(self, t):
+ templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider)
+ returnType = "Result<Option<%s>, ()>" % templateVars["typeName"]
+ jsConversion = templateVars["jsConversion"]
+
+ return CGWrapper(
+ CGIndenter(jsConversion, 4),
+ pre="fn TryConvertTo%s(cx: *mut JSContext, value: JSVal) -> %s {\n" % (t.name, returnType),
+ post="\n}")
+
+ def define(self):
+ from_jsval = self.from_jsval()
+ methods = CGIndenter(CGList([
+ self.try_method(t) for t in self.type.flatMemberTypes
+ ], "\n\n"))
+ return """
+%s
+
+impl %s {
+%s
+}
+""" % (from_jsval.define(), self.type, methods.define())
+
+
+class ClassItem:
+ """ Use with CGClass """
+ def __init__(self, name, visibility):
+ self.name = name
+ self.visibility = visibility
+ def declare(self, cgClass):
+ assert False
+ def define(self, cgClass):
+ assert False
+
+class ClassBase(ClassItem):
+ def __init__(self, name, visibility='pub'):
+ ClassItem.__init__(self, name, visibility)
+ def declare(self, cgClass):
+ return '%s %s' % (self.visibility, self.name)
+ def define(self, cgClass):
+ # Only in the header
+ return ''
+
+class ClassMethod(ClassItem):
+ def __init__(self, name, returnType, args, inline=False, static=False,
+ virtual=False, const=False, bodyInHeader=False,
+ templateArgs=None, visibility='public', body=None,
+ breakAfterReturnDecl="\n",
+ breakAfterSelf="\n", override=False):
+ """
+ override indicates whether to flag the method as MOZ_OVERRIDE
+ """
+ assert not override or virtual
+ self.returnType = returnType
+ self.args = args
+ self.inline = False
+ self.static = static
+ self.virtual = virtual
+ self.const = const
+ self.bodyInHeader = True
+ self.templateArgs = templateArgs
+ self.body = body
+ self.breakAfterReturnDecl = breakAfterReturnDecl
+ self.breakAfterSelf = breakAfterSelf
+ self.override = override
+ ClassItem.__init__(self, name, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.inline:
+ decorators.append('inline')
+ if declaring:
+ if self.static:
+ decorators.append('static')
+ if self.virtual:
+ decorators.append('virtual')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getBody(self):
+ # Override me or pass a string to constructor
+ assert self.body is not None
+ return self.body
+
+ def declare(self, cgClass):
+ templateClause = '<%s>' % ', '.join(self.templateArgs) \
+ if self.bodyInHeader and self.templateArgs else ''
+ args = ', '.join([a.declare() for a in self.args])
+ if self.bodyInHeader:
+ body = CGIndenter(CGGeneric(self.getBody())).define()
+ body = ' {\n' + body + '\n}'
+ else:
+ body = ';'
+
+ return string.Template("${decorators}%s"
+ "${visibility}fn ${name}${templateClause}(${args})${returnType}${const}${override}${body}%s" %
+ (self.breakAfterReturnDecl, self.breakAfterSelf)
+ ).substitute({
+ 'templateClause': templateClause,
+ 'decorators': self.getDecorators(True),
+ 'returnType': (" -> %s" % self.returnType) if self.returnType else "",
+ 'name': self.name,
+ 'const': ' const' if self.const else '',
+ 'override': ' MOZ_OVERRIDE' if self.override else '',
+ 'args': args,
+ 'body': body,
+ 'visibility': self.visibility + ' ' if self.visibility is not 'priv' else ''
+ })
+
+ def define(self, cgClass):
+ pass
+
+class ClassUsingDeclaration(ClassItem):
+ """"
+ Used for importing a name from a base class into a CGClass
+
+ baseClass is the name of the base class to import the name from
+
+ name is the name to import
+
+ visibility determines the visibility of the name (public,
+ protected, private), defaults to public.
+ """
+ def __init__(self, baseClass, name, visibility='public'):
+ self.baseClass = baseClass
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return string.Template("""using ${baseClass}::${name};
+""").substitute({ 'baseClass': self.baseClass,
+ 'name': self.name })
+
+ def define(self, cgClass):
+ return ''
+
+class ClassConstructor(ClassItem):
+ """
+ Used for adding a constructor to a CGClass.
+
+ args is a list of Argument objects that are the arguments taken by the
+ constructor.
+
+ inline should be True if the constructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the constructor (public,
+ protected, private), defaults to private.
+
+ explicit should be True if the constructor should be marked explicit.
+
+ baseConstructors is a list of strings containing calls to base constructors,
+ defaults to None.
+
+ body contains a string with the code for the constructor, defaults to empty.
+ """
+ def __init__(self, args, inline=False, bodyInHeader=False,
+ visibility="priv", explicit=False, baseConstructors=None,
+ body=""):
+ self.args = args
+ self.inline = False
+ self.bodyInHeader = bodyInHeader
+ self.explicit = explicit
+ self.baseConstructors = baseConstructors or []
+ self.body = body
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.explicit:
+ decorators.append('explicit')
+ if self.inline and declaring:
+ decorators.append('inline')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getInitializationList(self, cgClass):
+ items = [str(c) for c in self.baseConstructors]
+ for m in cgClass.members:
+ if not m.static:
+ initialize = m.body
+ if initialize:
+ items.append(m.name + "(" + initialize + ")")
+
+ if len(items) > 0:
+ return '\n : ' + ',\n '.join(items)
+ return ''
+
+ def getBody(self, cgClass):
+ initializers = [" parent: %s" % str(self.baseConstructors[0])]
+ return (self.body + (
+ "%s {\n"
+ "%s\n"
+ "}") % (cgClass.name, '\n'.join(initializers)))
+
+ def declare(self, cgClass):
+ args = ', '.join([a.declare() for a in self.args])
+ body = ' ' + self.getBody(cgClass);
+ body = stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+ body = ' {\n' + body + '}'
+
+ return string.Template("""pub fn ${decorators}new(${args}) -> ${className}${body}
+""").substitute({ 'decorators': self.getDecorators(True),
+ 'className': cgClass.getNameString(),
+ 'args': args,
+ 'body': body })
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ args = ', '.join([a.define() for a in self.args])
+
+ body = ' ' + self.getBody()
+ body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+
+ return string.Template("""${decorators}
+${className}::${className}(${args})${initializationList}
+{${body}}
+""").substitute({ 'decorators': self.getDecorators(False),
+ 'className': cgClass.getNameString(),
+ 'args': args,
+ 'initializationList': self.getInitializationList(cgClass),
+ 'body': body })
+
+class ClassDestructor(ClassItem):
+ """
+ Used for adding a destructor to a CGClass.
+
+ inline should be True if the destructor should be marked inline.
+
+ bodyInHeader should be True if the body should be placed in the class
+ declaration in the header.
+
+ visibility determines the visibility of the destructor (public,
+ protected, private), defaults to private.
+
+ body contains a string with the code for the destructor, defaults to empty.
+
+ virtual determines whether the destructor is virtual, defaults to False.
+ """
+ def __init__(self, inline=False, bodyInHeader=False,
+ visibility="private", body='', virtual=False):
+ self.inline = inline or bodyInHeader
+ self.bodyInHeader = bodyInHeader
+ self.body = body
+ self.virtual = virtual
+ ClassItem.__init__(self, None, visibility)
+
+ def getDecorators(self, declaring):
+ decorators = []
+ if self.virtual and declaring:
+ decorators.append('virtual')
+ if self.inline and declaring:
+ decorators.append('inline')
+ if decorators:
+ return ' '.join(decorators) + ' '
+ return ''
+
+ def getBody(self):
+ return self.body
+
+ def declare(self, cgClass):
+ if self.bodyInHeader:
+ body = ' ' + self.getBody();
+ body = stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+ body = '\n{\n' + body + '}'
+ else:
+ body = ';'
+
+ return string.Template("""${decorators}~${className}()${body}
+""").substitute({ 'decorators': self.getDecorators(True),
+ 'className': cgClass.getNameString(),
+ 'body': body })
+
+ def define(self, cgClass):
+ if self.bodyInHeader:
+ return ''
+
+ body = ' ' + self.getBody()
+ body = '\n' + stripTrailingWhitespace(body.replace('\n', '\n '))
+ if len(body) > 0:
+ body += '\n'
+
+ return string.Template("""${decorators}
+${className}::~${className}()
+{${body}}
+""").substitute({ 'decorators': self.getDecorators(False),
+ 'className': cgClass.getNameString(),
+ 'body': body })
+
+class ClassMember(ClassItem):
+ def __init__(self, name, type, visibility="priv", static=False,
+ body=None):
+ self.type = type;
+ self.static = static
+ self.body = body
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return '%s %s: %s,\n' % (self.visibility, self.name, self.type)
+
+ def define(self, cgClass):
+ if not self.static:
+ return ''
+ if self.body:
+ body = " = " + self.body
+ else:
+ body = ""
+ return '%s %s::%s%s;\n' % (self.type, cgClass.getNameString(),
+ self.name, body)
+
+class ClassTypedef(ClassItem):
+ def __init__(self, name, type, visibility="public"):
+ self.type = type
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return 'typedef %s %s;\n' % (self.type, self.name)
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+class ClassEnum(ClassItem):
+ def __init__(self, name, entries, values=None, visibility="public"):
+ self.entries = entries
+ self.values = values
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ entries = []
+ for i in range(0, len(self.entries)):
+ if not self.values or i >= len(self.values):
+ entry = '%s' % self.entries[i]
+ else:
+ entry = '%s = %s' % (self.entries[i], self.values[i])
+ entries.append(entry)
+ name = '' if not self.name else ' ' + self.name
+ return 'enum%s\n{\n %s\n};\n' % (name, ',\n '.join(entries))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+class ClassUnion(ClassItem):
+ def __init__(self, name, entries, visibility="public"):
+ self.entries = [entry + ";" for entry in entries]
+ ClassItem.__init__(self, name, visibility)
+
+ def declare(self, cgClass):
+ return 'union %s\n{\n %s\n};\n' % (self.name, '\n '.join(self.entries))
+
+ def define(self, cgClass):
+ # Only goes in the header
+ return ''
+
+class CGClass(CGThing):
+ def __init__(self, name, bases=[], members=[], constructors=[],
+ destructor=None, methods=[],
+ typedefs = [], enums=[], unions=[], templateArgs=[],
+ templateSpecialization=[], isStruct=False,
+ disallowCopyConstruction=False, indent='',
+ decorators='',
+ extradeclarations='',
+ extradefinitions=''):
+ CGThing.__init__(self)
+ self.name = name
+ self.bases = bases
+ self.members = members
+ self.constructors = constructors
+ # We store our single destructor in a list, since all of our
+ # code wants lists of members.
+ self.destructors = [destructor] if destructor else []
+ self.methods = methods
+ self.typedefs = typedefs
+ self.enums = enums
+ self.unions = unions
+ self.templateArgs = templateArgs
+ self.templateSpecialization = templateSpecialization
+ self.isStruct = isStruct
+ self.disallowCopyConstruction = disallowCopyConstruction
+ self.indent = indent
+ self.decorators = decorators
+ self.extradeclarations = extradeclarations
+ self.extradefinitions = extradefinitions
+
+ def getNameString(self):
+ className = self.name
+ if self.templateSpecialization:
+ className = className + \
+ '<%s>' % ', '.join([str(a) for a
+ in self.templateSpecialization])
+ return className
+
+ def define(self):
+ result = ''
+ if self.templateArgs:
+ templateArgs = [a.declare() for a in self.templateArgs]
+ templateArgs = templateArgs[len(self.templateSpecialization):]
+ result = result + self.indent + 'template <%s>\n' \
+ % ','.join([str(a) for a in templateArgs])
+
+ if self.templateSpecialization:
+ specialization = \
+ '<%s>' % ', '.join([str(a) for a in self.templateSpecialization])
+ else:
+ specialization = ''
+
+ myself = ''
+ if self.decorators != '':
+ myself += self.decorators + '\n'
+ myself += '%spub struct %s%s' % (self.indent, self.name, specialization)
+ result += myself
+
+ assert len(self.bases) == 1 #XXjdm Can we support multiple inheritance?
+
+ result += '{\n%s\n' % self.indent
+
+ if self.bases:
+ self.members = [ClassMember("parent", self.bases[0].name, "pub")] + self.members
+
+ result += CGIndenter(CGGeneric(self.extradeclarations),
+ len(self.indent)).define()
+
+ def declareMembers(cgClass, memberList):
+ result = ''
+
+ for member in memberList:
+ declaration = member.declare(cgClass)
+ declaration = CGIndenter(CGGeneric(declaration)).define()
+ result = result + declaration
+ return result
+
+ if self.disallowCopyConstruction:
+ class DisallowedCopyConstructor(object):
+ def __init__(self):
+ self.visibility = "private"
+ def declare(self, cgClass):
+ name = cgClass.getNameString()
+ return ("%s(const %s&) MOZ_DELETE;\n"
+ "void operator=(const %s) MOZ_DELETE;\n" % (name, name, name))
+ disallowedCopyConstructors = [DisallowedCopyConstructor()]
+ else:
+ disallowedCopyConstructors = []
+
+ order = [(self.enums, ''), (self.unions, ''),
+ (self.typedefs, ''), (self.members, '')]
+
+ for (memberList, separator) in order:
+ memberString = declareMembers(self, memberList)
+ if self.indent:
+ memberString = CGIndenter(CGGeneric(memberString),
+ len(self.indent)).define()
+ result = result + memberString
+
+ result += self.indent + '}\n\n'
+ result += 'impl %s {\n' % self.name
+
+ order = [(self.constructors + disallowedCopyConstructors, '\n'),
+ (self.destructors, '\n'), (self.methods, '\n)')]
+ for (memberList, separator) in order:
+ memberString = declareMembers(self, memberList)
+ if self.indent:
+ memberString = CGIndenter(CGGeneric(memberString),
+ len(self.indent)).define()
+ result = result + memberString
+
+ result += "}"
+ return result
+
+class CGProxySpecialOperation(CGPerSignatureCall):
+ """
+ Base class for classes for calling an indexed or named special operation
+ (don't use this directly, use the derived classes below).
+ """
+ def __init__(self, descriptor, operation):
+ nativeName = MakeNativeName(operation)
+ operation = descriptor.operations[operation]
+ assert len(operation.signatures()) == 1
+ signature = operation.signatures()[0]
+
+ (returnType, arguments) = signature
+
+ # We pass len(arguments) as the final argument so that the
+ # CGPerSignatureCall won't do any argument conversion of its own.
+ CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName,
+ False, descriptor, operation,
+ len(arguments))
+
+ if operation.isSetter() or operation.isCreator():
+ # arguments[0] is the index or name of the item that we're setting.
+ argument = arguments[1]
+ template, _, declType, needsRooting = getJSToNativeConversionTemplate(
+ argument.type, descriptor, treatNullAs=argument.treatNullAs)
+ templateValues = {
+ "val": "(*desc).value",
+ }
+ self.cgRoot.prepend(instantiateJSToNativeConversionTemplate(
+ template, templateValues, declType, argument.identifier.name,
+ needsRooting))
+ elif operation.isGetter():
+ self.cgRoot.prepend(CGGeneric("let mut found = false;"))
+
+ def getArguments(self):
+ def process(arg):
+ argVal = arg.identifier.name
+ if arg.type.isGeckoInterface() and not arg.type.unroll().inner.isCallback():
+ argVal += ".root_ref()"
+ return argVal
+ args = [(a, process(a)) for a in self.arguments]
+ if self.idlNode.isGetter():
+ args.append((FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
+ self.idlNode),
+ "&mut found"))
+ return args
+
+ def wrap_return_value(self):
+ if not self.idlNode.isGetter() or self.templateValues is None:
+ return ""
+
+ wrap = CGGeneric(wrapForType(**self.templateValues))
+ wrap = CGIfWrapper(wrap, "found")
+ return "\n" + wrap.define()
+
+class CGProxyIndexedGetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+ """
+ def __init__(self, descriptor, templateValues=None):
+ self.templateValues = templateValues
+ CGProxySpecialOperation.__init__(self, descriptor, 'IndexedGetter')
+
+class CGProxyIndexedSetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an indexed setter.
+ """
+ def __init__(self, descriptor):
+ CGProxySpecialOperation.__init__(self, descriptor, 'IndexedSetter')
+
+class CGProxyNamedGetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to an named getter. If templateValues is not None
+ the returned value will be wrapped with wrapForType using templateValues.
+ """
+ def __init__(self, descriptor, templateValues=None):
+ self.templateValues = templateValues
+ CGProxySpecialOperation.__init__(self, descriptor, 'NamedGetter')
+
+class CGProxyNamedSetter(CGProxySpecialOperation):
+ """
+ Class to generate a call to a named setter.
+ """
+ def __init__(self, descriptor):
+ CGProxySpecialOperation.__init__(self, descriptor, 'NamedSetter')
+
+class CGProxyUnwrap(CGAbstractMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSObject', 'obj')]
+ CGAbstractMethod.__init__(self, descriptor, "UnwrapProxy", '*const ' + descriptor.concreteType, args, alwaysInline=True)
+
+ def definition_body(self):
+ return CGGeneric("""/*if (xpc::WrapperFactory::IsXrayWrapper(obj)) {
+ obj = js::UnwrapObject(obj);
+}*/
+//MOZ_ASSERT(IsProxy(obj));
+let box_ = GetProxyPrivate(obj).to_private() as *const %s;
+return box_;""" % self.descriptor.concreteType)
+
+class CGDOMJSProxyHandler_getOwnPropertyDescriptor(CGAbstractExternMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'),
+ Argument('jsid', 'id'), Argument('bool', 'set'),
+ Argument('*mut JSPropertyDescriptor', 'desc')]
+ CGAbstractExternMethod.__init__(self, descriptor, "getOwnPropertyDescriptor",
+ "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ indexedSetter = self.descriptor.operations['IndexedSetter']
+
+ setOrIndexedGet = ""
+ if indexedGetter or indexedSetter:
+ setOrIndexedGet += "let index = GetArrayIndexFromId(cx, id);\n"
+
+ if indexedGetter:
+ readonly = toStringBool(self.descriptor.operations['IndexedSetter'] is None)
+ fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly
+ templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor}
+ get = ("if index.is_some() {\n" +
+ " let index = index.unwrap();\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define() + "\n" +
+ "}\n")
+
+ if indexedSetter or self.descriptor.operations['NamedSetter']:
+ setOrIndexedGet += "if set != 0 {\n"
+ if indexedSetter:
+ setOrIndexedGet += (" if index.is_some() {\n" +
+ " let index = index.unwrap();\n")
+ if not 'IndexedCreator' in self.descriptor.operations:
+ # FIXME need to check that this is a 'supported property index'
+ assert False
+ setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" +
+ " return true;\n" +
+ " }\n")
+ if self.descriptor.operations['NamedSetter']:
+ setOrIndexedGet += " if RUST_JSID_IS_STRING(id) {\n"
+ if not 'NamedCreator' in self.descriptor.operations:
+ # FIXME need to check that this is a 'supported property name'
+ assert False
+ setOrIndexedGet += (" FillPropertyDescriptor(&mut *desc, proxy, false);\n" +
+ " return true;\n" +
+ " }\n")
+ setOrIndexedGet += "}"
+ if indexedGetter:
+ setOrIndexedGet += (" else {\n" +
+ CGIndenter(CGGeneric(get)).define() +
+ "}")
+ setOrIndexedGet += "\n\n"
+ elif indexedGetter:
+ setOrIndexedGet += ("if !set {\n" +
+ CGIndenter(CGGeneric(get)).define() +
+ "}\n\n")
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter:
+ readonly = toStringBool(self.descriptor.operations['NamedSetter'] is None)
+ fillDescriptor = "FillPropertyDescriptor(&mut *desc, proxy, %s);\nreturn true;" % readonly
+ templateValues = {'jsvalRef': '(*desc).value', 'successCode': fillDescriptor}
+ # Once we start supporting OverrideBuiltins we need to make
+ # ResolveOwnProperty or EnumerateOwnProperties filter out named
+ # properties that shadow prototype properties.
+ namedGet = ("\n" +
+ "if !set && RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" +
+ " let name = jsid_to_str(cx, id);\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() + "\n" +
+ "}\n")
+ else:
+ namedGet = ""
+
+ return setOrIndexedGet + """let expando: *mut JSObject = GetExpandoObject(proxy);
+//if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
+if expando.is_not_null() {
+ let flags = if set { JSRESOLVE_ASSIGNING } else { 0 } | JSRESOLVE_QUALIFIED;
+ if JS_GetPropertyDescriptorById(cx, expando, id, flags, desc) == 0 {
+ return false;
+ }
+ if (*desc).obj.is_not_null() {
+ // Pretend the property lives on the wrapper.
+ (*desc).obj = proxy;
+ return true;
+ }
+}
+""" + namedGet + """
+(*desc).obj = ptr::mut_null();
+return true;"""
+
+ def definition_body(self):
+ return CGGeneric(self.getBody())
+
+class CGDOMJSProxyHandler_defineProperty(CGAbstractExternMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'),
+ Argument('jsid', 'id'),
+ Argument('*const JSPropertyDescriptor', 'desc')]
+ CGAbstractExternMethod.__init__(self, descriptor, "defineProperty", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ set = ""
+
+ indexedSetter = self.descriptor.operations['IndexedSetter']
+ if indexedSetter:
+ if not (self.descriptor.operations['IndexedCreator'] is indexedSetter):
+ raise TypeError("Can't handle creator that's different from the setter")
+ set += ("let index = GetArrayIndexFromId(cx, id);\n" +
+ "if index.is_some() {\n" +
+ " let index = index.unwrap();\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyIndexedSetter(self.descriptor)).define() +
+ " return true;\n" +
+ "}\n")
+ elif self.descriptor.operations['IndexedGetter']:
+ set += ("if GetArrayIndexFromId(cx, id).is_some() {\n" +
+ " return false;\n" +
+ " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" +
+ "}\n") % self.descriptor.name
+
+ namedSetter = self.descriptor.operations['NamedSetter']
+ if namedSetter:
+ if not self.descriptor.operations['NamedCreator'] is namedSetter:
+ raise TypeError("Can't handle creator that's different from the setter")
+ set += ("if RUST_JSID_IS_STRING(id) != 0 {\n" +
+ " let name = jsid_to_str(cx, id);\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyNamedSetter(self.descriptor)).define() + "\n" +
+ "}\n")
+ elif self.descriptor.operations['NamedGetter']:
+ set += ("if RUST_JSID_IS_STRING(id) {\n" +
+ " let name = jsid_to_str(cx, id);\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor)).define() +
+ " if (found) {\n"
+ " return false;\n" +
+ " //return ThrowErrorMessage(cx, MSG_NO_PROPERTY_SETTER, \"%s\");\n" +
+ " }\n" +
+ " return true;\n"
+ "}\n") % (self.descriptor.name)
+ return set + """return proxyhandler::defineProperty_(%s);""" % ", ".join(a.name for a in self.args)
+
+ def definition_body(self):
+ return CGGeneric(self.getBody())
+
+class CGDOMJSProxyHandler_hasOwn(CGAbstractExternMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'),
+ Argument('jsid', 'id'), Argument('*mut bool', 'bp')]
+ CGAbstractExternMethod.__init__(self, descriptor, "hasOwn", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ indexed = ("let index = GetArrayIndexFromId(cx, id);\n" +
+ "if index.is_some() {\n" +
+ " let index = index.unwrap();\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor)).define() + "\n" +
+ " *bp = found;\n" +
+ " return true;\n" +
+ "}\n\n")
+ else:
+ indexed = ""
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter:
+ named = ("if RUST_JSID_IS_STRING(id) != 0 && !HasPropertyOnPrototype(cx, proxy, id) {\n" +
+ " let name = jsid_to_str(cx, id);\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor)).define() + "\n" +
+ " *bp = found;\n"
+ " return true;\n"
+ "}\n" +
+ "\n")
+ else:
+ named = ""
+
+ return indexed + """let expando: *mut JSObject = GetExpandoObject(proxy);
+if expando.is_not_null() {
+ let mut b: JSBool = 1;
+ let ok = JS_HasPropertyById(cx, expando, id, &mut b) != 0;
+ *bp = b != 0;
+ if !ok || *bp {
+ return ok;
+ }
+}
+
+""" + named + """*bp = false;
+return true;"""
+
+ def definition_body(self):
+ return CGGeneric(self.getBody())
+
+class CGDOMJSProxyHandler_get(CGAbstractExternMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy'),
+ Argument('*mut JSObject', 'receiver'), Argument('jsid', 'id'),
+ Argument('*mut JSVal', 'vp')]
+ CGAbstractExternMethod.__init__(self, descriptor, "get", "bool", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ getFromExpando = """let expando = GetExpandoObject(proxy);
+if expando.is_not_null() {
+ let mut hasProp = 0;
+ if JS_HasPropertyById(cx, expando, id, &mut hasProp) == 0 {
+ return false;
+ }
+
+ if hasProp != 0 {
+ return JS_GetPropertyById(cx, expando, id, vp) != 0;
+ }
+}"""
+
+ templateValues = {
+ 'jsvalRef': '*vp',
+ 'successCode': 'return true;',
+ }
+
+ indexedGetter = self.descriptor.operations['IndexedGetter']
+ if indexedGetter:
+ getIndexedOrExpando = ("let index = GetArrayIndexFromId(cx, id);\n" +
+ "if index.is_some() {\n" +
+ " let index = index.unwrap();\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyIndexedGetter(self.descriptor, templateValues)).define())
+ getIndexedOrExpando += """
+ // Even if we don't have this index, we don't forward the
+ // get on to our expando object.
+} else {
+ %s
+}
+""" % (stripTrailingWhitespace(getFromExpando.replace('\n', '\n ')))
+ else:
+ getIndexedOrExpando = getFromExpando + "\n"
+
+ namedGetter = self.descriptor.operations['NamedGetter']
+ if namedGetter and False: #XXXjdm unfinished
+ getNamed = ("if (JSID_IS_STRING(id)) {\n" +
+ " let name = jsid_to_str(cx, id);\n" +
+ " let this = UnwrapProxy(proxy);\n" +
+ " let this = JS::from_raw(this);\n" +
+ " let this = this.root();\n" +
+ CGIndenter(CGProxyNamedGetter(self.descriptor, templateValues)).define() +
+ "}\n") % (self.descriptor.concreteType)
+ else:
+ getNamed = ""
+
+ return """//MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(proxy),
+ //"Should not have a XrayWrapper here");
+
+%s
+let mut found = false;
+if !GetPropertyOnPrototype(cx, proxy, id, &mut found, vp) {
+ return false;
+}
+
+if found {
+ return true;
+}
+%s
+*vp = UndefinedValue();
+return true;""" % (getIndexedOrExpando, getNamed)
+
+ def definition_body(self):
+ return CGGeneric(self.getBody())
+
+class CGDOMJSProxyHandler_obj_toString(CGAbstractExternMethod):
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('*mut JSObject', 'proxy')]
+ CGAbstractExternMethod.__init__(self, descriptor, "obj_toString", "*mut JSString", args)
+ self.descriptor = descriptor
+ def getBody(self):
+ stringifier = self.descriptor.operations['Stringifier']
+ if stringifier:
+ nativeName = MakeNativeName(stringifier.identifier.name)
+ signature = stringifier.signatures()[0]
+ returnType = signature[0]
+ extendedAttributes = self.descriptor.getExtendedAttributes(stringifier)
+ infallible = 'infallible' in extendedAttributes
+ if not infallible:
+ error = CGGeneric(
+ ('ThrowMethodFailedWithDetails(cx, rv, "%s", "toString");\n' +
+ "return NULL;") % self.descriptor.interface.identifier.name)
+ else:
+ error = None
+ call = CGCallGenerator(error, [], "", returnType, extendedAttributes, self.descriptor, nativeName, False, object="UnwrapProxy(proxy)")
+ return call.define() + """
+
+JSString* jsresult;
+return xpc_qsStringToJsstring(cx, result, &jsresult) ? jsresult : NULL;"""
+
+ return """let s = "%s".to_c_str();
+ _obj_toString(cx, s.as_ptr())""" % self.descriptor.name
+
+ def definition_body(self):
+ return CGGeneric(self.getBody())
+
+class CGAbstractClassHook(CGAbstractExternMethod):
+ """
+ Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
+ 'this' unwrapping as it assumes that the unwrapped type is always known.
+ """
+ def __init__(self, descriptor, name, returnType, args):
+ CGAbstractExternMethod.__init__(self, descriptor, name, returnType,
+ args)
+
+ def definition_body_prologue(self):
+ return CGGeneric("""\
+let this: *const %s = unwrap::<%s>(obj);
+""" % (self.descriptor.concreteType, self.descriptor.concreteType))
+
+ def definition_body(self):
+ return CGList([
+ self.definition_body_prologue(),
+ self.generate_code(),
+ ])
+
+ def generate_code(self):
+ # Override me
+ assert(False)
+
+def finalizeHook(descriptor, hookName, context):
+ release = """let val = JS_GetReservedSlot(obj, dom_object_slot(obj));
+let _: Box<%s> = mem::transmute(val.to_private());
+debug!("%s finalize: {:p}", this);
+""" % (descriptor.concreteType, descriptor.concreteType)
+ return release
+
+class CGClassTraceHook(CGAbstractClassHook):
+ """
+ A hook to trace through our native object; used for GC and CC
+ """
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSTracer', 'trc'), Argument('*mut JSObject', 'obj')]
+ CGAbstractClassHook.__init__(self, descriptor, TRACE_HOOK_NAME, 'void',
+ args)
+
+ def generate_code(self):
+ return CGGeneric("(*this).trace(%s);" % self.args[0].name)
+
+class CGClassConstructHook(CGAbstractExternMethod):
+ """
+ JS-visible constructor for our objects
+ """
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSContext', 'cx'), Argument('u32', 'argc'), Argument('*mut JSVal', 'vp')]
+ CGAbstractExternMethod.__init__(self, descriptor, CONSTRUCT_HOOK_NAME,
+ 'JSBool', args)
+ self._ctor = self.descriptor.interface.ctor()
+
+ def define(self):
+ if not self._ctor:
+ return ""
+ return CGAbstractExternMethod.define(self)
+
+ def definition_body(self):
+ preamble = CGGeneric("""\
+let global = global_object_for_js_object(JS_CALLEE(cx, vp).to_object());
+let global = global.root();
+""")
+ nativeName = MakeNativeName(self._ctor.identifier.name)
+ callGenerator = CGMethodCall(["&global.root_ref()"], nativeName, True,
+ self.descriptor, self._ctor)
+ return CGList([preamble, callGenerator])
+
+class CGClassFinalizeHook(CGAbstractClassHook):
+ """
+ A hook for finalize, used to release our native object.
+ """
+ def __init__(self, descriptor):
+ args = [Argument('*mut JSFreeOp', 'fop'), Argument('*mut JSObject', 'obj')]
+ CGAbstractClassHook.__init__(self, descriptor, FINALIZE_HOOK_NAME,
+ 'void', args)
+
+ def generate_code(self):
+ return CGGeneric(finalizeHook(self.descriptor, self.name, self.args[0].name))
+
+class CGDOMJSProxyHandlerDOMClass(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+ self.descriptor = descriptor
+
+ def define(self):
+ return """
+static Class: DOMClass = """ + DOMClass(self.descriptor) + """;
+
+"""
+
+
+class CGInterfaceTrait(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+
+ def argument_type(ty, optional=False, defaultValue=None, variadic=False):
+ _, _, declType, _ = getJSToNativeConversionTemplate(
+ ty, descriptor, isArgument=True)
+
+ if variadic:
+ declType = CGWrapper(declType, pre="Vec<", post=">")
+ elif optional and not defaultValue:
+ declType = CGWrapper(declType, pre="Option<", post=">")
+
+ if ty.isGeckoInterface() and not (ty.nullable() or optional):
+ declType = CGWrapper(declType, pre="&")
+ elif ty.isDictionary():
+ declType = CGWrapper(declType, pre="&")
+
+ return declType.define()
+
+ def attribute_arguments(needCx, argument=None):
+ if needCx:
+ yield "cx", "*mut JSContext"
+
+ if argument:
+ yield "value", argument_type(argument)
+
+ def method_arguments(returnType, arguments, trailing=None):
+ if needCx(returnType, arguments, True):
+ yield "cx", "*mut JSContext"
+
+ for argument in arguments:
+ ty = argument_type(argument.type, argument.optional,
+ argument.defaultValue, argument.variadic)
+ yield CGDictionary.makeMemberName(argument.identifier.name), ty
+
+ if trailing:
+ yield trailing
+
+ def return_type(rettype, infallible):
+ result = getRetvalDeclarationForType(rettype, descriptor)
+ if not infallible:
+ result = CGWrapper(result, pre="Fallible<", post=">")
+ return result.define()
+
+ def members():
+ for m in descriptor.interface.members:
+ if m.isMethod() and not m.isStatic():
+ name = CGSpecializedMethod.makeNativeName(descriptor, m)
+ infallible = 'infallible' in descriptor.getExtendedAttributes(m)
+ for idx, (rettype, arguments) in enumerate(m.signatures()):
+ arguments = method_arguments(rettype, arguments)
+ rettype = return_type(rettype, infallible)
+ yield name + ('_' * idx), arguments, rettype
+ elif m.isAttr() and not m.isStatic():
+ name = CGSpecializedGetter.makeNativeName(descriptor, m)
+ infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True)
+ needCx = typeNeedsCx(m.type)
+ yield name, attribute_arguments(needCx), return_type(m.type, infallible)
+
+ if not m.readonly:
+ name = CGSpecializedSetter.makeNativeName(descriptor, m)
+ infallible = 'infallible' in descriptor.getExtendedAttributes(m, setter=True)
+ if infallible:
+ rettype = "()"
+ else:
+ rettype = "ErrorResult"
+ yield name, attribute_arguments(needCx, m.type), rettype
+
+ if descriptor.proxy:
+ for name, operation in descriptor.operations.iteritems():
+ if not operation:
+ continue
+
+ assert len(operation.signatures()) == 1
+ rettype, arguments = operation.signatures()[0]
+
+ infallible = 'infallible' in descriptor.getExtendedAttributes(operation)
+ arguments = method_arguments(rettype, arguments, ("found", "&mut bool"))
+ rettype = return_type(rettype, infallible)
+ yield name, arguments, rettype
+
+ def fmt(arguments):
+ return "".join(", %s: %s" % argument for argument in arguments)
+
+ methods = CGList([
+ CGGeneric("fn %s(&self%s) -> %s;\n" % (name, fmt(arguments), rettype))
+ for name, arguments, rettype in members()
+ ], "")
+ self.cgRoot = CGWrapper(CGIndenter(methods),
+ pre="pub trait %sMethods {\n" % descriptor.interface.identifier.name,
+ post="}")
+
+ def define(self):
+ return self.cgRoot.define()
+
+
+class CGDescriptor(CGThing):
+ def __init__(self, descriptor):
+ CGThing.__init__(self)
+
+ assert not descriptor.interface.isCallback()
+
+ cgThings = []
+ cgThings.append(CGGetProtoObjectMethod(descriptor))
+ if descriptor.interface.hasInterfaceObject():
+ # https://github.com/mozilla/servo/issues/2665
+ # cgThings.append(CGGetConstructorObjectMethod(descriptor))
+ pass
+
+ (hasMethod, hasGetter, hasLenientGetter,
+ hasSetter, hasLenientSetter) = False, False, False, False, False
+ for m in descriptor.interface.members:
+ if m.isMethod() and not m.isIdentifierLess():
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticMethod(descriptor, m))
+ else:
+ cgThings.append(CGSpecializedMethod(descriptor, m))
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ hasMethod = True
+ elif m.isAttr():
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticGetter(descriptor, m))
+ else:
+ cgThings.append(CGSpecializedGetter(descriptor, m))
+ if m.hasLenientThis():
+ hasLenientGetter = True
+ else:
+ hasGetter = True
+
+ if not m.readonly:
+ if m.isStatic():
+ assert descriptor.interface.hasInterfaceObject()
+ cgThings.append(CGStaticSetter(descriptor, m))
+ else:
+ cgThings.append(CGSpecializedSetter(descriptor, m))
+ if m.hasLenientThis():
+ hasLenientSetter = True
+ else:
+ hasSetter = True
+
+ if not m.isStatic():
+ cgThings.append(CGMemberJITInfo(descriptor, m))
+ if hasMethod:
+ cgThings.append(CGGenericMethod(descriptor))
+ if hasGetter:
+ cgThings.append(CGGenericGetter(descriptor))
+ if hasLenientGetter:
+ pass
+ if hasSetter:
+ cgThings.append(CGGenericSetter(descriptor))
+ if hasLenientSetter:
+ pass
+
+ if descriptor.concrete:
+ cgThings.append(CGClassFinalizeHook(descriptor))
+ cgThings.append(CGClassTraceHook(descriptor))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGClassConstructHook(descriptor))
+ cgThings.append(CGInterfaceObjectJSClass(descriptor))
+
+ cgThings.append(CGPrototypeJSClass(descriptor))
+
+ properties = PropertyArrays(descriptor)
+ cgThings.append(CGGeneric(str(properties)))
+ cgThings.append(CGNativeProperties(descriptor, properties))
+ cgThings.append(CGNativePropertyHooks(descriptor, properties))
+ cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
+
+ cgThings.append(CGNamespace.build([descriptor.name + "Constants"],
+ CGConstant(m for m in descriptor.interface.members if m.isConst()),
+ public=True))
+
+ if descriptor.interface.hasInterfaceObject():
+ cgThings.append(CGDefineDOMInterfaceMethod(descriptor))
+
+ if descriptor.proxy:
+ cgThings.append(CGDefineProxyHandler(descriptor))
+
+ if descriptor.concrete:
+ if descriptor.proxy:
+ #cgThings.append(CGProxyIsProxy(descriptor))
+ cgThings.append(CGProxyUnwrap(descriptor))
+ cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
+ cgThings.append(CGDOMJSProxyHandler_getOwnPropertyDescriptor(descriptor))
+ cgThings.append(CGDOMJSProxyHandler_obj_toString(descriptor))
+ cgThings.append(CGDOMJSProxyHandler_get(descriptor))
+ cgThings.append(CGDOMJSProxyHandler_hasOwn(descriptor))
+ if descriptor.operations['IndexedSetter'] or descriptor.operations['NamedSetter']:
+ cgThings.append(CGDOMJSProxyHandler_defineProperty(descriptor))
+
+ #cgThings.append(CGDOMJSProxyHandler(descriptor))
+ #cgThings.append(CGIsMethod(descriptor))
+ pass
+ else:
+ cgThings.append(CGDOMJSClass(descriptor))
+ pass
+
+ cgThings.append(CGWrapMethod(descriptor))
+
+ cgThings.append(CGIDLInterface(descriptor))
+ cgThings.append(CGInterfaceTrait(descriptor))
+
+ cgThings = CGList(cgThings, "\n")
+ cgThings = CGWrapper(cgThings, pre='\n', post='\n')
+ #self.cgRoot = CGWrapper(CGNamespace(toBindingNamespace(descriptor.name),
+ # cgThings),
+ # post='\n')
+ self.cgRoot = cgThings
+
+ def define(self):
+ return self.cgRoot.define()
+
+class CGNamespacedEnum(CGThing):
+ def __init__(self, namespace, enumName, names, values, comment="", deriving=""):
+
+ if not values:
+ values = []
+
+ # Account for explicit enum values.
+ entries = []
+ for i in range(0, len(names)):
+ if len(values) > i and values[i] is not None:
+ entry = "%s = %s" % (names[i], values[i])
+ else:
+ entry = names[i]
+ entries.append(entry)
+
+ # Append a Count.
+ entries.append(enumName + 'Count = ' + str(len(entries)))
+
+ # Indent.
+ entries = [' ' + e for e in entries]
+
+ # Build the enum body.
+ enumstr = comment + 'pub enum %s {\n%s\n}\n' % (enumName, ',\n'.join(entries))
+ if deriving:
+ enumstr = ('#[deriving(%s)]\n' % deriving) + enumstr
+ curr = CGGeneric(enumstr)
+
+ # Add some whitespace padding.
+ curr = CGWrapper(curr, pre='\n',post='\n')
+
+ # Add the namespace.
+ curr = CGNamespace(namespace, curr, public=True)
+
+ # Add the typedef
+ #typedef = '\ntypedef %s::%s %s;\n\n' % (namespace, enumName, enumName)
+ #curr = CGList([curr, CGGeneric(typedef)])
+
+ # Save the result.
+ self.node = curr
+
+ def define(self):
+ return self.node.define()
+
+class CGDictionary(CGThing):
+ def __init__(self, dictionary, descriptorProvider):
+ self.dictionary = dictionary;
+ if all(CGDictionary(d, descriptorProvider).generatable for
+ d in CGDictionary.getDictionaryDependencies(dictionary)):
+ self.generatable = True
+ else:
+ self.generatable = False
+ # Nothing else to do here
+ return
+ self.memberInfo = [
+ (member,
+ getJSToNativeConversionTemplate(member.type,
+ descriptorProvider,
+ isMember="Dictionary",
+ defaultValue=member.defaultValue,
+ failureCode="return Err(());",
+ exceptionCode="return Err(());"))
+ for member in dictionary.members ]
+
+ def define(self):
+ if not self.generatable:
+ return ""
+ return self.struct() + "\n" + self.impl()
+
+ def struct(self):
+ d = self.dictionary
+ if d.parent:
+ inheritance = " pub parent: %s::%s<'a, 'b>,\n" % (self.makeModuleName(d.parent),
+ self.makeClassName(d.parent))
+ else:
+ inheritance = ""
+ memberDecls = [" pub %s: %s," %
+ (self.makeMemberName(m[0].identifier.name), self.getMemberType(m))
+ for m in self.memberInfo]
+
+ return (string.Template(
+ "pub struct ${selfName}<'a, 'b> {\n" +
+ "${inheritance}" +
+ "\n".join(memberDecls) + "\n" +
+ "}").substitute( { "selfName": self.makeClassName(d),
+ "inheritance": inheritance }))
+
+ def impl(self):
+ d = self.dictionary
+ if d.parent:
+ initParent = ("parent: match %s::%s::new(cx, val) {\n"
+ " Ok(parent) => parent,\n"
+ " Err(_) => return Err(()),\n"
+ "},\n") % (self.makeModuleName(d.parent),
+ self.makeClassName(d.parent))
+ else:
+ initParent = ""
+
+ def memberInit(memberInfo):
+ member, _ = memberInfo
+ name = self.makeMemberName(member.identifier.name)
+ conversion = self.getMemberConversion(memberInfo)
+ return CGGeneric("%s: %s,\n" % (name, conversion.define()))
+
+ memberInits = CGList([memberInit(m) for m in self.memberInfo])
+
+ return string.Template(
+ "impl<'a, 'b> ${selfName}<'a, 'b> {\n"
+ " pub fn empty() -> ${selfName}<'a, 'b> {\n"
+ " ${selfName}::new(ptr::mut_null(), NullValue()).unwrap()\n"
+ " }\n"
+ " pub fn new(cx: *mut JSContext, val: JSVal) -> Result<${selfName}<'a, 'b>, ()> {\n"
+ " let object = if val.is_null_or_undefined() {\n"
+ " ptr::mut_null()\n"
+ " } else if val.is_object() {\n"
+ " val.to_object()\n"
+ " } else {\n"
+ " throw_type_error(cx, \"Value not an object.\");\n"
+ " return Err(());\n"
+ " };\n"
+ " Ok(${selfName} {\n"
+ "${initParent}"
+ "${initMembers}"
+ " })\n"
+ " }\n"
+ "}").substitute({
+ "selfName": self.makeClassName(d),
+ "initParent": CGIndenter(CGGeneric(initParent), indentLevel=6).define(),
+ "initMembers": CGIndenter(memberInits, indentLevel=6).define(),
+ })
+
+ @staticmethod
+ def makeDictionaryName(dictionary):
+ return dictionary.identifier.name
+
+ def makeClassName(self, dictionary):
+ return self.makeDictionaryName(dictionary)
+
+ @staticmethod
+ def makeModuleName(dictionary):
+ name = dictionary.identifier.name
+ if name.endswith('Init'):
+ return toBindingNamespace(name.replace('Init', ''))
+ #XXXjdm This breaks on the test webidl files, sigh.
+ #raise TypeError("No idea how to find this dictionary's definition: " + name)
+ return "/* uh oh */ %s" % name
+
+ def getMemberType(self, memberInfo):
+ member, (_, _, declType, _) = memberInfo
+ if not member.defaultValue:
+ declType = CGWrapper(declType, pre="Option<", post=">")
+ return declType.define()
+
+ def getMemberConversion(self, memberInfo):
+ def indent(s):
+ return CGIndenter(CGGeneric(s), 8).define()
+
+ member, (templateBody, default, declType, _) = memberInfo
+ replacements = { "val": "value" }
+ conversion = string.Template(templateBody).substitute(replacements)
+
+ assert (member.defaultValue is None) == (default is None)
+ if not default:
+ default = "None"
+ conversion = "Some(%s)" % conversion
+
+ conversion = (
+ "match get_dictionary_property(cx, object, \"%s\") {\n"
+ " Err(()) => return Err(()),\n"
+ " Ok(Some(value)) => {\n"
+ "%s\n"
+ " },\n"
+ " Ok(None) => {\n"
+ "%s\n"
+ " },\n"
+ "}") % (member.identifier.name, indent(conversion), indent(default))
+
+ return CGGeneric(conversion)
+
+ @staticmethod
+ def makeIdName(name):
+ return name + "_id"
+
+ @staticmethod
+ def makeMemberName(name):
+ # Can't use Rust keywords as member names.
+ if name == "type":
+ return name + "_"
+ return name
+
+ @staticmethod
+ def getDictionaryDependencies(dictionary):
+ deps = set();
+ if dictionary.parent:
+ deps.add(dictionary.parent)
+ for member in dictionary.members:
+ if member.type.isDictionary():
+ deps.add(member.type.unroll().inner)
+ return deps
+
+class CGRegisterProtos(CGAbstractMethod):
+ def __init__(self, config):
+ arguments = [
+ Argument('*mut JSContext', 'cx'),
+ Argument('*mut JSObject', 'global'),
+ ]
+ CGAbstractMethod.__init__(self, None, 'Register', 'void', arguments,
+ unsafe=False, pub=True)
+ self.config = config
+
+ def definition_body(self):
+ return CGList([
+ CGGeneric("codegen::Bindings::%sBinding::DefineDOMInterface(cx, global);" % desc.name)
+ for desc in self.config.getDescriptors(hasInterfaceObject=True, register=True)
+ ], "\n")
+
+
+class CGRegisterProxyHandlersMethod(CGAbstractMethod):
+ def __init__(self, descriptors):
+ CGAbstractMethod.__init__(self, None, 'RegisterProxyHandlers', 'void', [],
+ unsafe=True, pub=True)
+ self.descriptors = descriptors
+
+ def definition_body(self):
+ return CGList([
+ CGGeneric("proxy_handlers[proxies::%s as uint] = codegen::Bindings::%sBinding::DefineProxyHandler();" % (desc.name, desc.name))
+ for desc in self.descriptors
+ ], "\n")
+
+
+class CGRegisterProxyHandlers(CGThing):
+ def __init__(self, config):
+ descriptors = config.getDescriptors(proxy=True)
+ length = len(descriptors)
+ self.root = CGList([
+ CGGeneric("pub static mut proxy_handlers: [*const libc::c_void, ..%d] = [0 as *const libc::c_void, ..%d];" % (length, length)),
+ CGRegisterProxyHandlersMethod(descriptors),
+ ], "\n")
+
+ def define(self):
+ return self.root.define()
+
+
+class CGBindingRoot(CGThing):
+ """
+ Root codegen class for binding generation. Instantiate the class, and call
+ declare or define to generate header or cpp code (respectively).
+ """
+ def __init__(self, config, prefix, webIDLFile):
+ descriptors = config.getDescriptors(webIDLFile=webIDLFile,
+ isCallback=False)
+ dictionaries = config.getDictionaries(webIDLFile=webIDLFile)
+
+ cgthings = []
+
+ mainCallbacks = config.getCallbacks(webIDLFile=webIDLFile)
+ callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
+ isCallback=True)
+
+ # Do codegen for all the enums
+ cgthings = [CGEnum(e) for e in config.getEnums(webIDLFile)]
+
+ cgthings.extend([CGDictionary(d, config.getDescriptorProvider())
+ for d in dictionaries])
+
+ # Do codegen for all the callbacks.
+ cgthings.extend(CGList([CGCallbackFunction(c, config.getDescriptorProvider()),
+ CGCallbackFunctionImpl(c)], "\n")
+ for c in mainCallbacks)
+
+ # Do codegen for all the descriptors
+ cgthings.extend([CGDescriptor(x) for x in descriptors])
+
+ # Do codegen for all the callback interfaces.
+ cgthings.extend(CGList([CGCallbackInterface(x),
+ CGCallbackFunctionImpl(x)], "\n")
+ for x in callbackDescriptors)
+
+ # And make sure we have the right number of newlines at the end
+ curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
+
+ # Wrap all of that in our namespaces.
+ #curr = CGNamespace.build(['dom'],
+ # CGWrapper(curr, pre="\n"))
+
+ # Add imports
+ #XXXjdm This should only import the namespace for the current binding,
+ # not every binding ever.
+ curr = CGImports(curr, descriptors, [
+ 'js',
+ 'js::{JS_ARGV, JS_CALLEE, JS_THIS_OBJECT}',
+ 'js::{JSCLASS_GLOBAL_SLOT_COUNT, JSCLASS_IS_DOMJSCLASS}',
+ 'js::{JSCLASS_IS_GLOBAL, JSCLASS_RESERVED_SLOTS_SHIFT}',
+ 'js::{JSCLASS_RESERVED_SLOTS_MASK, JSID_VOID, JSJitInfo}',
+ 'js::{JSPROP_ENUMERATE, JSPROP_NATIVE_ACCESSORS, JSPROP_SHARED}',
+ 'js::{JSRESOLVE_ASSIGNING, JSRESOLVE_QUALIFIED}',
+ 'js::jsapi::{JS_CallFunctionValue, JS_GetClass, JS_GetGlobalForObject}',
+ 'js::jsapi::{JS_GetObjectPrototype, JS_GetProperty, JS_GetPropertyById}',
+ 'js::jsapi::{JS_GetPropertyDescriptorById, JS_GetReservedSlot}',
+ 'js::jsapi::{JS_HasProperty, JS_HasPropertyById, JS_IsExceptionPending}',
+ 'js::jsapi::{JS_NewObject, JS_ObjectIsCallable, JS_SetPrototype}',
+ 'js::jsapi::{JS_SetReservedSlot, JS_WrapValue, JSBool, JSContext}',
+ 'js::jsapi::{JSClass, JSFreeOp, JSFunctionSpec, JSHandleObject, jsid}',
+ 'js::jsapi::{JSNativeWrapper, JSObject, JSPropertyDescriptor, JS_ArrayIterator}',
+ 'js::jsapi::{JSPropertyOpWrapper, JSPropertySpec, JS_PropertyStub}',
+ 'js::jsapi::{JSStrictPropertyOpWrapper, JSString, JSTracer, JS_ConvertStub}',
+ 'js::jsapi::{JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub}',
+ 'js::jsval::JSVal',
+ 'js::jsval::{ObjectValue, ObjectOrNullValue, PrivateValue}',
+ 'js::jsval::{NullValue, UndefinedValue}',
+ 'js::glue::{CallJitMethodOp, CallJitPropertyOp, CreateProxyHandler}',
+ 'js::glue::{GetProxyPrivate, NewProxyObject, ProxyTraps}',
+ 'js::glue::{RUST_FUNCTION_VALUE_TO_JITINFO}',
+ 'js::glue::{RUST_JS_NumberValue, RUST_JSID_IS_STRING}',
+ 'js::rust::with_compartment',
+ 'dom::types::*',
+ 'dom::bindings',
+ 'dom::bindings::global::GlobalRef',
+ 'dom::bindings::js::{JS, JSRef, Root, RootedReference, Temporary}',
+ 'dom::bindings::js::{OptionalRootable, OptionalRootedRootable, ResultRootable}',
+ 'dom::bindings::js::{OptionalRootedReference, OptionalOptionalRootedRootable}',
+ 'dom::bindings::utils::{CreateDOMGlobal, CreateInterfaceObjects2}',
+ 'dom::bindings::utils::{ConstantSpec, cx_for_dom_object}',
+ 'dom::bindings::utils::{dom_object_slot, DOM_OBJECT_SLOT, DOMClass}',
+ 'dom::bindings::utils::{DOMJSClass, JSCLASS_DOM_GLOBAL}',
+ 'dom::bindings::utils::{FindEnumStringIndex, GetArrayIndexFromId}',
+ 'dom::bindings::utils::{GetPropertyOnPrototype, GetProtoOrIfaceArray}',
+ 'dom::bindings::utils::{HasPropertyOnPrototype, IntVal}',
+ 'dom::bindings::utils::{jsid_to_str}',
+ 'dom::bindings::utils::global_object_for_js_object',
+ 'dom::bindings::utils::{Reflectable}',
+ 'dom::bindings::utils::{squirrel_away_unique}',
+ 'dom::bindings::utils::{ThrowingConstructor, unwrap, unwrap_jsmanaged}',
+ 'dom::bindings::utils::VoidVal',
+ 'dom::bindings::utils::get_dictionary_property',
+ 'dom::bindings::utils::{NativeProperties, NativePropertyHooks}',
+ 'dom::bindings::trace::JSTraceable',
+ 'dom::bindings::callback::{CallbackContainer,CallbackInterface,CallbackFunction}',
+ 'dom::bindings::callback::{CallSetup,ExceptionHandling}',
+ 'dom::bindings::callback::{WrapCallThisObject}',
+ 'dom::bindings::conversions::{FromJSValConvertible, ToJSValConvertible}',
+ 'dom::bindings::conversions::IDLInterface',
+ 'dom::bindings::conversions::{Default, Empty}',
+ 'dom::bindings::codegen::*',
+ 'dom::bindings::codegen::Bindings::*',
+ 'dom::bindings::codegen::RegisterBindings',
+ 'dom::bindings::codegen::UnionTypes::*',
+ 'dom::bindings::error::{FailureUnknown, Fallible, Error, ErrorResult}',
+ 'dom::bindings::error::throw_dom_exception',
+ 'dom::bindings::error::throw_type_error',
+ 'dom::bindings::proxyhandler',
+ 'dom::bindings::proxyhandler::{_obj_toString, defineProperty}',
+ 'dom::bindings::proxyhandler::{FillPropertyDescriptor, GetExpandoObject}',
+ 'dom::bindings::proxyhandler::{delete_, getPropertyDescriptor}',
+ 'dom::bindings::str::ByteString',
+ 'page::JSPageInfo',
+ 'libc',
+ 'servo_util::str::DOMString',
+ 'std::mem',
+ 'std::cmp',
+ 'std::ptr',
+ 'std::str',
+ 'std::num',
+ ])
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Store the final result.
+ self.root = curr
+
+ def define(self):
+ return stripTrailingWhitespace(self.root.define())
+
+class CGNativeMember(ClassMethod):
+ def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
+ breakAfter=True, passJSBitsAsNeeded=True, visibility="public",
+ jsObjectsArePtr=False, variadicIsSequence=False):
+ """
+ If jsObjectsArePtr is true, typed arrays and "object" will be
+ passed as JSObject*.
+
+ If passJSBitsAsNeeded is false, we don't automatically pass in a
+ JSContext* or a JSObject* based on the return and argument types.
+ """
+ self.descriptorProvider = descriptorProvider
+ self.member = member
+ self.extendedAttrs = extendedAttrs
+ self.passJSBitsAsNeeded = passJSBitsAsNeeded
+ self.jsObjectsArePtr = jsObjectsArePtr
+ self.variadicIsSequence = variadicIsSequence
+ breakAfterSelf = "\n" if breakAfter else ""
+ ClassMethod.__init__(self, name,
+ self.getReturnType(signature[0], False),
+ self.getArgs(signature[0], signature[1]),
+ static=member.isStatic(),
+ # Mark our getters, which are attrs that
+ # have a non-void return type, as const.
+ const=(not member.isStatic() and member.isAttr() and
+ not signature[0].isVoid()),
+ breakAfterReturnDecl=" ",
+ breakAfterSelf=breakAfterSelf,
+ visibility=visibility)
+
+ def getReturnType(self, type, isMember):
+ return self.getRetvalInfo(type, isMember)[0]
+
+ def getRetvalInfo(self, type, isMember):
+ """
+ Returns a tuple:
+
+ The first element is the type declaration for the retval
+
+ The second element is a template for actually returning a value stored in
+ "${declName}". This means actually returning it if
+ we're not outparam, else assigning to the "retval" outparam. If
+ isMember is true, this can be None, since in that case the caller will
+ never examine this value.
+ """
+ if type.isVoid():
+ typeDecl, template = "", ""
+ elif type.isPrimitive() and type.tag() in builtinNames:
+ result = CGGeneric(builtinNames[type.tag()])
+ if type.nullable():
+ raise TypeError("Nullable primitives are not supported here.")
+
+ typeDecl, template = result.define(), "return Ok(${declName});"
+ elif type.isDOMString():
+ if isMember:
+ # No need for a third element in the isMember case
+ typeDecl, template = "nsString", None
+ # Outparam
+ else:
+ typeDecl, template = "void", "retval = ${declName};"
+ elif type.isByteString():
+ if isMember:
+ # No need for a third element in the isMember case
+ typeDecl, template = "nsCString", None
+ # Outparam
+ typeDecl, template = "void", "retval = ${declName};"
+ elif type.isEnum():
+ enumName = type.unroll().inner.identifier.name
+ if type.nullable():
+ enumName = CGTemplatedType("Nullable",
+ CGGeneric(enumName)).define()
+ typeDecl, template = enumName, "return ${declName};"
+ elif type.isGeckoInterface():
+ iface = type.unroll().inner;
+ nativeType = self.descriptorProvider.getDescriptor(
+ iface.identifier.name).nativeType
+ # Now trim off unnecessary namespaces
+ nativeType = nativeType.split("::")
+ if nativeType[0] == "mozilla":
+ nativeType.pop(0)
+ if nativeType[0] == "dom":
+ nativeType.pop(0)
+ result = CGWrapper(CGGeneric("::".join(nativeType)), post="*")
+ # Since we always force an owning type for callback return values,
+ # our ${declName} is an OwningNonNull or nsRefPtr. So we can just
+ # .forget() to get our already_AddRefed.
+ typeDecl, template = result.define(), "return ${declName}.forget();"
+ elif type.isCallback():
+ typeDecl, template = \
+ ("already_AddRefed<%s>" % type.unroll().identifier.name,
+ "return ${declName}.forget();")
+ elif type.isAny():
+ typeDecl, template = "JSVal", "return Ok(${declName});"
+ elif type.isObject():
+ typeDecl, template = "JSObject*", "return ${declName};"
+ elif type.isSpiderMonkeyInterface():
+ if type.nullable():
+ returnCode = "return ${declName}.IsNull() ? nullptr : ${declName}.Value().Obj();"
+ else:
+ returnCode = "return ${declName}.Obj();"
+ typeDecl, template = "JSObject*", returnCode
+ elif type.isSequence():
+ # If we want to handle sequence-of-sequences return values, we're
+ # going to need to fix example codegen to not produce nsTArray<void>
+ # for the relevant argument...
+ assert not isMember
+ # Outparam.
+ if type.nullable():
+ returnCode = ("if (${declName}.IsNull()) {\n"
+ " retval.SetNull();\n"
+ "} else {\n"
+ " retval.SetValue().SwapElements(${declName}.Value());\n"
+ "}")
+ else:
+ returnCode = "retval.SwapElements(${declName});"
+ typeDecl, template = "void", returnCode
+ elif type.isDate():
+ result = CGGeneric("Date")
+ if type.nullable():
+ result = CGTemplatedType("Nullable", result)
+ typeDecl, template = result.define(), "return ${declName};"
+ else:
+ raise TypeError("Don't know how to declare return value for %s" % type)
+
+ if not 'infallible' in self.extendedAttrs:
+ if typeDecl:
+ typeDecl = "Fallible<%s>" % typeDecl
+ else:
+ typeDecl = "ErrorResult"
+ if not template:
+ template = "return Ok(());"
+ return typeDecl, template
+
+ def getArgs(self, returnType, argList):
+ args = [self.getArg(arg) for arg in argList]
+ # Now the outparams
+ if returnType.isDOMString():
+ args.append(Argument("nsString&", "retval"))
+ if returnType.isByteString():
+ args.append(Argument("nsCString&", "retval"))
+ elif returnType.isSequence():
+ nullable = returnType.nullable()
+ if nullable:
+ returnType = returnType.inner
+ # And now the actual underlying type
+ elementDecl = self.getReturnType(returnType.inner, True)
+ type = CGTemplatedType("nsTArray", CGGeneric(elementDecl))
+ if nullable:
+ type = CGTemplatedType("Nullable", type)
+ args.append(Argument("%s&" % type.define(), "retval"))
+ # The legacycaller thisval
+ if self.member.isMethod() and self.member.isLegacycaller():
+ # If it has an identifier, we can't deal with it yet
+ assert self.member.isIdentifierLess()
+ args.insert(0, Argument("JS::Value", "aThisVal"))
+ # And jscontext bits.
+ if needCx(returnType, argList, self.passJSBitsAsNeeded):
+ args.insert(0, Argument("JSContext*", "cx"))
+ # And if we're static, a global
+ if self.member.isStatic():
+ args.insert(0, Argument("const GlobalObject&", "global"))
+ return args
+
+ def doGetArgType(self, type, optional, isMember):
+ """
+ The main work of getArgType. Returns a string type decl, whether this
+ is a const ref, as well as whether the type should be wrapped in
+ Nullable as needed.
+
+ isMember can be false or one of the strings "Sequence" or "Variadic"
+ """
+ if type.isArray():
+ raise TypeError("Can't handle array arguments yet")
+
+ if type.isSequence():
+ nullable = type.nullable()
+ if nullable:
+ type = type.inner
+ elementType = type.inner
+ argType = self.getArgType(elementType, False, "Sequence")[0]
+ decl = CGTemplatedType("Sequence", argType)
+ return decl.define(), True, True
+
+ if type.isUnion():
+ if type.nullable():
+ type = type.inner
+ return str(type) + "::" + str(type), False, True
+
+ if type.isGeckoInterface() and not type.isCallbackInterface():
+ iface = type.unroll().inner
+ argIsPointer = type.nullable()
+ forceOwningType = iface.isCallback() or isMember
+ if argIsPointer:
+ if (optional or isMember) and forceOwningType:
+ typeDecl = "nsRefPtr<%s>"
+ else:
+ typeDecl = "*%s"
+ else:
+ if optional or isMember:
+ if forceOwningType:
+ typeDecl = "OwningNonNull<%s>"
+ else:
+ typeDecl = "NonNull<%s>"
+ else:
+ typeDecl = "%s"
+ descriptor = self.descriptorProvider.getDescriptor(iface.identifier.name)
+ return (typeDecl % descriptor.argumentType,
+ False, False)
+
+ if type.isSpiderMonkeyInterface():
+ if self.jsObjectsArePtr:
+ return "JSObject*", False, False
+
+ return type.name, True, True
+
+ if type.isDOMString():
+ declType = "DOMString"
+ return declType, True, False
+
+ if type.isByteString():
+ declType = "nsCString"
+ return declType, True, False
+
+ if type.isEnum():
+ return type.unroll().inner.identifier.name, False, True
+
+ if type.isCallback() or type.isCallbackInterface():
+ forceOwningType = optional or isMember
+ if type.nullable():
+ if forceOwningType:
+ declType = "nsRefPtr<%s>"
+ else:
+ declType = "%s*"
+ else:
+ if forceOwningType:
+ declType = "OwningNonNull<%s>"
+ else:
+ declType = "%s&"
+ if type.isCallback():
+ name = type.unroll().identifier.name
+ else:
+ name = type.unroll().inner.identifier.name
+ return declType % name, False, False
+
+ if type.isAny():
+ # Don't do the rooting stuff for variadics for now
+ if isMember:
+ declType = "JS::Value"
+ else:
+ declType = "JSVal"
+ return declType, False, False
+
+ if type.isObject():
+ if isMember:
+ declType = "JSObject*"
+ else:
+ declType = "JS::Handle<JSObject*>"
+ return declType, False, False
+
+ if type.isDictionary():
+ typeName = CGDictionary.makeDictionaryName(type.inner)
+ return typeName, True, True
+
+ if type.isDate():
+ return "Date", False, True
+
+ assert type.isPrimitive()
+
+ return builtinNames[type.tag()], False, True
+
+ def getArgType(self, type, optional, isMember):
+ """
+ Get the type of an argument declaration. Returns the type CGThing, and
+ whether this should be a const ref.
+
+ isMember can be False, "Sequence", or "Variadic"
+ """
+ (decl, ref, handleNullable) = self.doGetArgType(type, optional,
+ isMember)
+ decl = CGGeneric(decl)
+ if handleNullable and type.nullable():
+ decl = CGTemplatedType("Nullable", decl)
+ ref = True
+ if isMember == "Variadic":
+ arrayType = "Sequence" if self.variadicIsSequence else "nsTArray"
+ decl = CGTemplatedType(arrayType, decl)
+ ref = True
+ elif optional:
+ # Note: All variadic args claim to be optional, but we can just use
+ # empty arrays to represent them not being present.
+ decl = CGTemplatedType("Option", decl)
+ ref = False
+ return (decl, ref)
+
+ def getArg(self, arg):
+ """
+ Get the full argument declaration for an argument
+ """
+ (decl, ref) = self.getArgType(arg.type,
+ arg.optional and not arg.defaultValue,
+ "Variadic" if arg.variadic else False)
+ if ref:
+ decl = CGWrapper(decl, pre="&")
+
+ return Argument(decl.define(), arg.identifier.name)
+
+class CGCallback(CGClass):
+ def __init__(self, idlObject, descriptorProvider, baseName, methods,
+ getters=[], setters=[]):
+ self.baseName = baseName
+ self._deps = idlObject.getDeps()
+ name = idlObject.identifier.name
+ # For our public methods that needThisHandling we want most of the
+ # same args and the same return type as what CallbackMember
+ # generates. So we want to take advantage of all its
+ # CGNativeMember infrastructure, but that infrastructure can't deal
+ # with templates and most especially template arguments. So just
+ # cheat and have CallbackMember compute all those things for us.
+ realMethods = []
+ for method in methods:
+ if not method.needThisHandling:
+ realMethods.append(method)
+ else:
+ realMethods.extend(self.getMethodImpls(method))
+ CGClass.__init__(self, name,
+ bases=[ClassBase(baseName)],
+ constructors=self.getConstructors(),
+ methods=realMethods+getters+setters,
+ decorators="#[deriving(PartialEq,Clone,Encodable)]")
+
+ def getConstructors(self):
+ return [ClassConstructor(
+ [Argument("*mut JSObject", "aCallback")],
+ bodyInHeader=True,
+ visibility="pub",
+ explicit=False,
+ baseConstructors=[
+ "%s::new(aCallback)" % self.baseName
+ ])]
+
+ def getMethodImpls(self, method):
+ assert method.needThisHandling
+ args = list(method.args)
+ # Strip out the JSContext*/JSObject* args
+ # that got added.
+ assert args[0].name == "cx" and args[0].argType == "*mut JSContext"
+ assert args[1].name == "aThisObj" and args[1].argType == "*mut JSObject"
+ args = args[2:]
+ # Record the names of all the arguments, so we can use them when we call
+ # the private method.
+ argnames = [arg.name for arg in args]
+ argnamesWithThis = ["s.GetContext()", "thisObjJS"] + argnames
+ argnamesWithoutThis = ["s.GetContext()", "ptr::mut_null()"] + argnames
+ # Now that we've recorded the argnames for our call to our private
+ # method, insert our optional argument for deciding whether the
+ # CallSetup should re-throw exceptions on aRv.
+ args.append(Argument("ExceptionHandling", "aExceptionHandling",
+ "ReportExceptions"))
+
+ args[0] = Argument('&' + args[0].argType, args[0].name, args[0].default)
+ method.args[2] = args[0]
+
+ # And now insert our template argument.
+ argsWithoutThis = list(args)
+ args.insert(0, Argument("&JSRef<T>", "thisObj"))
+
+ # And the self argument
+ method.args.insert(0, Argument(None, "&self"))
+ args.insert(0, Argument(None, "&self"))
+ argsWithoutThis.insert(0, Argument(None, "&self"))
+
+ setupCall = ("let s = CallSetup::new(self, aExceptionHandling);\n"
+ "if s.GetContext().is_null() {\n"
+ " return Err(FailureUnknown);\n"
+ "}\n")
+
+ bodyWithThis = string.Template(
+ setupCall+
+ "let thisObjJS = WrapCallThisObject(s.GetContext(), thisObj);\n"
+ "if thisObjJS.is_null() {\n"
+ " return Err(FailureUnknown);\n"
+ "}\n"
+ "return ${methodName}(${callArgs});").substitute({
+ "callArgs" : ", ".join(argnamesWithThis),
+ "methodName": 'self.' + method.name,
+ })
+ bodyWithoutThis = string.Template(
+ setupCall +
+ "return ${methodName}(${callArgs});").substitute({
+ "callArgs" : ", ".join(argnamesWithoutThis),
+ "methodName": 'self.' + method.name,
+ })
+ return [ClassMethod(method.name+'_', method.returnType, args,
+ bodyInHeader=True,
+ templateArgs=["T: Reflectable"],
+ body=bodyWithThis,
+ visibility='pub'),
+ ClassMethod(method.name+'__', method.returnType, argsWithoutThis,
+ bodyInHeader=True,
+ body=bodyWithoutThis,
+ visibility='pub'),
+ method]
+
+ def deps(self):
+ return self._deps
+
+# We're always fallible
+def callbackGetterName(attr):
+ return "Get" + MakeNativeName(attr.identifier.name)
+
+def callbackSetterName(attr):
+ return "Set" + MakeNativeName(attr.identifier.name)
+
+class CGCallbackFunction(CGCallback):
+ def __init__(self, callback, descriptorProvider):
+ CGCallback.__init__(self, callback, descriptorProvider,
+ "CallbackFunction",
+ methods=[CallCallback(callback, descriptorProvider)])
+
+ def getConstructors(self):
+ return CGCallback.getConstructors(self)
+
+class CGCallbackFunctionImpl(CGGeneric):
+ def __init__(self, callback):
+ impl = string.Template("""impl CallbackContainer for ${type} {
+ fn new(callback: *mut JSObject) -> ${type} {
+ ${type}::new(callback)
+ }
+
+ fn callback(&self) -> *mut JSObject {
+ self.parent.callback()
+ }
+}
+
+impl ToJSValConvertible for ${type} {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ self.callback().to_jsval(cx)
+ }
+}
+""").substitute({"type": callback.name})
+ CGGeneric.__init__(self, impl)
+
+class CGCallbackInterface(CGCallback):
+ def __init__(self, descriptor):
+ iface = descriptor.interface
+ attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
+ getters = [CallbackGetter(a, descriptor) for a in attrs]
+ setters = [CallbackSetter(a, descriptor) for a in attrs
+ if not a.readonly]
+ methods = [m for m in iface.members
+ if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()]
+ methods = [CallbackOperation(m, sig, descriptor) for m in methods
+ for sig in m.signatures()]
+ assert not iface.isJSImplemented() or not iface.ctor()
+ CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
+ methods, getters=getters, setters=setters)
+
+class FakeMember():
+ def __init__(self):
+ self.treatNullAs = "Default"
+ def isStatic(self):
+ return False
+ def isAttr(self):
+ return False
+ def isMethod(self):
+ return False
+ def getExtendedAttribute(self, name):
+ return None
+
+class CallbackMember(CGNativeMember):
+ def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
+ """
+ needThisHandling is True if we need to be able to accept a specified
+ thisObj, False otherwise.
+ """
+ assert not rethrowContentException or not needThisHandling
+
+ self.retvalType = sig[0]
+ self.originalSig = sig
+ args = sig[1]
+ self.argCount = len(args)
+ if self.argCount > 0:
+ # Check for variadic arguments
+ lastArg = args[self.argCount-1]
+ if lastArg.variadic:
+ self.argCountStr = (
+ "(%d - 1) + %s.Length()" % (self.argCount,
+ lastArg.identifier.name))
+ else:
+ self.argCountStr = "%d" % self.argCount
+ self.needThisHandling = needThisHandling
+ # If needThisHandling, we generate ourselves as private and the caller
+ # will handle generating public versions that handle the "this" stuff.
+ visibility = "priv" if needThisHandling else "pub"
+ self.rethrowContentException = rethrowContentException
+ # We don't care, for callback codegen, whether our original member was
+ # a method or attribute or whatnot. Just always pass FakeMember()
+ # here.
+ CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
+ name, (self.retvalType, args),
+ extendedAttrs={},
+ passJSBitsAsNeeded=False,
+ visibility=visibility,
+ jsObjectsArePtr=True)
+ # We have to do all the generation of our body now, because
+ # the caller relies on us throwing if we can't manage it.
+ self.exceptionCode= "return Err(FailureUnknown);\n"
+ self.body = self.getImpl()
+
+ def getImpl(self):
+ replacements = {
+ "declRval": self.getRvalDecl(),
+ "returnResult": self.getResultConversion(),
+ "convertArgs": self.getArgConversions(),
+ "doCall": self.getCall(),
+ "setupCall": self.getCallSetup(),
+ }
+ if self.argCount > 0:
+ replacements["argCount"] = self.argCountStr
+ replacements["argvDecl"] = string.Template(
+ "let mut argv = Vec::from_elem(${argCount}, UndefinedValue());\n"
+ ).substitute(replacements)
+ else:
+ # Avoid weird 0-sized arrays
+ replacements["argvDecl"] = ""
+
+ # Newlines and semicolons are in the values
+ pre = string.Template(
+ "${setupCall}"
+ "${declRval}"
+ "${argvDecl}").substitute(replacements)
+ body = string.Template(
+ "${convertArgs}"
+ "${doCall}"
+ "${returnResult}").substitute(replacements)
+ return CGList([
+ CGGeneric(pre),
+ CGWrapper(CGIndenter(CGGeneric(body)),
+ pre="with_compartment(cx, self.parent.callback(), || {\n",
+ post="})")
+ ], "\n").define()
+
+ def getResultConversion(self):
+ replacements = {
+ "val": "rval",
+ "declName": "rvalDecl",
+ }
+
+ template, _, declType, needsRooting = getJSToNativeConversionTemplate(
+ self.retvalType,
+ self.descriptorProvider,
+ exceptionCode=self.exceptionCode,
+ isCallbackReturnValue="Callback",
+ # XXXbz we should try to do better here
+ sourceDescription="return value")
+
+ convertType = instantiateJSToNativeConversionTemplate(
+ template, replacements, declType, "rvalDecl", needsRooting)
+
+ assignRetval = string.Template(
+ self.getRetvalInfo(self.retvalType,
+ False)[1]).substitute(replacements)
+ return convertType.define() + "\n" + assignRetval + "\n"
+
+ def getArgConversions(self):
+ # Just reget the arglist from self.originalSig, because our superclasses
+ # just have way to many members they like to clobber, so I can't find a
+ # safe member name to store it in.
+ argConversions = [self.getArgConversion(i, arg) for (i, arg)
+ in enumerate(self.originalSig[1])]
+ # Do them back to front, so our argc modifications will work
+ # correctly, because we examine trailing arguments first.
+ argConversions.reverse();
+ # Wrap each one in a scope so that any locals it has don't leak out, and
+ # also so that we can just "break;" for our successCode.
+ argConversions = [CGWrapper(CGIndenter(CGGeneric(c)),
+ pre="loop {\n",
+ post="\nbreak;}\n")
+ for c in argConversions]
+ if self.argCount > 0:
+ argConversions.insert(0, self.getArgcDecl())
+ # And slap them together.
+ return CGList(argConversions, "\n\n").define() + "\n\n"
+
+ def getArgConversion(self, i, arg):
+ argval = arg.identifier.name
+
+ if arg.variadic:
+ argval = argval + "[idx]"
+ jsvalIndex = "%d + idx" % i
+ else:
+ jsvalIndex = "%d" % i
+ if arg.optional and not arg.defaultValue:
+ argval += ".clone().unwrap()"
+
+ conversion = wrapForType("*argv.get_mut(%s)" % jsvalIndex,
+ result=argval,
+ successCode="continue;" if arg.variadic else "break;")
+ if arg.variadic:
+ conversion = string.Template(
+ "for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {\n" +
+ CGIndenter(CGGeneric(conversion)).define() + "\n"
+ "}\n"
+ "break;").substitute({ "arg": arg.identifier.name })
+ elif arg.optional and not arg.defaultValue:
+ conversion = (
+ CGIfWrapper(CGGeneric(conversion),
+ "%s.is_some()" % arg.identifier.name).define() +
+ " else if (argc == %d) {\n"
+ " // This is our current trailing argument; reduce argc\n"
+ " argc -= 1;\n"
+ "} else {\n"
+ " *argv.get_mut(%d) = UndefinedValue();\n"
+ "}" % (i+1, i))
+ return conversion
+
+ def getArgs(self, returnType, argList):
+ args = CGNativeMember.getArgs(self, returnType, argList)
+ if not self.needThisHandling:
+ # Since we don't need this handling, we're the actual method that
+ # will be called, so we need an aRethrowExceptions argument.
+ if self.rethrowContentException:
+ args.append(Argument("JSCompartment*", "aCompartment", "nullptr"))
+ else:
+ args.append(Argument("ExceptionHandling", "aExceptionHandling",
+ "ReportExceptions"))
+ return args
+ # We want to allow the caller to pass in a "this" object, as
+ # well as a JSContext.
+ return [Argument("*mut JSContext", "cx"),
+ Argument("*mut JSObject", "aThisObj")] + args
+
+ def getCallSetup(self):
+ if self.needThisHandling:
+ # It's been done for us already
+ return ""
+ callSetup = "CallSetup s(CallbackPreserveColor(), aRv"
+ if self.rethrowContentException:
+ # getArgs doesn't add the aExceptionHandling argument but does add
+ # aCompartment for us.
+ callSetup += ", RethrowContentExceptions, aCompartment"
+ else:
+ callSetup += ", aExceptionHandling"
+ callSetup += ");"
+ return string.Template(
+ "${callSetup}\n"
+ "JSContext* cx = s.GetContext();\n"
+ "if (!cx) {\n"
+ " return Err(FailureUnknown);\n"
+ "}\n").substitute({
+ "callSetup": callSetup,
+ })
+
+ def getArgcDecl(self):
+ return CGGeneric("let mut argc = %su32;" % self.argCountStr);
+
+ @staticmethod
+ def ensureASCIIName(idlObject):
+ type = "attribute" if idlObject.isAttr() else "operation"
+ if re.match("[^\x20-\x7E]", idlObject.identifier.name):
+ raise SyntaxError('Callback %s name "%s" contains non-ASCII '
+ "characters. We can't handle that. %s" %
+ (type, idlObject.identifier.name,
+ idlObject.location))
+ if re.match('"', idlObject.identifier.name):
+ raise SyntaxError("Callback %s name '%s' contains "
+ "double-quote character. We can't handle "
+ "that. %s" %
+ (type, idlObject.identifier.name,
+ idlObject.location))
+
+class CallbackMethod(CallbackMember):
+ def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
+ CallbackMember.__init__(self, sig, name, descriptorProvider,
+ needThisHandling, rethrowContentException)
+ def getRvalDecl(self):
+ return "let mut rval = UndefinedValue();\n"
+
+ def getCall(self):
+ replacements = {
+ "thisObj": self.getThisObj(),
+ "getCallable": self.getCallableDecl()
+ }
+ if self.argCount > 0:
+ replacements["argv"] = "argv.as_mut_ptr()"
+ replacements["argc"] = "argc"
+ else:
+ replacements["argv"] = "nullptr"
+ replacements["argc"] = "0"
+ return string.Template("${getCallable}"
+ "let ok = unsafe {\n"
+ " JS_CallFunctionValue(cx, ${thisObj}, callable,\n"
+ " ${argc}, ${argv}, &mut rval)\n"
+ "};\n"
+ "if ok == 0 {\n"
+ " return Err(FailureUnknown);\n"
+ "}\n").substitute(replacements)
+
+class CallCallback(CallbackMethod):
+ def __init__(self, callback, descriptorProvider):
+ CallbackMethod.__init__(self, callback.signatures()[0], "Call",
+ descriptorProvider, needThisHandling=True)
+
+ def getThisObj(self):
+ return "aThisObj"
+
+ def getCallableDecl(self):
+ return "let callable = ObjectValue(unsafe {&*self.parent.callback()});\n";
+
+class CallbackOperationBase(CallbackMethod):
+ """
+ Common class for implementing various callback operations.
+ """
+ def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False):
+ self.singleOperation = singleOperation
+ self.methodName = jsName
+ CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException)
+
+ def getThisObj(self):
+ if not self.singleOperation:
+ return "self.parent.callback()"
+ # This relies on getCallableDecl declaring a boolean
+ # isCallable in the case when we're a single-operation
+ # interface.
+ return "if isCallable { aThisObj } else { self.parent.callback() }"
+
+ def getCallableDecl(self):
+ replacements = {
+ "methodName": self.methodName
+ }
+ getCallableFromProp = string.Template(
+ 'match self.parent.GetCallableProperty(cx, "${methodName}") {\n'
+ ' Err(_) => return Err(FailureUnknown),\n'
+ ' Ok(callable) => callable,\n'
+ '}').substitute(replacements)
+ if not self.singleOperation:
+ return 'JS::Rooted<JS::Value> callable(cx);\n' + getCallableFromProp
+ return (
+ 'let isCallable = unsafe { JS_ObjectIsCallable(cx, self.parent.callback()) != 0 };\n'
+ 'let callable =\n' +
+ CGIndenter(
+ CGIfElseWrapper('isCallable',
+ CGGeneric('unsafe { ObjectValue(&*self.parent.callback()) }'),
+ CGGeneric(getCallableFromProp))).define() + ';\n')
+
+class CallbackOperation(CallbackOperationBase):
+ """
+ Codegen actual WebIDL operations on callback interfaces.
+ """
+ def __init__(self, method, signature, descriptor):
+ self.ensureASCIIName(method)
+ jsName = method.identifier.name
+ CallbackOperationBase.__init__(self, signature,
+ jsName, MakeNativeName(jsName),
+ descriptor, descriptor.interface.isSingleOperationInterface(),
+ rethrowContentException=descriptor.interface.isJSImplemented())
+
+class CallbackGetter(CallbackMember):
+ def __init__(self, attr, descriptor):
+ self.ensureASCIIName(attr)
+ self.attrName = attr.identifier.name
+ CallbackMember.__init__(self,
+ (attr.type, []),
+ callbackGetterName(attr),
+ descriptor,
+ needThisHandling=False,
+ rethrowContentException=descriptor.interface.isJSImplemented())
+
+ def getRvalDecl(self):
+ return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
+
+ def getCall(self):
+ replacements = {
+ "attrName": self.attrName
+ }
+ return string.Template(
+ 'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n'
+ ' return Err(FailureUnknown);\n'
+ '}\n').substitute(replacements);
+
+class CallbackSetter(CallbackMember):
+ def __init__(self, attr, descriptor):
+ self.ensureASCIIName(attr)
+ self.attrName = attr.identifier.name
+ CallbackMember.__init__(self,
+ (BuiltinTypes[IDLBuiltinType.Types.void],
+ [FakeArgument(attr.type, attr)]),
+ callbackSetterName(attr),
+ descriptor,
+ needThisHandling=False,
+ rethrowContentException=descriptor.interface.isJSImplemented())
+
+ def getRvalDecl(self):
+ # We don't need an rval
+ return ""
+
+ def getCall(self):
+ replacements = {
+ "attrName": self.attrName,
+ "argv": "argv.handleAt(0)",
+ }
+ return string.Template(
+ 'MOZ_ASSERT(argv.length() == 1);\n'
+ 'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n'
+ ' return Err(FailureUnknown);\n'
+ '}\n').substitute(replacements)
+
+ def getArgcDecl(self):
+ return None
+
+class GlobalGenRoots():
+ """
+ Roots for global codegen.
+
+ To generate code, call the method associated with the target, and then
+ call the appropriate define/declare method.
+ """
+
+ @staticmethod
+ def PrototypeList(config):
+ # Prototype ID enum.
+ protos = [d.name for d in config.getDescriptors(isCallback=False)]
+ proxies = [d.name for d in config.getDescriptors(proxy=True)]
+
+ return CGList([
+ CGGeneric(AUTOGENERATED_WARNING_COMMENT),
+ CGGeneric("pub static MAX_PROTO_CHAIN_LENGTH: uint = %d;\n\n" % config.maxProtoChainLength),
+ CGNamespacedEnum('id', 'ID', protos, [0], deriving="PartialEq"),
+ CGNamespacedEnum('proxies', 'Proxy', proxies, [0], deriving="PartialEq"),
+ ])
+
+
+ @staticmethod
+ def RegisterBindings(config):
+ # TODO - Generate the methods we want
+ code = CGList([
+ CGRegisterProtos(config),
+ CGRegisterProxyHandlers(config),
+ ], "\n")
+
+ return CGImports(code, [], [
+ 'dom::bindings::codegen',
+ 'dom::bindings::codegen::PrototypeList::proxies',
+ 'js::jsapi::JSContext',
+ 'js::jsapi::JSObject',
+ 'libc',
+ ])
+
+ @staticmethod
+ def InterfaceTypes(config):
+ descriptors = [d.name for d in config.getDescriptors(register=True, isCallback=False)]
+ curr = CGList([CGGeneric("pub use dom::%s::%s;\n" % (name.lower(), name)) for name in descriptors])
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+ return curr
+
+ @staticmethod
+ def Bindings(config):
+
+ descriptors = (set(d.name + "Binding" for d in config.getDescriptors(register=True)) |
+ set(d.unroll().module() for d in config.callbacks))
+ curr = CGList([CGGeneric("pub mod %s;\n" % name) for name in sorted(descriptors)])
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+ return curr
+
+ @staticmethod
+ def InheritTypes(config):
+
+ descriptors = config.getDescriptors(register=True, isCallback=False)
+ allprotos = [CGGeneric("#![allow(unused_imports)]\n"),
+ CGGeneric("use dom::types::*;\n"),
+ CGGeneric("use dom::bindings::js::{JS, JSRef, Temporary};\n"),
+ CGGeneric("use dom::bindings::trace::JSTraceable;\n"),
+ CGGeneric("use dom::bindings::utils::Reflectable;\n"),
+ CGGeneric("use serialize::{Encodable, Encoder};\n"),
+ CGGeneric("use js::jsapi::JSTracer;\n\n")]
+ for descriptor in descriptors:
+ name = descriptor.name
+ protos = [CGGeneric('pub trait %s {}\n' % (name + 'Base'))]
+ for proto in descriptor.prototypeChain:
+ protos += [CGGeneric('impl %s for %s {}\n' % (proto + 'Base',
+ descriptor.concreteType))]
+ derived = [CGGeneric('pub trait %s { fn %s(&self) -> bool; }\n' %
+ (name + 'Derived', 'is_' + name.lower()))]
+ for protoName in descriptor.prototypeChain[1:-1]:
+ protoDescriptor = config.getDescriptor(protoName)
+ delegate = string.Template('''impl ${selfName} for ${baseName} {
+ fn ${fname}(&self) -> bool {
+ self.${parentName}.${fname}()
+ }
+}
+''').substitute({'fname': 'is_' + name.lower(),
+ 'selfName': name + 'Derived',
+ 'baseName': protoDescriptor.concreteType,
+ 'parentName': protoDescriptor.prototypeChain[-2].lower()})
+ derived += [CGGeneric(delegate)]
+ derived += [CGGeneric('\n')]
+
+ cast = [CGGeneric(string.Template('''pub trait ${castTraitName} {
+ #[inline(always)]
+ fn to_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a JSRef<'b, T>) -> Option<&'a JSRef<'b, Self>> {
+ match base.deref().${checkFn}() {
+ true => unsafe { Some(base.transmute()) },
+ false => None
+ }
+ }
+
+ #[inline(always)]
+ fn to_mut_ref<'a, 'b, T: ${toBound}+Reflectable>(base: &'a mut JSRef<'b, T>) -> Option<&'a mut JSRef<'b, Self>> {
+ match base.deref().${checkFn}() {
+ true => unsafe { Some(base.transmute_mut()) },
+ false => None
+ }
+ }
+
+ #[inline(always)]
+ fn from_ref<'a, 'b, T: ${fromBound}>(derived: &'a JSRef<'b, T>) -> &'a JSRef<'b, Self> {
+ unsafe { derived.transmute() }
+ }
+
+ #[inline(always)]
+ fn from_mut_ref<'a, 'b, T: ${fromBound}>(derived: &'a mut JSRef<'b, T>) -> &'a mut JSRef<'b, Self> {
+ unsafe { derived.transmute_mut() }
+ }
+
+ #[inline(always)]
+ fn from_temporary<T: ${fromBound}+Reflectable>(derived: Temporary<T>) -> Temporary<Self> {
+ unsafe { derived.transmute() }
+ }
+}
+''').substitute({'checkFn': 'is_' + name.lower(),
+ 'castTraitName': name + 'Cast',
+ 'fromBound': name + 'Base',
+ 'toBound': name + 'Derived'})),
+ CGGeneric("impl %s for %s {}\n\n" % (name + 'Cast', name))]
+
+ trace = [CGGeneric(string.Template('''impl JSTraceable for ${name} {
+ fn trace(&self, tracer: *mut JSTracer) {
+ unsafe {
+ self.encode(&mut *tracer).ok().expect("failed to encode");
+ }
+ }
+}
+''').substitute({'name': name}))]
+
+ allprotos += protos + derived + cast + trace
+
+ curr = CGList(allprotos)
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+ return curr
+
+ @staticmethod
+ def UnionTypes(config):
+
+ curr = UnionTypes(config.getDescriptors(),
+ config.getDictionaries(),
+ config.getCallbacks(),
+ config)
+
+ # Add the auto-generated comment.
+ curr = CGWrapper(curr, pre=AUTOGENERATED_WARNING_COMMENT)
+
+ # Done.
+ return curr
diff --git a/components/script/dom/bindings/codegen/Configuration.py b/components/script/dom/bindings/codegen/Configuration.py
new file mode 100644
index 00000000000..d9be43fd2e6
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Configuration.py
@@ -0,0 +1,341 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from WebIDL import IDLInterface
+
+autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
+
+class Configuration:
+ """
+ Represents global configuration state based on IDL parse data and
+ the configuration file.
+ """
+ def __init__(self, filename, parseData):
+ # Read the configuration file.
+ glbl = {}
+ execfile(filename, glbl)
+ config = glbl['DOMInterfaces']
+
+ # Build descriptors for all the interfaces we have in the parse data.
+ # This allows callers to specify a subset of interfaces by filtering
+ # |parseData|.
+ self.descriptors = []
+ self.interfaces = {}
+ self.maxProtoChainLength = 0;
+ for thing in parseData:
+ # Some toplevel things are sadly types, and those have an
+ # isInterface that doesn't mean the same thing as IDLObject's
+ # isInterface()...
+ if not isinstance(thing, IDLInterface):
+ continue
+
+ iface = thing
+ self.interfaces[iface.identifier.name] = iface
+ if iface.identifier.name not in config:
+ # Completely skip consequential interfaces with no descriptor
+ # if they have no interface object because chances are we
+ # don't need to do anything interesting with them.
+ if iface.isConsequential() and not iface.hasInterfaceObject():
+ continue
+ entry = {}
+ else:
+ entry = config[iface.identifier.name]
+ if not isinstance(entry, list):
+ assert isinstance(entry, dict)
+ entry = [entry]
+ self.descriptors.extend([Descriptor(self, iface, x) for x in entry])
+
+ # Mark the descriptors for which only a single nativeType implements
+ # an interface.
+ for descriptor in self.descriptors:
+ intefaceName = descriptor.interface.identifier.name
+ otherDescriptors = [d for d in self.descriptors
+ if d.interface.identifier.name == intefaceName]
+ descriptor.uniqueImplementation = len(otherDescriptors) == 1
+
+ self.enums = [e for e in parseData if e.isEnum()]
+ self.dictionaries = [d for d in parseData if d.isDictionary()]
+ self.callbacks = [c for c in parseData if
+ c.isCallback() and not c.isInterface()]
+
+ # Keep the descriptor list sorted for determinism.
+ self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
+
+ def getInterface(self, ifname):
+ return self.interfaces[ifname]
+ def getDescriptors(self, **filters):
+ """Gets the descriptors that match the given filters."""
+ curr = self.descriptors
+ for key, val in filters.iteritems():
+ if key == 'webIDLFile':
+ getter = lambda x: x.interface.filename()
+ elif key == 'hasInterfaceObject':
+ getter = lambda x: x.interface.hasInterfaceObject()
+ elif key == 'isCallback':
+ getter = lambda x: x.interface.isCallback()
+ elif key == 'isJSImplemented':
+ getter = lambda x: x.interface.isJSImplemented()
+ else:
+ getter = lambda x: getattr(x, key)
+ curr = filter(lambda x: getter(x) == val, curr)
+ return curr
+ def getEnums(self, webIDLFile):
+ return filter(lambda e: e.filename() == webIDLFile, self.enums)
+
+ @staticmethod
+ def _filterForFile(items, webIDLFile=""):
+ """Gets the items that match the given filters."""
+ if not webIDLFile:
+ return items
+
+ return filter(lambda x: x.filename() == webIDLFile, items)
+
+ def getDictionaries(self, webIDLFile=""):
+ return self._filterForFile(self.dictionaries, webIDLFile=webIDLFile)
+ def getCallbacks(self, webIDLFile=""):
+ return self._filterForFile(self.callbacks, webIDLFile=webIDLFile)
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name.
+ """
+ iface = self.getInterface(interfaceName)
+ descriptors = self.getDescriptors(interface=iface)
+
+ # We should have exactly one result.
+ if len(descriptors) is not 1:
+ raise NoSuchDescriptorError("For " + interfaceName + " found " +
+ str(len(matches)) + " matches");
+ return descriptors[0]
+ def getDescriptorProvider(self):
+ """
+ Gets a descriptor provider that can provide descriptors as needed.
+ """
+ return DescriptorProvider(self)
+
+class NoSuchDescriptorError(TypeError):
+ def __init__(self, str):
+ TypeError.__init__(self, str)
+
+class DescriptorProvider:
+ """
+ A way of getting descriptors for interface names
+ """
+ def __init__(self, config):
+ self.config = config
+
+ def getDescriptor(self, interfaceName):
+ """
+ Gets the appropriate descriptor for the given interface name given the
+ context of the current descriptor.
+ """
+ return self.config.getDescriptor(interfaceName)
+
+class Descriptor(DescriptorProvider):
+ """
+ Represents a single descriptor for an interface. See Bindings.conf.
+ """
+ def __init__(self, config, interface, desc):
+ DescriptorProvider.__init__(self, config)
+ self.interface = interface
+
+ # Read the desc, and fill in the relevant defaults.
+ ifaceName = self.interface.identifier.name
+
+ # Callback types do not use JS smart pointers, so we should not use the
+ # built-in rooting mechanisms for them.
+ if self.interface.isCallback():
+ self.needsRooting = False
+ else:
+ self.needsRooting = True
+
+ self.returnType = desc.get('returnType', "Temporary<%s>" % ifaceName)
+ self.argumentType = "JSRef<%s>" % ifaceName
+ self.memberType = "Root<'a, 'b, %s>" % ifaceName
+ self.nativeType = desc.get('nativeType', 'JS<%s>' % ifaceName)
+ self.concreteType = desc.get('concreteType', ifaceName)
+ self.register = desc.get('register', True)
+ self.outerObjectHook = desc.get('outerObjectHook', 'None')
+
+ # If we're concrete, we need to crawl our ancestor interfaces and mark
+ # them as having a concrete descendant.
+ self.concrete = desc.get('concrete', True)
+ if self.concrete:
+ self.proxy = False
+ operations = {
+ 'IndexedGetter': None,
+ 'IndexedSetter': None,
+ 'IndexedCreator': None,
+ 'IndexedDeleter': None,
+ 'NamedGetter': None,
+ 'NamedSetter': None,
+ 'NamedCreator': None,
+ 'NamedDeleter': None,
+ 'Stringifier': None
+ }
+ iface = self.interface
+ while iface:
+ for m in iface.members:
+ if not m.isMethod():
+ continue
+
+ def addOperation(operation, m):
+ if not operations[operation]:
+ operations[operation] = m
+ def addIndexedOrNamedOperation(operation, m):
+ self.proxy = True
+ if m.isIndexed():
+ operation = 'Indexed' + operation
+ else:
+ assert m.isNamed()
+ operation = 'Named' + operation
+ addOperation(operation, m)
+
+ if m.isStringifier():
+ addOperation('Stringifier', m)
+ else:
+ if m.isGetter():
+ addIndexedOrNamedOperation('Getter', m)
+ if m.isSetter():
+ addIndexedOrNamedOperation('Setter', m)
+ if m.isCreator():
+ addIndexedOrNamedOperation('Creator', m)
+ if m.isDeleter():
+ addIndexedOrNamedOperation('Deleter', m)
+ raise TypeError("deleter specified on %s but we "
+ "don't support deleters yet" %
+ self.interface.identifier.name)
+
+ iface.setUserData('hasConcreteDescendant', True)
+ iface = iface.parent
+
+ if self.proxy:
+ self.operations = operations
+ iface = self.interface
+ while iface:
+ iface.setUserData('hasProxyDescendant', True)
+ iface = iface.parent
+
+ self.name = interface.identifier.name
+
+ # self.extendedAttributes is a dict of dicts, keyed on
+ # all/getterOnly/setterOnly and then on member name. Values are an
+ # array of extended attributes.
+ self.extendedAttributes = { 'all': {}, 'getterOnly': {}, 'setterOnly': {} }
+
+ def addExtendedAttribute(attribute, config):
+ def add(key, members, attribute):
+ for member in members:
+ self.extendedAttributes[key].setdefault(member, []).append(attribute)
+
+ if isinstance(config, dict):
+ for key in ['all', 'getterOnly', 'setterOnly']:
+ add(key, config.get(key, []), attribute)
+ elif isinstance(config, list):
+ add('all', config, attribute)
+ else:
+ assert isinstance(config, str)
+ if config == '*':
+ iface = self.interface
+ while iface:
+ add('all', map(lambda m: m.name, iface.members), attribute)
+ iface = iface.parent
+ else:
+ add('all', [config], attribute)
+
+ # Build the prototype chain.
+ self.prototypeChain = []
+ parent = interface
+ while parent:
+ self.prototypeChain.insert(0, parent.identifier.name)
+ parent = parent.parent
+ config.maxProtoChainLength = max(config.maxProtoChainLength,
+ len(self.prototypeChain))
+
+ def getExtendedAttributes(self, member, getter=False, setter=False):
+ def maybeAppendInfallibleToAttrs(attrs, throws):
+ if throws is None:
+ attrs.append("infallible")
+ elif throws is True:
+ pass
+ else:
+ raise TypeError("Unknown value for 'Throws'")
+
+ name = member.identifier.name
+ if member.isMethod():
+ attrs = self.extendedAttributes['all'].get(name, [])
+ throws = member.getExtendedAttribute("Throws")
+ maybeAppendInfallibleToAttrs(attrs, throws)
+ return attrs
+
+ assert member.isAttr()
+ assert bool(getter) != bool(setter)
+ key = 'getterOnly' if getter else 'setterOnly'
+ attrs = self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, [])
+ throws = member.getExtendedAttribute("Throws")
+ if throws is None:
+ throwsAttr = "GetterThrows" if getter else "SetterThrows"
+ throws = member.getExtendedAttribute(throwsAttr)
+ maybeAppendInfallibleToAttrs(attrs, throws)
+ return attrs
+
+ def isGlobal(self):
+ """
+ Returns true if this is the primary interface for a global object
+ of some sort.
+ """
+ return (self.interface.getExtendedAttribute("Global") or
+ self.interface.getExtendedAttribute("PrimaryGlobal"))
+
+
+# Some utility methods
+def getTypesFromDescriptor(descriptor):
+ """
+ Get all argument and return types for all members of the descriptor
+ """
+ members = [m for m in descriptor.interface.members]
+ if descriptor.interface.ctor():
+ members.append(descriptor.interface.ctor())
+ members.extend(descriptor.interface.namedConstructors)
+ signatures = [s for m in members if m.isMethod() for s in m.signatures()]
+ types = []
+ for s in signatures:
+ assert len(s) == 2
+ (returnType, arguments) = s
+ types.append(returnType)
+ types.extend(a.type for a in arguments)
+
+ types.extend(a.type for a in members if a.isAttr())
+ return types
+
+def getFlatTypes(types):
+ retval = set()
+ for type in types:
+ type = type.unroll()
+ if type.isUnion():
+ retval |= set(type.flatMemberTypes)
+ else:
+ retval.add(type)
+ return retval
+
+def getTypesFromDictionary(dictionary):
+ """
+ Get all member types for this dictionary
+ """
+ types = []
+ curDict = dictionary
+ while curDict:
+ types.extend([m.type for m in curDict.members])
+ curDict = curDict.parent
+ return types
+
+def getTypesFromCallback(callback):
+ """
+ Get the types this callback depends on: its return type and the
+ types of its arguments.
+ """
+ sig = callback.signatures()[0]
+ types = [sig[0]] # Return type
+ types.extend(arg.type for arg in sig[1]) # Arguments
+ return types
diff --git a/components/script/dom/bindings/codegen/DOMJSClass.h b/components/script/dom/bindings/codegen/DOMJSClass.h
new file mode 100644
index 00000000000..151960b5901
--- /dev/null
+++ b/components/script/dom/bindings/codegen/DOMJSClass.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMJSClass_h
+#define mozilla_dom_DOMJSClass_h
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+#include "mozilla/dom/PrototypeList.h" // auto-generated
+
+// We use slot 0 for holding the raw object. This is safe for both
+// globals and non-globals.
+#define DOM_OBJECT_SLOT 0
+
+// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT. We have to
+// start at 1 past JSCLASS_GLOBAL_SLOT_COUNT because XPConnect uses
+// that one.
+#define DOM_PROTOTYPE_SLOT (JSCLASS_GLOBAL_SLOT_COUNT + 1)
+
+// We use these flag bits for the new bindings.
+#define JSCLASS_DOM_GLOBAL JSCLASS_USERBIT1
+
+// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
+// LSetDOMProperty. Those constants need to be changed accordingly if this value
+// changes.
+#define DOM_PROTO_INSTANCE_CLASS_SLOT 0
+
+namespace mozilla {
+namespace dom {
+
+typedef bool
+(* ResolveProperty)(JSContext* cx, JSObject* wrapper, jsid id, bool set,
+ JSPropertyDescriptor* desc);
+typedef bool
+(* EnumerateProperties)(JSContext* cx, JSObject* wrapper,
+ JS::AutoIdVector& props);
+
+struct NativePropertyHooks
+{
+ ResolveProperty mResolveOwnProperty;
+ ResolveProperty mResolveProperty;
+ EnumerateProperties mEnumerateOwnProperties;
+ EnumerateProperties mEnumerateProperties;
+
+ const NativePropertyHooks *mProtoHooks;
+};
+
+struct DOMClass
+{
+ // A list of interfaces that this object implements, in order of decreasing
+ // derivedness.
+ const prototypes::ID mInterfaceChain[prototypes::id::_ID_Count];
+
+ // We store the DOM object in reserved slot with index DOM_OBJECT_SLOT or in
+ // the proxy private if we use a proxy object.
+ // Sometimes it's an nsISupports and sometimes it's not; this class tells
+ // us which it is.
+ const bool mDOMObjectIsISupports;
+
+ const NativePropertyHooks* mNativeHooks;
+};
+
+// Special JSClass for reflected DOM objects.
+struct DOMJSClass
+{
+ // It would be nice to just inherit from JSClass, but that precludes pure
+ // compile-time initialization of the form |DOMJSClass = {...};|, since C++
+ // only allows brace initialization for aggregate/POD types.
+ JSClass mBase;
+
+ DOMClass mClass;
+
+ static DOMJSClass* FromJSClass(JSClass* base) {
+ MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS);
+ return reinterpret_cast<DOMJSClass*>(base);
+ }
+ static const DOMJSClass* FromJSClass(const JSClass* base) {
+ MOZ_ASSERT(base->flags & JSCLASS_IS_DOMJSCLASS);
+ return reinterpret_cast<const DOMJSClass*>(base);
+ }
+
+ static DOMJSClass* FromJSClass(js::Class* base) {
+ return FromJSClass(Jsvalify(base));
+ }
+ static const DOMJSClass* FromJSClass(const js::Class* base) {
+ return FromJSClass(Jsvalify(base));
+ }
+
+ JSClass* ToJSClass() { return &mBase; }
+};
+
+inline bool
+HasProtoOrIfaceArray(JSObject* global)
+{
+ MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL);
+ // This can be undefined if we GC while creating the global
+ return !js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).isUndefined();
+}
+
+inline JSObject**
+GetProtoOrIfaceArray(JSObject* global)
+{
+ MOZ_ASSERT(js::GetObjectClass(global)->flags & JSCLASS_DOM_GLOBAL);
+ return static_cast<JSObject**>(
+ js::GetReservedSlot(global, DOM_PROTOTYPE_SLOT).toPrivate());
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_DOMJSClass_h */
diff --git a/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp b/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp
new file mode 100644
index 00000000000..af45cc6ed1a
--- /dev/null
+++ b/components/script/dom/bindings/codegen/DOMJSProxyHandler.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=2 sw=2 et tw=99 ft=cpp: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Util.h"
+
+#include "DOMJSProxyHandler.h"
+#include "xpcpublic.h"
+#include "xpcprivate.h"
+#include "XPCQuickStubs.h"
+#include "XPCWrapper.h"
+#include "WrapperFactory.h"
+#include "nsDOMClassInfo.h"
+#include "nsGlobalWindow.h"
+#include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/BindingUtils.h"
+
+#include "jsapi.h"
+
+using namespace JS;
+
+namespace mozilla {
+namespace dom {
+
+jsid s_length_id = JSID_VOID;
+
+bool
+DefineStaticJSVals(JSContext* cx)
+{
+ JSAutoRequest ar(cx);
+
+ return InternJSString(cx, s_length_id, "length");
+}
+
+
+int HandlerFamily;
+
+// Store the information for the specialized ICs.
+struct SetListBaseInformation
+{
+ SetListBaseInformation() {
+ js::SetListBaseInformation((void*) &HandlerFamily, js::JSSLOT_PROXY_EXTRA + JSPROXYSLOT_EXPANDO);
+ }
+};
+
+SetListBaseInformation gSetListBaseInformation;
+
+
+bool
+DefineConstructor(JSContext* cx, JSObject* obj, DefineInterface aDefine, nsresult* aResult)
+{
+ bool enabled;
+ bool defined = aDefine(cx, obj, &enabled);
+ MOZ_ASSERT(!defined || enabled,
+ "We defined a constructor but the new bindings are disabled?");
+ *aResult = defined ? NS_OK : NS_ERROR_FAILURE;
+ return enabled;
+}
+
+// static
+JSObject*
+DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JSObject* obj)
+{
+ NS_ASSERTION(IsDOMProxy(obj), "expected a DOM proxy object");
+ JSObject* expando = GetExpandoObject(obj);
+ if (!expando) {
+ expando = JS_NewObjectWithGivenProto(cx, nullptr, nullptr,
+ js::GetObjectParent(obj));
+ if (!expando) {
+ return NULL;
+ }
+
+ xpc::CompartmentPrivate* priv = xpc::GetCompartmentPrivate(obj);
+ if (!priv->RegisterDOMExpandoObject(obj)) {
+ return NULL;
+ }
+
+ nsWrapperCache* cache;
+ CallQueryInterface(UnwrapDOMObject<nsISupports>(obj, eProxyDOMObject), &cache);
+ cache->SetPreservingWrapper(true);
+
+ js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
+ }
+ return expando;
+}
+
+bool
+DOMProxyHandler::getPropertyDescriptor(JSContext* cx, JSObject* proxy, jsid id, bool set,
+ JSPropertyDescriptor* desc)
+{
+ if (!getOwnPropertyDescriptor(cx, proxy, id, set, desc)) {
+ return false;
+ }
+ if (desc->obj) {
+ return true;
+ }
+
+ JSObject* proto;
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ desc->obj = NULL;
+ return true;
+ }
+
+ return JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, desc);
+}
+
+bool
+DOMProxyHandler::defineProperty(JSContext* cx, JSObject* proxy, jsid id,
+ JSPropertyDescriptor* desc)
+{
+ if ((desc->attrs & JSPROP_GETTER) && desc->setter == JS_StrictPropertyStub) {
+ return JS_ReportErrorFlagsAndNumber(cx,
+ JSREPORT_WARNING | JSREPORT_STRICT |
+ JSREPORT_STRICT_MODE_ERROR,
+ js_GetErrorMessage, NULL,
+ JSMSG_GETTER_ONLY);
+ }
+
+ if (xpc::WrapperFactory::IsXrayWrapper(proxy)) {
+ return true;
+ }
+
+ JSObject* expando = EnsureExpandoObject(cx, proxy);
+ if (!expando) {
+ return false;
+ }
+
+ return JS_DefinePropertyById(cx, expando, id, desc->value, desc->getter, desc->setter,
+ desc->attrs);
+}
+
+bool
+DOMProxyHandler::delete_(JSContext* cx, JSObject* proxy, jsid id, bool* bp)
+{
+ JSBool b = true;
+
+ JSObject* expando;
+ if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
+ Value v;
+ if (!JS_DeletePropertyById2(cx, expando, id, &v) || !JS_ValueToBoolean(cx, v, &b)) {
+ return false;
+ }
+ }
+
+ *bp = !!b;
+ return true;
+}
+
+bool
+DOMProxyHandler::enumerate(JSContext* cx, JSObject* proxy, AutoIdVector& props)
+{
+ JSObject* proto;
+ if (!JS_GetPrototype(cx, proxy, &proto)) {
+ return false;
+ }
+ return getOwnPropertyNames(cx, proxy, props) &&
+ (!proto || js::GetPropertyNames(cx, proto, 0, &props));
+}
+
+bool
+DOMProxyHandler::fix(JSContext* cx, JSObject* proxy, Value* vp)
+{
+ vp->setUndefined();
+ return true;
+}
+
+bool
+DOMProxyHandler::has(JSContext* cx, JSObject* proxy, jsid id, bool* bp)
+{
+ if (!hasOwn(cx, proxy, id, bp)) {
+ return false;
+ }
+
+ if (*bp) {
+ // We have the property ourselves; no need to worry about our prototype
+ // chain.
+ return true;
+ }
+
+ // OK, now we have to look at the proto
+ JSObject *proto;
+ if (!js::GetObjectProto(cx, proxy, &proto)) {
+ return false;
+ }
+ if (!proto) {
+ return true;
+ }
+ JSBool protoHasProp;
+ bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp);
+ if (ok) {
+ *bp = protoHasProp;
+ }
+ return ok;
+}
+
+// static
+JSString*
+DOMProxyHandler::obj_toString(JSContext* cx, const char* className)
+{
+ size_t nchars = sizeof("[object ]") - 1 + strlen(className);
+ jschar* chars = static_cast<jschar*>(JS_malloc(cx, (nchars + 1) * sizeof(jschar)));
+ if (!chars) {
+ return NULL;
+ }
+
+ const char* prefix = "[object ";
+ nchars = 0;
+ while ((chars[nchars] = (jschar)*prefix) != 0) {
+ nchars++, prefix++;
+ }
+ while ((chars[nchars] = (jschar)*className) != 0) {
+ nchars++, className++;
+ }
+ chars[nchars++] = ']';
+ chars[nchars] = 0;
+
+ JSString* str = JS_NewUCString(cx, chars, nchars);
+ if (!str) {
+ JS_free(cx, chars);
+ }
+ return str;
+}
+
+int32_t
+IdToInt32(JSContext* cx, jsid id)
+{
+ JSAutoRequest ar(cx);
+
+ jsval idval;
+ double array_index;
+ int32_t i;
+ if (!::JS_IdToValue(cx, id, &idval) ||
+ !::JS_ValueToNumber(cx, idval, &array_index) ||
+ !::JS_DoubleIsInt32(array_index, &i)) {
+ return -1;
+ }
+
+ return i;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/components/script/dom/bindings/codegen/DOMJSProxyHandler.h b/components/script/dom/bindings/codegen/DOMJSProxyHandler.h
new file mode 100644
index 00000000000..394e2dc4d2f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/DOMJSProxyHandler.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_DOMJSProxyHandler_h
+#define mozilla_dom_DOMJSProxyHandler_h
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "jsproxy.h"
+#include "xpcpublic.h"
+#include "nsString.h"
+#include "mozilla/Likely.h"
+
+#define DOM_PROXY_OBJECT_SLOT js::JSSLOT_PROXY_PRIVATE
+
+namespace mozilla {
+namespace dom {
+
+enum {
+ JSPROXYSLOT_EXPANDO = 0
+};
+
+template<typename T> struct Prefable;
+
+class DOMProxyHandler : public DOMBaseProxyHandler
+{
+public:
+ DOMProxyHandler(const DOMClass& aClass)
+ : DOMBaseProxyHandler(true),
+ mClass(aClass)
+ {
+ }
+
+ bool getPropertyDescriptor(JSContext* cx, JSObject* proxy, jsid id, bool set,
+ JSPropertyDescriptor* desc);
+ bool defineProperty(JSContext* cx, JSObject* proxy, jsid id,
+ JSPropertyDescriptor* desc);
+ bool delete_(JSContext* cx, JSObject* proxy, jsid id, bool* bp);
+ bool enumerate(JSContext* cx, JSObject* proxy, JS::AutoIdVector& props);
+ bool fix(JSContext* cx, JSObject* proxy, JS::Value* vp);
+ bool has(JSContext* cx, JSObject* proxy, jsid id, bool* bp);
+ using js::BaseProxyHandler::obj_toString;
+
+ static JSObject* GetExpandoObject(JSObject* obj)
+ {
+ MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
+ JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+ return v.isUndefined() ? NULL : v.toObjectOrNull();
+ }
+ static JSObject* EnsureExpandoObject(JSContext* cx, JSObject* obj);
+
+ const DOMClass& mClass;
+
+protected:
+ static JSString* obj_toString(JSContext* cx, const char* className);
+};
+
+extern jsid s_length_id;
+
+int32_t IdToInt32(JSContext* cx, jsid id);
+
+inline int32_t
+GetArrayIndexFromId(JSContext* cx, jsid id)
+{
+ if (MOZ_LIKELY(JSID_IS_INT(id))) {
+ return JSID_TO_INT(id);
+ }
+ if (MOZ_LIKELY(id == s_length_id)) {
+ return -1;
+ }
+ if (MOZ_LIKELY(JSID_IS_ATOM(id))) {
+ JSAtom* atom = JSID_TO_ATOM(id);
+ jschar s = *js::GetAtomChars(atom);
+ if (MOZ_LIKELY((unsigned)s >= 'a' && (unsigned)s <= 'z'))
+ return -1;
+
+ uint32_t i;
+ JSLinearString* str = js::AtomToLinearString(JSID_TO_ATOM(id));
+ return js::StringIsArrayIndex(str, &i) ? i : -1;
+ }
+ return IdToInt32(cx, id);
+}
+
+inline void
+FillPropertyDescriptor(JSPropertyDescriptor* desc, JSObject* obj, bool readonly)
+{
+ desc->obj = obj;
+ desc->attrs = (readonly ? JSPROP_READONLY : 0) | JSPROP_ENUMERATE;
+ desc->getter = NULL;
+ desc->setter = NULL;
+ desc->shortid = 0;
+}
+
+inline void
+FillPropertyDescriptor(JSPropertyDescriptor* desc, JSObject* obj, jsval v, bool readonly)
+{
+ desc->value = v;
+ FillPropertyDescriptor(desc, obj, readonly);
+}
+
+JSObject*
+EnsureExpandoObject(JSContext* cx, JSObject* obj);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_DOMProxyHandler_h */
diff --git a/components/script/dom/bindings/codegen/ErrorResult.h b/components/script/dom/bindings/codegen/ErrorResult.h
new file mode 100644
index 00000000000..bbd9404a865
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ErrorResult.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * A struct for tracking exceptions that need to be thrown to JS.
+ */
+
+#ifndef mozilla_ErrorResult_h
+#define mozilla_ErrorResult_h
+
+#include "nscore.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+class ErrorResult {
+public:
+ ErrorResult() {
+ mResult = NS_OK;
+ }
+
+ void Throw(nsresult rv) {
+ MOZ_ASSERT(NS_FAILED(rv), "Please don't try throwing success");
+ mResult = rv;
+ }
+
+ // In the future, we can add overloads of Throw that take more
+ // interesting things, like strings or DOM exception types or
+ // something if desired.
+
+ // Backwards-compat to make conversion simpler. We don't call
+ // Throw() here because people can easily pass success codes to
+ // this.
+ void operator=(nsresult rv) {
+ mResult = rv;
+ }
+
+ bool Failed() const {
+ return NS_FAILED(mResult);
+ }
+
+ nsresult ErrorCode() const {
+ return mResult;
+ }
+
+private:
+ nsresult mResult;
+
+ // Not to be implemented, to make sure people always pass this by
+ // reference, not by value.
+ ErrorResult(const ErrorResult&) MOZ_DELETE;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_ErrorResult_h */
diff --git a/components/script/dom/bindings/codegen/Errors.msg b/components/script/dom/bindings/codegen/Errors.msg
new file mode 100644
index 00000000000..81d6624cec8
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Errors.msg
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The format for each error message is:
+ *
+ * MSG_DEF(<SYMBOLIC_NAME>, <ARGUMENT_COUNT>, <FORMAT_STRING>)
+ *
+ * where
+ *
+ * <SYMBOLIC_NAME> is a legal C++ identifer that will be used in the source.
+ *
+ * <ARGUMENT_COUNT> is an integer literal specifying the total number of
+ * replaceable arguments in the following format string.
+ *
+ * <FORMAT_STRING> is a string literal, containing <ARGUMENT_COUNT> sequences
+ * {X} where X is an integer representing the argument number that will
+ * be replaced with a string value when the error is reported.
+ */
+
+MSG_DEF(MSG_INVALID_ENUM_VALUE, 2, "Value '{0}' is not a valid value for enumeration {1}.")
+MSG_DEF(MSG_MISSING_ARGUMENTS, 1, "Not enough arguments to {0}.")
+MSG_DEF(MSG_NOT_OBJECT, 0, "Value not an object.")
+MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 1, "Value does not implement interface {0}.")
+MSG_DEF(MSG_NOT_IN_UNION, 1, "Value could not be converted to any of: {0}.")
+MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.")
+MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.")
+MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.")
+MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.")
diff --git a/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py b/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py
new file mode 100644
index 00000000000..1bb50afaee7
--- /dev/null
+++ b/components/script/dom/bindings/codegen/GenerateCSS2PropertiesWebIDL.py
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+import string
+
+propList = eval(sys.stdin.read())
+props = ""
+for [prop, pref] in propList:
+ extendedAttrs = ["Throws", "TreatNullAs=EmptyString"]
+ if pref is not "":
+ extendedAttrs.append("Pref=%s" % pref)
+ if not prop.startswith("Moz"):
+ prop = prop[0].lower() + prop[1:]
+ # Unfortunately, even some of the getters here are fallible
+ # (e.g. on nsComputedDOMStyle).
+ props += " [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
+ prop)
+
+idlFile = open(sys.argv[1], "r");
+idlTemplate = idlFile.read();
+idlFile.close();
+
+print ("/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n" +
+ string.Template(idlTemplate).substitute({ "props": props }))
diff --git a/components/script/dom/bindings/codegen/GlobalGen.py b/components/script/dom/bindings/codegen/GlobalGen.py
new file mode 100644
index 00000000000..cdca464e029
--- /dev/null
+++ b/components/script/dom/bindings/codegen/GlobalGen.py
@@ -0,0 +1,83 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# We do one global pass over all the WebIDL to generate our prototype enum
+# and generate information for subsequent phases.
+
+import sys
+sys.path.append("./parser/")
+sys.path.append("./ply/")
+import os
+import cStringIO
+import WebIDL
+import cPickle
+from Configuration import *
+from CodegenRust import GlobalGenRoots, replaceFileIfChanged
+# import Codegen in general, so we can set a variable on it
+import Codegen
+
+def generate_file(config, name, filename):
+ root = getattr(GlobalGenRoots, name)(config)
+ code = root.define()
+
+ if replaceFileIfChanged(filename, code):
+ print "Generating %s" % (filename)
+ else:
+ print "%s hasn't changed - not touching it" % (filename)
+
+def main():
+ # Parse arguments.
+ from optparse import OptionParser
+ usageString = "usage: %prog [options] webidldir [files]"
+ o = OptionParser(usage=usageString)
+ o.add_option("--cachedir", dest='cachedir', default=None,
+ help="Directory in which to cache lex/parse tables.")
+ o.add_option("--verbose-errors", action='store_true', default=False,
+ help="When an error happens, display the Python traceback.")
+ (options, args) = o.parse_args()
+
+ if len(args) < 2:
+ o.error(usageString)
+
+ configFile = args[0]
+ baseDir = args[1]
+ fileList = args[2:]
+
+ # Parse the WebIDL.
+ parser = WebIDL.Parser(options.cachedir)
+ for filename in fileList:
+ fullPath = os.path.normpath(os.path.join(baseDir, filename))
+ f = open(fullPath, 'rb')
+ lines = f.readlines()
+ f.close()
+ parser.parse(''.join(lines), fullPath)
+ parserResults = parser.finish()
+
+ # Write the parser results out to a pickle.
+ resultsFile = open('ParserResults.pkl', 'wb')
+ cPickle.dump(parserResults, resultsFile, -1)
+ resultsFile.close()
+
+ # Load the configuration.
+ config = Configuration(configFile, parserResults)
+
+ # Generate the prototype list.
+ generate_file(config, 'PrototypeList', 'PrototypeList.rs')
+
+ # Generate the common code.
+ generate_file(config, 'RegisterBindings', 'RegisterBindings.rs')
+
+ # Generate the type list.
+ generate_file(config, 'InterfaceTypes', 'InterfaceTypes.rs')
+
+ # Generate the type list.
+ generate_file(config, 'InheritTypes', 'InheritTypes.rs')
+
+ # Generate the module declarations.
+ generate_file(config, 'Bindings', 'Bindings/mod.rs')
+
+ generate_file(config, 'UnionTypes', 'UnionTypes.rs')
+
+if __name__ == '__main__':
+ main()
diff --git a/components/script/dom/bindings/codegen/Makefile.in b/components/script/dom/bindings/codegen/Makefile.in
new file mode 100644
index 00000000000..5fef1e77218
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Makefile.in
@@ -0,0 +1,165 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+FAIL_ON_WARNINGS := 1
+
+MODULE = dom
+LIBRARY_NAME = dombindings_s
+LIBXUL_LIBRARY = 1
+FORCE_STATIC_LIB = 1
+EXPORT_LIBRARY = 1
+
+include $(topsrcdir)/config/config.mk
+
+# Need this to find all our DOM source files.
+include $(topsrcdir)/dom/dom-config.mk
+
+include $(topsrcdir)/dom/webidl/WebIDL.mk
+
+binding_include_path := mozilla/dom
+all_webidl_files = $(webidl_files) $(generated_webidl_files)
+# Set exported_binding_headers before adding the test IDL to the mix
+exported_binding_headers := $(subst .webidl,Binding.h,$(all_webidl_files))
+# Set linked_binding_cpp_files before adding the test IDL to the mix
+linked_binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files))
+
+all_webidl_files += $(test_webidl_files)
+
+binding_header_files := $(subst .webidl,Binding.h,$(all_webidl_files))
+binding_cpp_files := $(subst .webidl,Binding.cpp,$(all_webidl_files))
+
+globalgen_targets := \
+ PrototypeList.h \
+ RegisterBindings.h \
+ RegisterBindings.cpp \
+ UnionTypes.h \
+ UnionConversions.h \
+ $(NULL)
+
+CPPSRCS = \
+ $(linked_binding_cpp_files) \
+ $(filter %.cpp, $(globalgen_targets)) \
+ BindingUtils.cpp \
+ DOMJSProxyHandler.cpp \
+ $(NULL)
+
+EXPORTS_NAMESPACES = $(binding_include_path) mozilla
+
+EXPORTS_mozilla = \
+ ErrorResult.h \
+ $(NULL)
+
+EXPORTS_$(binding_include_path) = \
+ BindingUtils.h \
+ DOMJSClass.h \
+ DOMJSProxyHandler.h \
+ Errors.msg \
+ Nullable.h \
+ PrimitiveConversions.h \
+ PrototypeList.h \
+ RegisterBindings.h \
+ TypedArray.h \
+ UnionConversions.h \
+ UnionTypes.h \
+ $(exported_binding_headers) \
+ $(NULL)
+
+LOCAL_INCLUDES += -I$(topsrcdir)/js/xpconnect/src \
+ -I$(topsrcdir)/js/xpconnect/wrappers \
+ -I$(topsrcdir)/content/canvas/src \
+ -I$(topsrcdir)/content/html/content/src
+
+include $(topsrcdir)/config/rules.mk
+
+# If you change bindinggen_dependencies here, change it in
+# dom/bindings/test/Makefile.in too.
+bindinggen_dependencies := \
+ BindingGen.py \
+ Bindings.conf \
+ Configuration.py \
+ Codegen.py \
+ parser/WebIDL.py \
+ ParserResults.pkl \
+ $(GLOBAL_DEPS) \
+ $(NULL)
+
+CSS2Properties.webidl: $(topsrcdir)/layout/style/nsCSSPropList.h \
+ $(topsrcdir)/layout/style/nsCSSPropAliasList.h \
+ $(webidl_base)/CSS2Properties.webidl.in \
+ $(webidl_base)/CSS2PropertiesProps.h \
+ $(srcdir)/GenerateCSS2PropertiesWebIDL.py \
+ $(GLOBAL_DEPS)
+ $(CPP) $(DEFINES) $(ACDEFINES) -I$(topsrcdir)/layout/style $(webidl_base)/CSS2PropertiesProps.h | \
+ $(PYTHON) \
+ $(srcdir)/GenerateCSS2PropertiesWebIDL.py $(webidl_base)/CSS2Properties.webidl.in > CSS2Properties.webidl
+
+$(webidl_files): %: $(webidl_base)/%
+ $(INSTALL) $(IFLAGS1) $(webidl_base)/$* .
+
+$(test_webidl_files): %: $(srcdir)/test/%
+ $(INSTALL) $(IFLAGS1) $(srcdir)/test/$* .
+
+$(binding_header_files): %Binding.h: $(bindinggen_dependencies) \
+ %.webidl \
+ $(NULL)
+ $(PYTHON) $(topsrcdir)/config/pythonpath.py \
+ $(PLY_INCLUDE) -I$(srcdir)/parser \
+ $(srcdir)/BindingGen.py header \
+ $(srcdir)/Bindings.conf $*Binding \
+ $*.webidl
+
+$(binding_cpp_files): %Binding.cpp: $(bindinggen_dependencies) \
+ %.webidl \
+ $(NULL)
+ $(PYTHON) $(topsrcdir)/config/pythonpath.py \
+ $(PLY_INCLUDE) -I$(srcdir)/parser \
+ $(srcdir)/BindingGen.py cpp \
+ $(srcdir)/Bindings.conf $*Binding \
+ $*.webidl
+
+$(globalgen_targets): ParserResults.pkl
+
+CACHE_DIR = _cache
+
+globalgen_dependencies := \
+ GlobalGen.py \
+ Bindings.conf \
+ Configuration.py \
+ Codegen.py \
+ parser/WebIDL.py \
+ $(CACHE_DIR)/.done \
+ $(GLOBAL_DEPS) \
+ $(NULL)
+
+$(CACHE_DIR)/.done:
+ $(MKDIR) -p $(CACHE_DIR)
+ @$(TOUCH) $@
+
+ParserResults.pkl: $(globalgen_dependencies) \
+ $(all_webidl_files)
+ $(PYTHON) $(topsrcdir)/config/pythonpath.py \
+ $(PLY_INCLUDE) -I$(srcdir)/parser \
+ $(srcdir)/GlobalGen.py $(srcdir)/Bindings.conf . \
+ --cachedir=$(CACHE_DIR) \
+ $(all_webidl_files)
+
+GARBAGE += \
+ $(binding_header_files) \
+ $(binding_cpp_files) \
+ $(all_webidl_files) \
+ $(globalgen_targets) \
+ ParserResults.pkl \
+ webidlyacc.py \
+ parser.out \
+ $(NULL)
+
+# Make sure all binding header files are created during the export stage, so we
+# don't have issues with .cpp files being compiled before we've generated the
+# headers they depend on. This is really only needed for the test files, since
+# the non-test headers are all exported above anyway.
+export:: $(binding_header_files)
diff --git a/components/script/dom/bindings/codegen/Nullable.h b/components/script/dom/bindings/codegen/Nullable.h
new file mode 100644
index 00000000000..8b2cc08642b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/Nullable.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Nullable_h
+#define mozilla_dom_Nullable_h
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+namespace dom {
+
+// Support for nullable types
+template <typename T>
+struct Nullable
+{
+private:
+ T mValue;
+ bool mIsNull;
+
+public:
+ Nullable()
+ : mIsNull(true)
+ {}
+
+ Nullable(T aValue)
+ : mValue(aValue)
+ , mIsNull(false)
+ {}
+
+ void SetValue(T aValue) {
+ mValue = aValue;
+ mIsNull = false;
+ }
+
+ // For cases when |T| is some type with nontrivial copy behavior, we may want
+ // to get a reference to our internal copy of T and work with it directly
+ // instead of relying on the copying version of SetValue().
+ T& SetValue() {
+ mIsNull = false;
+ return mValue;
+ }
+
+ void SetNull() {
+ mIsNull = true;
+ }
+
+ const T& Value() const {
+ MOZ_ASSERT(!mIsNull);
+ return mValue;
+ }
+
+ T& Value() {
+ MOZ_ASSERT(!mIsNull);
+ return mValue;
+ }
+
+ bool IsNull() const {
+ return mIsNull;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_Nullable_h */
diff --git a/components/script/dom/bindings/codegen/PrimitiveConversions.h b/components/script/dom/bindings/codegen/PrimitiveConversions.h
new file mode 100644
index 00000000000..40c27425772
--- /dev/null
+++ b/components/script/dom/bindings/codegen/PrimitiveConversions.h
@@ -0,0 +1,350 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Conversions from jsval to primitive values
+ */
+
+#ifndef mozilla_dom_PrimitiveConversions_h
+#define mozilla_dom_PrimitiveConversions_h
+
+#include <limits>
+#include <math.h>
+#include "mozilla/Assertions.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/FloatingPoint.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+namespace dom {
+
+template<typename T>
+struct TypeName {
+};
+
+template<>
+struct TypeName<int8_t> {
+ static const char* value() {
+ return "byte";
+ }
+};
+template<>
+struct TypeName<uint8_t> {
+ static const char* value() {
+ return "octet";
+ }
+};
+template<>
+struct TypeName<int16_t> {
+ static const char* value() {
+ return "short";
+ }
+};
+template<>
+struct TypeName<uint16_t> {
+ static const char* value() {
+ return "unsigned short";
+ }
+};
+template<>
+struct TypeName<int32_t> {
+ static const char* value() {
+ return "long";
+ }
+};
+template<>
+struct TypeName<uint32_t> {
+ static const char* value() {
+ return "unsigned long";
+ }
+};
+template<>
+struct TypeName<int64_t> {
+ static const char* value() {
+ return "long long";
+ }
+};
+template<>
+struct TypeName<uint64_t> {
+ static const char* value() {
+ return "unsigned long long";
+ }
+};
+
+
+enum ConversionBehavior {
+ eDefault,
+ eEnforceRange,
+ eClamp
+};
+
+template<typename T, ConversionBehavior B>
+struct PrimitiveConversionTraits {
+};
+
+template<typename T>
+struct DisallowedConversion {
+ typedef int jstype;
+ typedef int intermediateType;
+
+private:
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ MOZ_NOT_REACHED("This should never be instantiated!");
+ return false;
+ }
+};
+
+struct PrimitiveConversionTraits_smallInt {
+ // The output of JS::ToInt32 is determined as follows:
+ // 1) The value is converted to a double
+ // 2) Anything that's not a finite double returns 0
+ // 3) The double is rounded towards zero to the nearest integer
+ // 4) The resulting integer is reduced mod 2^32. The output of this
+ // operation is an integer in the range [0, 2^32).
+ // 5) If the resulting number is >= 2^31, 2^32 is subtracted from it.
+ //
+ // The result of all this is a number in the range [-2^31, 2^31)
+ //
+ // WebIDL conversions for the 8-bit, 16-bit, and 32-bit integer types
+ // are defined in the same way, except that step 4 uses reduction mod
+ // 2^8 and 2^16 for the 8-bit and 16-bit types respectively, and step 5
+ // is only done for the signed types.
+ //
+ // C/C++ define integer conversion semantics to unsigned types as taking
+ // your input integer mod (1 + largest value representable in the
+ // unsigned type). Since 2^32 is zero mod 2^8, 2^16, and 2^32,
+ // converting to the unsigned int of the relevant width will correctly
+ // perform step 4; in particular, the 2^32 possibly subtracted in step 5
+ // will become 0.
+ //
+ // Once we have step 4 done, we're just going to assume 2s-complement
+ // representation and cast directly to the type we really want.
+ //
+ // So we can cast directly for all unsigned types and for int32_t; for
+ // the smaller-width signed types we need to cast through the
+ // corresponding unsigned type.
+ typedef int32_t jstype;
+ typedef int32_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ return JS::ToInt32(cx, v, retval);
+ }
+};
+template<>
+struct PrimitiveConversionTraits<int8_t, eDefault> : PrimitiveConversionTraits_smallInt {
+ typedef uint8_t intermediateType;
+};
+template<>
+struct PrimitiveConversionTraits<uint8_t, eDefault> : PrimitiveConversionTraits_smallInt {
+};
+template<>
+struct PrimitiveConversionTraits<int16_t, eDefault> : PrimitiveConversionTraits_smallInt {
+ typedef uint16_t intermediateType;
+};
+template<>
+struct PrimitiveConversionTraits<uint16_t, eDefault> : PrimitiveConversionTraits_smallInt {
+};
+template<>
+struct PrimitiveConversionTraits<int32_t, eDefault> : PrimitiveConversionTraits_smallInt {
+};
+template<>
+struct PrimitiveConversionTraits<uint32_t, eDefault> : PrimitiveConversionTraits_smallInt {
+};
+
+template<>
+struct PrimitiveConversionTraits<int64_t, eDefault> {
+ typedef int64_t jstype;
+ typedef int64_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ return JS::ToInt64(cx, v, retval);
+ }
+};
+
+template<>
+struct PrimitiveConversionTraits<uint64_t, eDefault> {
+ typedef uint64_t jstype;
+ typedef uint64_t intermediateType;
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ return JS::ToUint64(cx, v, retval);
+ }
+};
+
+template<typename T>
+struct PrimitiveConversionTraits_Limits {
+ static inline T min() {
+ return std::numeric_limits<T>::min();
+ }
+ static inline T max() {
+ return std::numeric_limits<T>::max();
+ }
+};
+
+template<>
+struct PrimitiveConversionTraits_Limits<int64_t> {
+ static inline int64_t min() {
+ return -(1LL << 53) + 1;
+ }
+ static inline int64_t max() {
+ return (1LL << 53) - 1;
+ }
+};
+
+template<>
+struct PrimitiveConversionTraits_Limits<uint64_t> {
+ static inline uint64_t min() {
+ return 0;
+ }
+ static inline uint64_t max() {
+ return (1LL << 53) - 1;
+ }
+};
+
+template<typename T, bool (*Enforce)(JSContext* cx, const double& d, T* retval)>
+struct PrimitiveConversionTraits_ToCheckedIntHelper {
+ typedef T jstype;
+ typedef T intermediateType;
+
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ double intermediate;
+ if (!JS::ToNumber(cx, v, &intermediate)) {
+ return false;
+ }
+
+ return Enforce(cx, intermediate, retval);
+ }
+};
+
+template<typename T>
+inline bool
+PrimitiveConversionTraits_EnforceRange(JSContext* cx, const double& d, T* retval)
+{
+ MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer,
+ "This can only be applied to integers!");
+
+ if (!MOZ_DOUBLE_IS_FINITE(d)) {
+ return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_NON_FINITE, TypeName<T>::value());
+ }
+
+ bool neg = (d < 0);
+ double rounded = floor(neg ? -d : d);
+ rounded = neg ? -rounded : rounded;
+ if (rounded < PrimitiveConversionTraits_Limits<T>::min() ||
+ rounded > PrimitiveConversionTraits_Limits<T>::max()) {
+ return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_OUT_OF_RANGE, TypeName<T>::value());
+ }
+
+ *retval = static_cast<T>(rounded);
+ return true;
+}
+
+template<typename T>
+struct PrimitiveConversionTraits<T, eEnforceRange> :
+ public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_EnforceRange<T> > {
+};
+
+template<typename T>
+inline bool
+PrimitiveConversionTraits_Clamp(JSContext* cx, const double& d, T* retval)
+{
+ MOZ_STATIC_ASSERT(std::numeric_limits<T>::is_integer,
+ "This can only be applied to integers!");
+
+ if (MOZ_DOUBLE_IS_NaN(d)) {
+ *retval = 0;
+ return true;
+ }
+ if (d >= PrimitiveConversionTraits_Limits<T>::max()) {
+ *retval = PrimitiveConversionTraits_Limits<T>::max();
+ return true;
+ }
+ if (d <= PrimitiveConversionTraits_Limits<T>::min()) {
+ *retval = PrimitiveConversionTraits_Limits<T>::min();
+ return true;
+ }
+
+ MOZ_ASSERT(MOZ_DOUBLE_IS_FINITE(d));
+
+ // Banker's rounding (round ties towards even).
+ // We move away from 0 by 0.5f and then truncate. That gets us the right
+ // answer for any starting value except plus or minus N.5. With a starting
+ // value of that form, we now have plus or minus N+1. If N is odd, this is
+ // the correct result. If N is even, plus or minus N is the correct result.
+ double toTruncate = (d < 0) ? d - 0.5 : d + 0.5;
+
+ T truncated(toTruncate);
+
+ if (truncated == toTruncate) {
+ /*
+ * It was a tie (since moving away from 0 by 0.5 gave us the exact integer
+ * we want). Since we rounded away from 0, we either already have an even
+ * number or we have an odd number but the number we want is one closer to
+ * 0. So just unconditionally masking out the ones bit should do the trick
+ * to get us the value we want.
+ */
+ truncated &= ~1;
+ }
+
+ *retval = truncated;
+ return true;
+}
+
+template<typename T>
+struct PrimitiveConversionTraits<T, eClamp> :
+ public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_Clamp<T> > {
+};
+
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<bool, B> : public DisallowedConversion<bool> {};
+
+template<>
+struct PrimitiveConversionTraits<bool, eDefault> {
+ typedef JSBool jstype;
+ typedef bool intermediateType;
+ static inline bool converter(JSContext* /* unused */, JS::Value v, jstype* retval) {
+ *retval = JS::ToBoolean(v);
+ return true;
+ }
+};
+
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<float, B> : public DisallowedConversion<float> {};
+
+template<ConversionBehavior B>
+struct PrimitiveConversionTraits<double, B> : public DisallowedConversion<double> {};
+
+struct PrimitiveConversionTraits_float {
+ typedef double jstype;
+ typedef double intermediateType;
+ static inline bool converter(JSContext* cx, JS::Value v, jstype* retval) {
+ return JS::ToNumber(cx, v, retval);
+ }
+};
+
+template<>
+struct PrimitiveConversionTraits<float, eDefault> : PrimitiveConversionTraits_float {
+};
+template<>
+struct PrimitiveConversionTraits<double, eDefault> : PrimitiveConversionTraits_float {
+};
+
+
+template<typename T, ConversionBehavior B>
+bool ValueToPrimitive(JSContext* cx, JS::Value v, T* retval)
+{
+ typename PrimitiveConversionTraits<T, B>::jstype t;
+ if (!PrimitiveConversionTraits<T, B>::converter(cx, v, &t))
+ return false;
+
+ *retval =
+ static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(t);
+ return true;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PrimitiveConversions_h */
diff --git a/components/script/dom/bindings/codegen/RegisterBindings.h b/components/script/dom/bindings/codegen/RegisterBindings.h
new file mode 100644
index 00000000000..7d83a747cc3
--- /dev/null
+++ b/components/script/dom/bindings/codegen/RegisterBindings.h
@@ -0,0 +1,14 @@
+#ifndef mozilla_dom_RegisterBindings_h__
+#define mozilla_dom_RegisterBindings_h__
+
+
+namespace mozilla {
+namespace dom {
+void
+Register(nsScriptNameSpaceManager* aNameSpaceManager);
+
+} // namespace dom
+} // namespace mozilla
+
+
+#endif // mozilla_dom_RegisterBindings_h__
diff --git a/components/script/dom/bindings/codegen/TypedArray.h b/components/script/dom/bindings/codegen/TypedArray.h
new file mode 100644
index 00000000000..2a6f17bcb96
--- /dev/null
+++ b/components/script/dom/bindings/codegen/TypedArray.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_TypedArray_h
+#define mozilla_dom_TypedArray_h
+
+#include "jsfriendapi.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Various typed array classes for argument conversion. We have a base class
+ * that has a way of initializing a TypedArray from an existing typed array, and
+ * a subclass of the base class that supports creation of a relevant typed array
+ * or array buffer object.
+ */
+template<typename T,
+ JSObject* UnboxArray(JSContext*, JSObject*, uint32_t*, T**)>
+struct TypedArray_base {
+ TypedArray_base(JSContext* cx, JSObject* obj)
+ {
+ mObj = UnboxArray(cx, obj, &mLength, &mData);
+ }
+
+private:
+ T* mData;
+ uint32_t mLength;
+ JSObject* mObj;
+
+public:
+ inline bool inited() const {
+ return !!mObj;
+ }
+
+ inline T *Data() const {
+ MOZ_ASSERT(inited());
+ return mData;
+ }
+
+ inline uint32_t Length() const {
+ MOZ_ASSERT(inited());
+ return mLength;
+ }
+
+ inline JSObject *Obj() const {
+ MOZ_ASSERT(inited());
+ return mObj;
+ }
+};
+
+
+template<typename T,
+ T* GetData(JSObject*, JSContext*),
+ JSObject* UnboxArray(JSContext*, JSObject*, uint32_t*, T**),
+ JSObject* CreateNew(JSContext*, uint32_t)>
+struct TypedArray : public TypedArray_base<T,UnboxArray> {
+ TypedArray(JSContext* cx, JSObject* obj) :
+ TypedArray_base<T,UnboxArray>(cx, obj)
+ {}
+
+ static inline JSObject*
+ Create(JSContext* cx, nsWrapperCache* creator, uint32_t length,
+ const T* data = NULL) {
+ JSObject* creatorWrapper;
+ Maybe<JSAutoCompartment> ac;
+ if (creator && (creatorWrapper = creator->GetWrapperPreserveColor())) {
+ ac.construct(cx, creatorWrapper);
+ }
+ JSObject* obj = CreateNew(cx, length);
+ if (!obj) {
+ return NULL;
+ }
+ if (data) {
+ T* buf = static_cast<T*>(GetData(obj, cx));
+ memcpy(buf, data, length*sizeof(T));
+ }
+ return obj;
+ }
+};
+
+typedef TypedArray<int8_t, JS_GetInt8ArrayData, JS_GetObjectAsInt8Array,
+ JS_NewInt8Array>
+ Int8Array;
+typedef TypedArray<uint8_t, JS_GetUint8ArrayData,
+ JS_GetObjectAsUint8Array, JS_NewUint8Array>
+ Uint8Array;
+typedef TypedArray<uint8_t, JS_GetUint8ClampedArrayData,
+ JS_GetObjectAsUint8ClampedArray, JS_NewUint8ClampedArray>
+ Uint8ClampedArray;
+typedef TypedArray<int16_t, JS_GetInt16ArrayData,
+ JS_GetObjectAsInt16Array, JS_NewInt16Array>
+ Int16Array;
+typedef TypedArray<uint16_t, JS_GetUint16ArrayData,
+ JS_GetObjectAsUint16Array, JS_NewUint16Array>
+ Uint16Array;
+typedef TypedArray<int32_t, JS_GetInt32ArrayData,
+ JS_GetObjectAsInt32Array, JS_NewInt32Array>
+ Int32Array;
+typedef TypedArray<uint32_t, JS_GetUint32ArrayData,
+ JS_GetObjectAsUint32Array, JS_NewUint32Array>
+ Uint32Array;
+typedef TypedArray<float, JS_GetFloat32ArrayData,
+ JS_GetObjectAsFloat32Array, JS_NewFloat32Array>
+ Float32Array;
+typedef TypedArray<double, JS_GetFloat64ArrayData,
+ JS_GetObjectAsFloat64Array, JS_NewFloat64Array>
+ Float64Array;
+typedef TypedArray_base<uint8_t, JS_GetObjectAsArrayBufferView>
+ ArrayBufferView;
+typedef TypedArray<uint8_t, JS_GetArrayBufferData,
+ JS_GetObjectAsArrayBuffer, JS_NewArrayBuffer>
+ ArrayBuffer;
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_TypedArray_h */
diff --git a/components/script/dom/bindings/codegen/crashtests/769464.html b/components/script/dom/bindings/codegen/crashtests/769464.html
new file mode 100644
index 00000000000..84d6dbc08b4
--- /dev/null
+++ b/components/script/dom/bindings/codegen/crashtests/769464.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+
+function boom()
+{
+ window.getComputedStyle(new Worker("404.js"));
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
diff --git a/components/script/dom/bindings/codegen/crashtests/crashtests.list b/components/script/dom/bindings/codegen/crashtests/crashtests.list
new file mode 100644
index 00000000000..cb954bd91fc
--- /dev/null
+++ b/components/script/dom/bindings/codegen/crashtests/crashtests.list
@@ -0,0 +1 @@
+asserts-if(cocoaWidget,0-1) load 769464.html
diff --git a/components/script/dom/bindings/codegen/parser/README b/components/script/dom/bindings/codegen/parser/README
new file mode 100644
index 00000000000..94b64b88459
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/README
@@ -0,0 +1 @@
+A WebIDL parser written in Python to be used in Mozilla. \ No newline at end of file
diff --git a/components/script/dom/bindings/codegen/parser/UPSTREAM b/components/script/dom/bindings/codegen/parser/UPSTREAM
new file mode 100644
index 00000000000..7ac5899379e
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/UPSTREAM
@@ -0,0 +1 @@
+http://dev.w3.org/cvsweb/~checkout~/2006/webapi/WebIDL/Overview.html?rev=1.409;content-type=text%2Fhtml%3b+charset=utf-8 \ No newline at end of file
diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py
new file mode 100644
index 00000000000..32f80e82c56
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/WebIDL.py
@@ -0,0 +1,5583 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+""" A WebIDL parser. """
+
+from ply import lex, yacc
+import re
+import os
+import traceback
+import math
+from collections import defaultdict
+
+# Machinery
+
+def parseInt(literal):
+ string = literal
+ sign = 0
+ base = 0
+
+ if string[0] == '-':
+ sign = -1
+ string = string[1:]
+ else:
+ sign = 1
+
+ if string[0] == '0' and len(string) > 1:
+ if string[1] == 'x' or string[1] == 'X':
+ base = 16
+ string = string[2:]
+ else:
+ base = 8
+ string = string[1:]
+ else:
+ base = 10
+
+ value = int(string, base)
+ return value * sign
+
+# Magic for creating enums
+def M_add_class_attribs(attribs, start):
+ def foo(name, bases, dict_):
+ for v, k in enumerate(attribs):
+ dict_[k] = start + v
+ assert 'length' not in dict_
+ dict_['length'] = start + len(attribs)
+ return type(name, bases, dict_)
+ return foo
+
+def enum(*names, **kw):
+ if len(kw) == 1:
+ base = kw['base'].__class__
+ start = base.length
+ else:
+ assert len(kw) == 0
+ base = object
+ start = 0
+ class Foo(base):
+ __metaclass__ = M_add_class_attribs(names, start)
+ def __setattr__(self, name, value): # this makes it read-only
+ raise NotImplementedError
+ return Foo()
+
+class WebIDLError(Exception):
+ def __init__(self, message, locations, warning=False):
+ self.message = message
+ self.locations = [str(loc) for loc in locations]
+ self.warning = warning
+
+ def __str__(self):
+ return "%s: %s%s%s" % (self.warning and 'warning' or 'error',
+ self.message,
+ ", " if len(self.locations) != 0 else "",
+ "\n".join(self.locations))
+
+class Location(object):
+ def __init__(self, lexer, lineno, lexpos, filename):
+ self._line = None
+ self._lineno = lineno
+ self._lexpos = lexpos
+ self._lexdata = lexer.lexdata
+ self._file = filename if filename else "<unknown>"
+
+ def __eq__(self, other):
+ return self._lexpos == other._lexpos and \
+ self._file == other._file
+
+ def filename(self):
+ return self._file
+
+ def resolve(self):
+ if self._line:
+ return
+
+ startofline = self._lexdata.rfind('\n', 0, self._lexpos) + 1
+ endofline = self._lexdata.find('\n', self._lexpos, self._lexpos + 80)
+ if endofline != -1:
+ self._line = self._lexdata[startofline:endofline]
+ else:
+ self._line = self._lexdata[startofline:]
+ self._colno = self._lexpos - startofline
+
+ # Our line number seems to point to the start of self._lexdata
+ self._lineno += self._lexdata.count('\n', 0, startofline)
+
+ def get(self):
+ self.resolve()
+ return "%s line %s:%s" % (self._file, self._lineno, self._colno)
+
+ def _pointerline(self):
+ return " " * self._colno + "^"
+
+ def __str__(self):
+ self.resolve()
+ return "%s line %s:%s\n%s\n%s" % (self._file, self._lineno, self._colno,
+ self._line, self._pointerline())
+
+class BuiltinLocation(object):
+ def __init__(self, text):
+ self.msg = text + "\n"
+
+ def __eq__(self, other):
+ return isinstance(other, BuiltinLocation) and \
+ self.msg == other.msg
+
+ def filename(self):
+ return '<builtin>'
+
+ def resolve(self):
+ pass
+
+ def get(self):
+ return self.msg
+
+ def __str__(self):
+ return self.get()
+
+
+# Data Model
+
+class IDLObject(object):
+ def __init__(self, location):
+ self.location = location
+ self.userData = dict()
+
+ def filename(self):
+ return self.location.filename()
+
+ def isInterface(self):
+ return False
+
+ def isEnum(self):
+ return False
+
+ def isCallback(self):
+ return False
+
+ def isType(self):
+ return False
+
+ def isDictionary(self):
+ return False;
+
+ def isUnion(self):
+ return False
+
+ def getUserData(self, key, default):
+ return self.userData.get(key, default)
+
+ def setUserData(self, key, value):
+ self.userData[key] = value
+
+ def addExtendedAttributes(self, attrs):
+ assert False # Override me!
+
+ def handleExtendedAttribute(self, attr):
+ assert False # Override me!
+
+ def _getDependentObjects(self):
+ assert False # Override me!
+
+ def getDeps(self, visited=None):
+ """ Return a set of files that this object depends on. If any of
+ these files are changed the parser needs to be rerun to regenerate
+ a new IDLObject.
+
+ The visited argument is a set of all the objects already visited.
+ We must test to see if we are in it, and if so, do nothing. This
+ prevents infinite recursion."""
+
+ # NB: We can't use visited=set() above because the default value is
+ # evaluated when the def statement is evaluated, not when the function
+ # is executed, so there would be one set for all invocations.
+ if visited == None:
+ visited = set()
+
+ if self in visited:
+ return set()
+
+ visited.add(self)
+
+ deps = set()
+ if self.filename() != "<builtin>":
+ deps.add(self.filename())
+
+ for d in self._getDependentObjects():
+ deps = deps.union(d.getDeps(visited))
+
+ return deps
+
+class IDLScope(IDLObject):
+ def __init__(self, location, parentScope, identifier):
+ IDLObject.__init__(self, location)
+
+ self.parentScope = parentScope
+ if identifier:
+ assert isinstance(identifier, IDLIdentifier)
+ self._name = identifier
+ else:
+ self._name = None
+
+ self._dict = {}
+ self.globalNames = set()
+ # A mapping from global name to the set of global interfaces
+ # that have that global name.
+ self.globalNameMapping = defaultdict(set)
+ self.primaryGlobalAttr = None
+ self.primaryGlobalName = None
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ if self._name:
+ return self._name.QName() + "::"
+ return "::"
+
+ def ensureUnique(self, identifier, object):
+ """
+ Ensure that there is at most one 'identifier' in scope ('self').
+ Note that object can be None. This occurs if we end up here for an
+ interface type we haven't seen yet.
+ """
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+ assert not object or isinstance(object, IDLObjectWithIdentifier)
+ assert not object or object.identifier == identifier
+
+ if identifier.name in self._dict:
+ if not object:
+ return
+
+ # ensureUnique twice with the same object is not allowed
+ assert id(object) != id(self._dict[identifier.name])
+
+ replacement = self.resolveIdentifierConflict(self, identifier,
+ self._dict[identifier.name],
+ object)
+ self._dict[identifier.name] = replacement
+ return
+
+ assert object
+
+ self._dict[identifier.name] = object
+
+ def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
+ if isinstance(originalObject, IDLExternalInterface) and \
+ isinstance(newObject, IDLExternalInterface) and \
+ originalObject.identifier.name == newObject.identifier.name:
+ return originalObject
+
+ if (isinstance(originalObject, IDLExternalInterface) or
+ isinstance(newObject, IDLExternalInterface)):
+ raise WebIDLError(
+ "Name collision between "
+ "interface declarations for identifier '%s' at '%s' and '%s'"
+ % (identifier.name,
+ originalObject.location, newObject.location), [])
+
+ if (isinstance(originalObject, IDLDictionary) or
+ isinstance(newObject, IDLDictionary)):
+ raise WebIDLError(
+ "Name collision between dictionary declarations for "
+ "identifier '%s'.\n%s\n%s"
+ % (identifier.name,
+ originalObject.location, newObject.location), [])
+
+ # We do the merging of overloads here as opposed to in IDLInterface
+ # because we need to merge overloads of NamedConstructors and we need to
+ # detect conflicts in those across interfaces. See also the comment in
+ # IDLInterface.addExtendedAttributes for "NamedConstructor".
+ if originalObject.tag == IDLInterfaceMember.Tags.Method and \
+ newObject.tag == IDLInterfaceMember.Tags.Method:
+ return originalObject.addOverload(newObject)
+
+ # Default to throwing, derived classes can override.
+ conflictdesc = "\n\t%s at %s\n\t%s at %s" % \
+ (originalObject, originalObject.location, newObject, newObject.location)
+
+ raise WebIDLError(
+ "Multiple unresolvable definitions of identifier '%s' in scope '%s%s"
+ % (identifier.name, str(self), conflictdesc), [])
+
+ def _lookupIdentifier(self, identifier):
+ return self._dict[identifier.name]
+
+ def lookupIdentifier(self, identifier):
+ assert isinstance(identifier, IDLIdentifier)
+ assert identifier.scope == self
+ return self._lookupIdentifier(identifier)
+
+class IDLIdentifier(IDLObject):
+ def __init__(self, location, scope, name):
+ IDLObject.__init__(self, location)
+
+ self.name = name
+ assert isinstance(scope, IDLScope)
+ self.scope = scope
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ return self.scope.QName() + self.name
+
+ def __hash__(self):
+ return self.QName().__hash__()
+
+ def __eq__(self, other):
+ return self.QName() == other.QName()
+
+ def object(self):
+ return self.scope.lookupIdentifier(self)
+
+class IDLUnresolvedIdentifier(IDLObject):
+ def __init__(self, location, name, allowDoubleUnderscore = False,
+ allowForbidden = False):
+ IDLObject.__init__(self, location)
+
+ assert len(name) > 0
+
+ if name[:2] == "__" and name != "__content" and name != "___noSuchMethod__" and not allowDoubleUnderscore:
+ raise WebIDLError("Identifiers beginning with __ are reserved",
+ [location])
+ if name[0] == '_' and not allowDoubleUnderscore:
+ name = name[1:]
+ # TODO: Bug 872377, Restore "toJSON" to below list.
+ # We sometimes need custom serialization, so allow toJSON for now.
+ if (name in ["constructor", "toString"] and
+ not allowForbidden):
+ raise WebIDLError("Cannot use reserved identifier '%s'" % (name),
+ [location])
+
+ self.name = name
+
+ def __str__(self):
+ return self.QName()
+
+ def QName(self):
+ return "<unresolved scope>::" + self.name
+
+ def resolve(self, scope, object):
+ assert isinstance(scope, IDLScope)
+ assert not object or isinstance(object, IDLObjectWithIdentifier)
+ assert not object or object.identifier == self
+
+ scope.ensureUnique(self, object)
+
+ identifier = IDLIdentifier(self.location, scope, self.name)
+ if object:
+ object.identifier = identifier
+ return identifier
+
+ def finish(self):
+ assert False # Should replace with a resolved identifier first.
+
+class IDLObjectWithIdentifier(IDLObject):
+ def __init__(self, location, parentScope, identifier):
+ IDLObject.__init__(self, location)
+
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+
+ self.identifier = identifier
+
+ if parentScope:
+ self.resolve(parentScope)
+
+ self.treatNullAs = "Default"
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(self.identifier, IDLUnresolvedIdentifier)
+ self.identifier.resolve(parentScope, self)
+
+ def checkForStringHandlingExtendedAttributes(self, attrs,
+ isDictionaryMember=False,
+ isOptional=False):
+ """
+ A helper function to deal with TreatNullAs. Returns the list
+ of attrs it didn't handle itself.
+ """
+ assert isinstance(self, IDLArgument) or isinstance(self, IDLAttribute)
+ unhandledAttrs = list()
+ for attr in attrs:
+ if not attr.hasValue():
+ unhandledAttrs.append(attr)
+ continue
+
+ identifier = attr.identifier()
+ value = attr.value()
+ if identifier == "TreatNullAs":
+ if not self.type.isDOMString() or self.type.nullable():
+ raise WebIDLError("[TreatNullAs] is only allowed on "
+ "arguments or attributes whose type is "
+ "DOMString",
+ [self.location])
+ if isDictionaryMember:
+ raise WebIDLError("[TreatNullAs] is not allowed for "
+ "dictionary members", [self.location])
+ if value != 'EmptyString':
+ raise WebIDLError("[TreatNullAs] must take the identifier "
+ "'EmptyString', not '%s'" % value,
+ [self.location])
+ self.treatNullAs = value
+ else:
+ unhandledAttrs.append(attr)
+
+ return unhandledAttrs
+
+class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope):
+ def __init__(self, location, parentScope, identifier):
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
+ IDLScope.__init__(self, location, parentScope, self.identifier)
+
+class IDLIdentifierPlaceholder(IDLObjectWithIdentifier):
+ def __init__(self, location, identifier):
+ assert isinstance(identifier, IDLUnresolvedIdentifier)
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+
+ def finish(self, scope):
+ try:
+ scope._lookupIdentifier(self.identifier)
+ except:
+ raise WebIDLError("Unresolved type '%s'." % self.identifier,
+ [self.location])
+
+ obj = self.identifier.resolve(scope, None)
+ return scope.lookupIdentifier(obj)
+
+class IDLExternalInterface(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, identifier):
+ raise WebIDLError("Servo does not support external interfaces.",
+ [self.location])
+
+class IDLPartialInterface(IDLObject):
+ def __init__(self, location, name, members, nonPartialInterface):
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ IDLObject.__init__(self, location)
+ self.identifier = name
+ self.members = members
+ # propagatedExtendedAttrs are the ones that should get
+ # propagated to our non-partial interface.
+ self.propagatedExtendedAttrs = []
+ self._nonPartialInterface = nonPartialInterface
+ self._finished = False
+ nonPartialInterface.addPartialInterface(self)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ if identifier in ["Constructor", "NamedConstructor"]:
+ self.propagatedExtendedAttrs.append(attr)
+ elif identifier == "Exposed":
+ # This just gets propagated to all our members.
+ for member in self.members:
+ if len(member._exposureGlobalNames) != 0:
+ raise WebIDLError("[Exposed] specified on both a "
+ "partial interface member and on the "
+ "partial interface itself",
+ [member.location, attr.location])
+ member.addExtendedAttributes([attr])
+ else:
+ raise WebIDLError("Unknown extended attribute %s on partial "
+ "interface" % identifier,
+ [attr.location])
+
+ def finish(self, scope):
+ if self._finished:
+ return
+ self._finished = True
+ # Need to make sure our non-partial interface gets finished so it can
+ # report cases when we only have partial interfaces.
+ self._nonPartialInterface.finish(scope)
+
+ def validate(self):
+ pass
+
+
+def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet):
+ assert len(targetSet) == 0
+ if exposedAttr.hasValue():
+ targetSet.add(exposedAttr.value())
+ else:
+ assert exposedAttr.hasArgs()
+ targetSet.update(exposedAttr.args())
+
+def globalNameSetToExposureSet(globalScope, nameSet, exposureSet):
+ for name in nameSet:
+ exposureSet.update(globalScope.globalNameMapping[name])
+
+class IDLInterface(IDLObjectWithScope):
+ def __init__(self, location, parentScope, name, parent, members,
+ isKnownNonPartial):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+ assert isKnownNonPartial or not parent
+ assert isKnownNonPartial or len(members) == 0
+
+ self.parent = None
+ self._callback = False
+ self._finished = False
+ self.members = []
+ self._partialInterfaces = []
+ self._extendedAttrDict = {}
+ # namedConstructors needs deterministic ordering because bindings code
+ # outputs the constructs in the order that namedConstructors enumerates
+ # them.
+ self.namedConstructors = list()
+ self.implementedInterfaces = set()
+ self._consequential = False
+ self._isKnownNonPartial = False
+ # self.interfacesBasedOnSelf is the set of interfaces that inherit from
+ # self or have self as a consequential interface, including self itself.
+ # Used for distinguishability checking.
+ self.interfacesBasedOnSelf = set([self])
+ # self.interfacesImplementingSelf is the set of interfaces that directly
+ # have self as a consequential interface
+ self.interfacesImplementingSelf = set()
+ self._hasChildInterfaces = False
+ self._isOnGlobalProtoChain = False
+ # Tracking of the number of reserved slots we need for our
+ # members and those of ancestor interfaces.
+ self.totalMembersInSlots = 0
+ # Tracking of the number of own own members we have in slots
+ self._ownMembersInSlots = 0
+ # _exposureGlobalNames are the global names listed in our [Exposed]
+ # extended attribute. exposureSet is the exposure set as defined in the
+ # Web IDL spec: it contains interface names.
+ self._exposureGlobalNames = set()
+ self.exposureSet = set()
+
+ IDLObjectWithScope.__init__(self, location, parentScope, name)
+
+ if isKnownNonPartial:
+ self.setNonPartial(location, parent, members)
+
+ def __str__(self):
+ return "Interface '%s'" % self.identifier.name
+
+ def ctor(self):
+ identifier = IDLUnresolvedIdentifier(self.location, "constructor",
+ allowForbidden=True)
+ try:
+ return self._lookupIdentifier(identifier)
+ except:
+ return None
+
+ def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject):
+ assert isinstance(scope, IDLScope)
+ assert isinstance(originalObject, IDLInterfaceMember)
+ assert isinstance(newObject, IDLInterfaceMember)
+
+ retval = IDLScope.resolveIdentifierConflict(self, scope, identifier,
+ originalObject, newObject)
+
+ # Might be a ctor, which isn't in self.members
+ if newObject in self.members:
+ self.members.remove(newObject)
+ return retval
+
+ def finish(self, scope):
+ if self._finished:
+ return
+
+ self._finished = True
+
+ if not self._isKnownNonPartial:
+ raise WebIDLError("Interface %s does not have a non-partial "
+ "declaration" % self.identifier.name,
+ [self.location])
+
+ # Verify that our [Exposed] value, if any, makes sense.
+ for globalName in self._exposureGlobalNames:
+ if globalName not in scope.globalNames:
+ raise WebIDLError("Unknown [Exposed] value %s" % globalName,
+ [self.location])
+
+ if len(self._exposureGlobalNames) == 0:
+ self._exposureGlobalNames.add(scope.primaryGlobalName)
+
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames,
+ self.exposureSet)
+
+ # Now go ahead and merge in our partial interfaces.
+ for partial in self._partialInterfaces:
+ partial.finish(scope)
+ self.addExtendedAttributes(partial.propagatedExtendedAttrs)
+ self.members.extend(partial.members)
+
+ # Now that we've merged in our partial interfaces, set the
+ # _exposureGlobalNames on any members that don't have it set yet. Note
+ # that any partial interfaces that had [Exposed] set have already set up
+ # _exposureGlobalNames on all the members coming from them, so this is
+ # just implementing the "members default to interface that defined them"
+ # and "partial interfaces default to interface they're a partial for"
+ # rules from the spec.
+ for m in self.members:
+ # If m, or the partial interface m came from, had [Exposed]
+ # specified, it already has a nonempty exposure global names set.
+ if len(m._exposureGlobalNames) == 0:
+ m._exposureGlobalNames.update(self._exposureGlobalNames)
+
+ assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder)
+ parent = self.parent.finish(scope) if self.parent else None
+ if parent and isinstance(parent, IDLExternalInterface):
+ raise WebIDLError("%s inherits from %s which does not have "
+ "a definition" %
+ (self.identifier.name,
+ self.parent.identifier.name),
+ [self.location])
+ assert not parent or isinstance(parent, IDLInterface)
+
+ self.parent = parent
+
+ assert iter(self.members)
+
+ if self.parent:
+ self.parent.finish(scope)
+
+ self.parent._hasChildInterfaces = True
+
+ self.totalMembersInSlots = self.parent.totalMembersInSlots
+
+ # Interfaces with [Global] or [PrimaryGlobal] must not
+ # have anything inherit from them
+ if (self.parent.getExtendedAttribute("Global") or
+ self.parent.getExtendedAttribute("PrimaryGlobal")):
+ # Note: This is not a self.parent.isOnGlobalProtoChain() check
+ # because ancestors of a [Global] interface can have other
+ # descendants.
+ raise WebIDLError("[Global] interface has another interface "
+ "inheriting from it",
+ [self.location, self.parent.location])
+
+ # Make sure that we're not exposed in places where our parent is not
+ if not self.exposureSet.issubset(self.parent.exposureSet):
+ raise WebIDLError("Interface %s is exposed in globals where its "
+ "parent interface %s is not exposed." %
+ (self.identifier.name,
+ self.parent.identifier.name),
+ [self.location, self.parent.location])
+
+ # Callbacks must not inherit from non-callbacks or inherit from
+ # anything that has consequential interfaces.
+ # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending.
+ # XXXbz Can callbacks have consequential interfaces? Spec issue pending
+ if self.isCallback():
+ if not self.parent.isCallback():
+ raise WebIDLError("Callback interface %s inheriting from "
+ "non-callback interface %s" %
+ (self.identifier.name,
+ self.parent.identifier.name),
+ [self.location, self.parent.location])
+ elif self.parent.isCallback():
+ raise WebIDLError("Non-callback interface %s inheriting from "
+ "callback interface %s" %
+ (self.identifier.name,
+ self.parent.identifier.name),
+ [self.location, self.parent.location])
+
+ for iface in self.implementedInterfaces:
+ iface.finish(scope)
+
+ cycleInGraph = self.findInterfaceLoopPoint(self)
+ if cycleInGraph:
+ raise WebIDLError("Interface %s has itself as ancestor or "
+ "implemented interface" % self.identifier.name,
+ [self.location, cycleInGraph.location])
+
+ if self.isCallback():
+ # "implements" should have made sure we have no
+ # consequential interfaces.
+ assert len(self.getConsequentialInterfaces()) == 0
+ # And that we're not consequential.
+ assert not self.isConsequential()
+
+ # Now resolve() and finish() our members before importing the
+ # ones from our implemented interfaces.
+
+ # resolve() will modify self.members, so we need to iterate
+ # over a copy of the member list here.
+ for member in list(self.members):
+ member.resolve(self)
+
+ for member in self.members:
+ member.finish(scope)
+
+ # Now that we've finished our members, which has updated their exposure
+ # sets, make sure they aren't exposed in places where we are not.
+ for member in self.members:
+ if not member.exposureSet.issubset(self.exposureSet):
+ raise WebIDLError("Interface member has larger exposure set "
+ "than the interface itself",
+ [member.location, self.location])
+
+ ctor = self.ctor()
+ if ctor is not None:
+ ctor.finish(scope)
+
+ for ctor in self.namedConstructors:
+ ctor.finish(scope)
+
+ # Make a copy of our member list, so things that implement us
+ # can get those without all the stuff we implement ourselves
+ # admixed.
+ self.originalMembers = list(self.members)
+
+ # Import everything from our consequential interfaces into
+ # self.members. Sort our consequential interfaces by name
+ # just so we have a consistent order.
+ for iface in sorted(self.getConsequentialInterfaces(),
+ cmp=cmp,
+ key=lambda x: x.identifier.name):
+ # Flag the interface as being someone's consequential interface
+ iface.setIsConsequentialInterfaceOf(self)
+ # Verify that we're not exposed somewhere where iface is not exposed
+ if not self.exposureSet.issubset(iface.exposureSet):
+ raise WebIDLError("Interface %s is exposed in globals where its "
+ "consequential interface %s is not exposed." %
+ (self.identifier.name, iface.identifier.name),
+ [self.location, iface.location])
+ additionalMembers = iface.originalMembers;
+ for additionalMember in additionalMembers:
+ for member in self.members:
+ if additionalMember.identifier.name == member.identifier.name:
+ raise WebIDLError(
+ "Multiple definitions of %s on %s coming from 'implements' statements" %
+ (member.identifier.name, self),
+ [additionalMember.location, member.location])
+ self.members.extend(additionalMembers)
+ iface.interfacesImplementingSelf.add(self)
+
+ for ancestor in self.getInheritedInterfaces():
+ ancestor.interfacesBasedOnSelf.add(self)
+ for ancestorConsequential in ancestor.getConsequentialInterfaces():
+ ancestorConsequential.interfacesBasedOnSelf.add(self)
+
+ # Deal with interfaces marked [Unforgeable], now that we have our full
+ # member list, except unforgeables pulled in from parents. We want to
+ # do this before we set "originatingInterface" on our unforgeable
+ # members.
+ if self.getExtendedAttribute("Unforgeable"):
+ # Check that the interface already has all the things the
+ # spec would otherwise require us to synthesize and is
+ # missing the ones we plan to synthesize.
+ if not any(m.isMethod() and m.isStringifier() for m in self.members):
+ raise WebIDLError("Unforgeable interface %s does not have a "
+ "stringifier" % self.identifier.name,
+ [self.location])
+
+ for m in self.members:
+ if ((m.isMethod() and m.isJsonifier()) or
+ m.identifier.name == "toJSON"):
+ raise WebIDLError("Unforgeable interface %s has a "
+ "jsonifier so we won't be able to add "
+ "one ourselves" % self.identifier.name,
+ [self.location, m.location])
+
+ if m.identifier.name == "valueOf" and not m.isStatic():
+ raise WebIDLError("Unforgeable interface %s has a valueOf "
+ "member so we won't be able to add one "
+ "ourselves" % self.identifier.name,
+ [self.location, m.location])
+
+ for member in self.members:
+ if ((member.isAttr() or member.isMethod()) and
+ member.isUnforgeable() and
+ not hasattr(member, "originatingInterface")):
+ member.originatingInterface = self
+
+ # Compute slot indices for our members before we pull in
+ # unforgeable members from our parent.
+ for member in self.members:
+ if (member.isAttr() and
+ (member.getExtendedAttribute("StoreInSlot") or
+ member.getExtendedAttribute("Cached"))):
+ member.slotIndex = self.totalMembersInSlots
+ self.totalMembersInSlots += 1
+ if member.getExtendedAttribute("StoreInSlot"):
+ self._ownMembersInSlots += 1
+
+ if self.parent:
+ # Make sure we don't shadow any of the [Unforgeable] attributes on
+ # our ancestor interfaces. We don't have to worry about
+ # consequential interfaces here, because those have already been
+ # imported into the relevant .members lists. And we don't have to
+ # worry about anything other than our parent, because it has already
+ # imported its ancestors unforgeable attributes into its member
+ # list.
+ for unforgeableMember in (member for member in self.parent.members if
+ (member.isAttr() or member.isMethod()) and
+ member.isUnforgeable()):
+ shadows = [ m for m in self.members if
+ (m.isAttr() or m.isMethod()) and
+ not m.isStatic() and
+ m.identifier.name == unforgeableMember.identifier.name ]
+ if len(shadows) != 0:
+ locs = [unforgeableMember.location] + [ s.location for s
+ in shadows ]
+ raise WebIDLError("Interface %s shadows [Unforgeable] "
+ "members of %s" %
+ (self.identifier.name,
+ ancestor.identifier.name),
+ locs)
+ # And now just stick it in our members, since we won't be
+ # inheriting this down the proto chain. If we really cared we
+ # could try to do something where we set up the unforgeable
+ # attributes/methods of ancestor interfaces, with their
+ # corresponding getters, on our interface, but that gets pretty
+ # complicated and seems unnecessary.
+ self.members.append(unforgeableMember)
+
+ # Ensure that there's at most one of each {named,indexed}
+ # {getter,setter,creator,deleter}, at most one stringifier,
+ # and at most one legacycaller. Note that this last is not
+ # quite per spec, but in practice no one overloads
+ # legacycallers.
+ specialMembersSeen = {}
+ for member in self.members:
+ if not member.isMethod():
+ continue
+
+ if member.isGetter():
+ memberType = "getters"
+ elif member.isSetter():
+ memberType = "setters"
+ elif member.isCreator():
+ memberType = "creators"
+ elif member.isDeleter():
+ memberType = "deleters"
+ elif member.isStringifier():
+ memberType = "stringifiers"
+ elif member.isJsonifier():
+ memberType = "jsonifiers"
+ elif member.isLegacycaller():
+ memberType = "legacycallers"
+ else:
+ continue
+
+ if (memberType != "stringifiers" and memberType != "legacycallers" and
+ memberType != "jsonifiers"):
+ if member.isNamed():
+ memberType = "named " + memberType
+ else:
+ assert member.isIndexed()
+ memberType = "indexed " + memberType
+
+ if memberType in specialMembersSeen:
+ raise WebIDLError("Multiple " + memberType + " on %s" % (self),
+ [self.location,
+ specialMembersSeen[memberType].location,
+ member.location])
+
+ specialMembersSeen[memberType] = member
+
+ if self._isOnGlobalProtoChain:
+ # Make sure we have no named setters, creators, or deleters
+ for memberType in ["setter", "creator", "deleter"]:
+ memberId = "named " + memberType + "s"
+ if memberId in specialMembersSeen:
+ raise WebIDLError("Interface with [Global] has a named %s" %
+ memberType,
+ [self.location,
+ specialMembersSeen[memberId].location])
+ # Make sure we're not [OverrideBuiltins]
+ if self.getExtendedAttribute("OverrideBuiltins"):
+ raise WebIDLError("Interface with [Global] also has "
+ "[OverrideBuiltins]",
+ [self.location])
+ # Mark all of our ancestors as being on the global's proto chain too
+ parent = self.parent
+ while parent:
+ # Must not inherit from an interface with [OverrideBuiltins]
+ if parent.getExtendedAttribute("OverrideBuiltins"):
+ raise WebIDLError("Interface with [Global] inherits from "
+ "interface with [OverrideBuiltins]",
+ [self.location, parent.location])
+ parent._isOnGlobalProtoChain = True
+ parent = parent.parent
+
+ def validate(self):
+ # We don't support consequential unforgeable interfaces. Need to check
+ # this here, becaue in finish() an interface might not know yet that
+ # it's consequential.
+ if self.getExtendedAttribute("Unforgeable") and self.isConsequential():
+ raise WebIDLError(
+ "%s is an unforgeable consequential interface" %
+ self.identifier.name,
+ [self.location] +
+ list(i.location for i in
+ (self.interfacesBasedOnSelf - { self }) ))
+
+ # We also don't support inheriting from unforgeable interfaces.
+ if self.getExtendedAttribute("Unforgeable") and self.hasChildInterfaces():
+ raise WebIDLError("%s is an unforgeable ancestor interface" %
+ self.identifier.name,
+ [self.location] +
+ list(i.location for i in
+ self.interfacesBasedOnSelf if i.parent == self))
+
+
+ for member in self.members:
+ member.validate()
+
+ # Check that PutForwards refers to another attribute and that no
+ # cycles exist in forwarded assignments.
+ if member.isAttr():
+ iface = self
+ attr = member
+ putForwards = attr.getExtendedAttribute("PutForwards")
+ if putForwards and self.isCallback():
+ raise WebIDLError("[PutForwards] used on an attribute "
+ "on interface %s which is a callback "
+ "interface" % self.identifier.name,
+ [self.location, member.location])
+
+ while putForwards is not None:
+ forwardIface = attr.type.unroll().inner
+ fowardAttr = None
+
+ for forwardedMember in forwardIface.members:
+ if (not forwardedMember.isAttr() or
+ forwardedMember.identifier.name != putForwards[0]):
+ continue
+ if forwardedMember == member:
+ raise WebIDLError("Cycle detected in forwarded "
+ "assignments for attribute %s on "
+ "%s" %
+ (member.identifier.name, self),
+ [member.location])
+ fowardAttr = forwardedMember
+ break
+
+ if fowardAttr is None:
+ raise WebIDLError("Attribute %s on %s forwards to "
+ "missing attribute %s" %
+ (attr.identifier.name, iface, putForwards),
+ [attr.location])
+
+ iface = forwardIface
+ attr = fowardAttr
+ putForwards = attr.getExtendedAttribute("PutForwards")
+
+ if (self.getExtendedAttribute("Pref") and
+ self._exposureGlobalNames != set([self.parentScope.primaryGlobalName])):
+ raise WebIDLError("[Pref] used on an member that is not %s-only" %
+ self.parentScope.primaryGlobalName,
+ [self.location])
+
+
+ def isInterface(self):
+ return True
+
+ def isExternal(self):
+ return False
+
+ def setIsConsequentialInterfaceOf(self, other):
+ self._consequential = True
+ self.interfacesBasedOnSelf.add(other)
+
+ def isConsequential(self):
+ return self._consequential
+
+ def setCallback(self, value):
+ self._callback = value
+
+ def isCallback(self):
+ return self._callback
+
+ def isSingleOperationInterface(self):
+ assert self.isCallback() or self.isJSImplemented()
+ return (
+ # JS-implemented things should never need the
+ # this-handling weirdness of single-operation interfaces.
+ not self.isJSImplemented() and
+ # Not inheriting from another interface
+ not self.parent and
+ # No consequential interfaces
+ len(self.getConsequentialInterfaces()) == 0 and
+ # No attributes of any kinds
+ not any(m.isAttr() for m in self.members) and
+ # There is at least one regular operation, and all regular
+ # operations have the same identifier
+ len(set(m.identifier.name for m in self.members if
+ m.isMethod() and not m.isStatic())) == 1)
+
+ def inheritanceDepth(self):
+ depth = 0
+ parent = self.parent
+ while parent:
+ depth = depth + 1
+ parent = parent.parent
+ return depth
+
+ def hasConstants(self):
+ return any(m.isConst() for m in self.members)
+
+ def hasInterfaceObject(self):
+ if self.isCallback():
+ return self.hasConstants()
+ return not hasattr(self, "_noInterfaceObject")
+
+ def hasInterfacePrototypeObject(self):
+ return not self.isCallback() and self.getUserData('hasConcreteDescendant', False)
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ identifier = attr.identifier()
+
+ # Special cased attrs
+ if identifier == "TreatNonCallableAsNull":
+ raise WebIDLError("TreatNonCallableAsNull cannot be specified on interfaces",
+ [attr.location, self.location])
+ if identifier == "TreatNonObjectAsNull":
+ raise WebIDLError("TreatNonObjectAsNull cannot be specified on interfaces",
+ [attr.location, self.location])
+ elif identifier == "NoInterfaceObject":
+ if not attr.noArguments():
+ raise WebIDLError("[NoInterfaceObject] must take no arguments",
+ [attr.location])
+
+ if self.ctor():
+ raise WebIDLError("Constructor and NoInterfaceObject are incompatible",
+ [self.location])
+
+ self._noInterfaceObject = True
+ elif identifier == "Constructor" or identifier == "NamedConstructor" or identifier == "ChromeConstructor":
+ if identifier == "Constructor" and not self.hasInterfaceObject():
+ raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible",
+ [self.location])
+
+ if identifier == "NamedConstructor" and not attr.hasValue():
+ raise WebIDLError("NamedConstructor must either take an identifier or take a named argument list",
+ [attr.location])
+
+ if identifier == "ChromeConstructor" and not self.hasInterfaceObject():
+ raise WebIDLError(str(identifier) + " and NoInterfaceObject are incompatible",
+ [self.location])
+
+ args = attr.args() if attr.hasArgs() else []
+
+ retType = IDLWrapperType(self.location, self)
+
+ if identifier == "Constructor" or identifier == "ChromeConstructor":
+ name = "constructor"
+ allowForbidden = True
+ else:
+ name = attr.value()
+ allowForbidden = False
+
+ methodIdentifier = IDLUnresolvedIdentifier(self.location, name,
+ allowForbidden=allowForbidden)
+
+ method = IDLMethod(self.location, methodIdentifier, retType,
+ args, static=True)
+ # Constructors are always NewObject and are always
+ # assumed to be able to throw (since there's no way to
+ # indicate otherwise) and never have any other
+ # extended attributes.
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("NewObject",)),
+ IDLExtendedAttribute(self.location, ("Throws",))])
+ if identifier == "ChromeConstructor":
+ method.addExtendedAttributes(
+ [IDLExtendedAttribute(self.location, ("ChromeOnly",))])
+
+ if identifier == "Constructor" or identifier == "ChromeConstructor":
+ method.resolve(self)
+ else:
+ # We need to detect conflicts for NamedConstructors across
+ # interfaces. We first call resolve on the parentScope,
+ # which will merge all NamedConstructors with the same
+ # identifier accross interfaces as overloads.
+ method.resolve(self.parentScope)
+
+ # Then we look up the identifier on the parentScope. If the
+ # result is the same as the method we're adding then it
+ # hasn't been added as an overload and it's the first time
+ # we've encountered a NamedConstructor with that identifier.
+ # If the result is not the same as the method we're adding
+ # then it has been added as an overload and we need to check
+ # whether the result is actually one of our existing
+ # NamedConstructors.
+ newMethod = self.parentScope.lookupIdentifier(method.identifier)
+ if newMethod == method:
+ self.namedConstructors.append(method)
+ elif not newMethod in self.namedConstructors:
+ raise WebIDLError("NamedConstructor conflicts with a NamedConstructor of a different interface",
+ [method.location, newMethod.location])
+ elif (identifier == "ArrayClass"):
+ if not attr.noArguments():
+ raise WebIDLError("[ArrayClass] must take no arguments",
+ [attr.location])
+ if self.parent:
+ raise WebIDLError("[ArrayClass] must not be specified on "
+ "an interface with inherited interfaces",
+ [attr.location, self.location])
+ elif (identifier == "ExceptionClass"):
+ if not attr.noArguments():
+ raise WebIDLError("[ExceptionClass] must take no arguments",
+ [attr.location])
+ if self.parent:
+ raise WebIDLError("[ExceptionClass] must not be specified on "
+ "an interface with inherited interfaces",
+ [attr.location, self.location])
+ elif identifier == "Global":
+ if attr.hasValue():
+ self.globalNames = [ attr.value() ]
+ elif attr.hasArgs():
+ self.globalNames = attr.args()
+ else:
+ self.globalNames = [ self.identifier.name ]
+ self.parentScope.globalNames.update(self.globalNames)
+ for globalName in self.globalNames:
+ self.parentScope.globalNameMapping[globalName].add(self.identifier.name)
+ self._isOnGlobalProtoChain = True
+ elif identifier == "PrimaryGlobal":
+ if not attr.noArguments():
+ raise WebIDLError("[PrimaryGlobal] must take no arguments",
+ [attr.location])
+ if self.parentScope.primaryGlobalAttr is not None:
+ raise WebIDLError(
+ "[PrimaryGlobal] specified twice",
+ [attr.location,
+ self.parentScope.primaryGlobalAttr.location])
+ self.parentScope.primaryGlobalAttr = attr
+ self.parentScope.primaryGlobalName = self.identifier.name
+ self.parentScope.globalNames.add(self.identifier.name)
+ self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name)
+ self._isOnGlobalProtoChain = True
+ elif (identifier == "NeedNewResolve" or
+ identifier == "OverrideBuiltins" or
+ identifier == "ChromeOnly" or
+ identifier == "Unforgeable" or
+ identifier == "LegacyEventInit"):
+ # Known extended attributes that do not take values
+ if not attr.noArguments():
+ raise WebIDLError("[%s] must take no arguments" % identifier,
+ [attr.location])
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr,
+ self._exposureGlobalNames)
+ elif (identifier == "Pref" or
+ identifier == "JSImplementation" or
+ identifier == "HeaderFile" or
+ identifier == "NavigatorProperty" or
+ identifier == "AvailableIn" or
+ identifier == "Func" or
+ identifier == "CheckPermissions"):
+ # Known extended attributes that take a string value
+ if not attr.hasValue():
+ raise WebIDLError("[%s] must have a value" % identifier,
+ [attr.location])
+ else:
+ raise WebIDLError("Unknown extended attribute %s on interface" % identifier,
+ [attr.location])
+
+ attrlist = attr.listValue()
+ self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
+
+ def addImplementedInterface(self, implementedInterface):
+ assert(isinstance(implementedInterface, IDLInterface))
+ self.implementedInterfaces.add(implementedInterface)
+
+ def getInheritedInterfaces(self):
+ """
+ Returns a list of the interfaces this interface inherits from
+ (not including this interface itself). The list is in order
+ from most derived to least derived.
+ """
+ assert(self._finished)
+ if not self.parent:
+ return []
+ parentInterfaces = self.parent.getInheritedInterfaces()
+ parentInterfaces.insert(0, self.parent)
+ return parentInterfaces
+
+ def getConsequentialInterfaces(self):
+ assert(self._finished)
+ # The interfaces we implement directly
+ consequentialInterfaces = set(self.implementedInterfaces)
+
+ # And their inherited interfaces
+ for iface in self.implementedInterfaces:
+ consequentialInterfaces |= set(iface.getInheritedInterfaces())
+
+ # And now collect up the consequential interfaces of all of those
+ temp = set()
+ for iface in consequentialInterfaces:
+ temp |= iface.getConsequentialInterfaces()
+
+ return consequentialInterfaces | temp
+
+ def findInterfaceLoopPoint(self, otherInterface):
+ """
+ Finds an interface, amongst our ancestors and consequential interfaces,
+ that inherits from otherInterface or implements otherInterface
+ directly. If there is no such interface, returns None.
+ """
+ if self.parent:
+ if self.parent == otherInterface:
+ return self
+ loopPoint = self.parent.findInterfaceLoopPoint(otherInterface)
+ if loopPoint:
+ return loopPoint
+ if otherInterface in self.implementedInterfaces:
+ return self
+ for iface in self.implementedInterfaces:
+ loopPoint = iface.findInterfaceLoopPoint(otherInterface)
+ if loopPoint:
+ return loopPoint
+ return None
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def setNonPartial(self, location, parent, members):
+ assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
+ if self._isKnownNonPartial:
+ raise WebIDLError("Two non-partial definitions for the "
+ "same interface",
+ [location, self.location])
+ self._isKnownNonPartial = True
+ # Now make it look like we were parsed at this new location, since
+ # that's the place where the interface is "really" defined
+ self.location = location
+ assert not self.parent
+ self.parent = parent
+ # Put the new members at the beginning
+ self.members = members + self.members
+
+ def addPartialInterface(self, partial):
+ assert self.identifier.name == partial.identifier.name
+ self._partialInterfaces.append(partial)
+
+ def getJSImplementation(self):
+ classId = self.getExtendedAttribute("JSImplementation")
+ if not classId:
+ return classId
+ assert isinstance(classId, list)
+ assert len(classId) == 1
+ return classId[0]
+
+ def isJSImplemented(self):
+ return bool(self.getJSImplementation())
+
+ def getNavigatorProperty(self):
+ naviProp = self.getExtendedAttribute("NavigatorProperty")
+ if not naviProp:
+ return None
+ assert len(naviProp) == 1
+ assert isinstance(naviProp, list)
+ assert len(naviProp[0]) != 0
+ return naviProp[0]
+
+ def hasChildInterfaces(self):
+ return self._hasChildInterfaces
+
+ def isOnGlobalProtoChain(self):
+ return self._isOnGlobalProtoChain
+
+ def _getDependentObjects(self):
+ deps = set(self.members)
+ deps.union(self.implementedInterfaces)
+ if self.parent:
+ deps.add(self.parent)
+ return deps
+
+ def hasMembersInSlots(self):
+ return self._ownMembersInSlots != 0
+
+class IDLDictionary(IDLObjectWithScope):
+ def __init__(self, location, parentScope, name, parent, members):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+ assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
+
+ self.parent = parent
+ self._finished = False
+ self.members = list(members)
+
+ IDLObjectWithScope.__init__(self, location, parentScope, name)
+
+ def __str__(self):
+ return "Dictionary '%s'" % self.identifier.name
+
+ def isDictionary(self):
+ return True;
+
+ def finish(self, scope):
+ if self._finished:
+ return
+
+ self._finished = True
+
+ if self.parent:
+ assert isinstance(self.parent, IDLIdentifierPlaceholder)
+ oldParent = self.parent
+ self.parent = self.parent.finish(scope)
+ if not isinstance(self.parent, IDLDictionary):
+ raise WebIDLError("Dictionary %s has parent that is not a dictionary" %
+ self.identifier.name,
+ [oldParent.location, self.parent.location])
+
+ # Make sure the parent resolves all its members before we start
+ # looking at them.
+ self.parent.finish(scope)
+
+ for member in self.members:
+ member.resolve(self)
+ if not member.isComplete():
+ member.complete(scope)
+ assert member.type.isComplete()
+
+ # Members of a dictionary are sorted in lexicographic order
+ self.members.sort(cmp=cmp, key=lambda x: x.identifier.name)
+
+ inheritedMembers = []
+ ancestor = self.parent
+ while ancestor:
+ if ancestor == self:
+ raise WebIDLError("Dictionary %s has itself as an ancestor" %
+ self.identifier.name,
+ [self.identifier.location])
+ inheritedMembers.extend(ancestor.members)
+ ancestor = ancestor.parent
+
+ # Catch name duplication
+ for inheritedMember in inheritedMembers:
+ for member in self.members:
+ if member.identifier.name == inheritedMember.identifier.name:
+ raise WebIDLError("Dictionary %s has two members with name %s" %
+ (self.identifier.name, member.identifier.name),
+ [member.location, inheritedMember.location])
+
+ def validate(self):
+ def typeContainsDictionary(memberType, dictionary):
+ """
+ Returns a tuple whose:
+
+ - First element is a Boolean value indicating whether
+ memberType contains dictionary.
+
+ - Second element is:
+ A list of locations that leads from the type that was passed in
+ the memberType argument, to the dictionary being validated,
+ if the boolean value in the first element is True.
+
+ None, if the boolean value in the first element is False.
+ """
+
+ if (memberType.nullable() or
+ memberType.isArray() or
+ memberType.isSequence() or
+ memberType.isMozMap()):
+ return typeContainsDictionary(memberType.inner, dictionary)
+
+ if memberType.isDictionary():
+ if memberType.inner == dictionary:
+ return (True, [memberType.location])
+
+ (contains, locations) = dictionaryContainsDictionary(memberType.inner, \
+ dictionary)
+ if contains:
+ return (True, [memberType.location] + locations)
+
+ if memberType.isUnion():
+ for member in memberType.flatMemberTypes:
+ (contains, locations) = typeContainsDictionary(member, dictionary)
+ if contains:
+ return (True, locations)
+
+ return (False, None)
+
+ def dictionaryContainsDictionary(dictMember, dictionary):
+ for member in dictMember.members:
+ (contains, locations) = typeContainsDictionary(member.type, dictionary)
+ if contains:
+ return (True, [member.location] + locations)
+
+ if dictMember.parent:
+ if dictMember.parent == dictionary:
+ return (True, [dictMember.location])
+ else:
+ (contains, locations) = dictionaryContainsDictionary(dictMember.parent, dictionary)
+ if contains:
+ return (True, [dictMember.location] + locations)
+
+ return (False, None)
+
+ for member in self.members:
+ if member.type.isDictionary() and member.type.nullable():
+ raise WebIDLError("Dictionary %s has member with nullable "
+ "dictionary type" % self.identifier.name,
+ [member.location])
+ (contains, locations) = typeContainsDictionary(member.type, self)
+ if contains:
+ raise WebIDLError("Dictionary %s has member with itself as type." %
+ self.identifier.name,
+ [member.location] + locations)
+
+ def addExtendedAttributes(self, attrs):
+ assert len(attrs) == 0
+
+ def _getDependentObjects(self):
+ deps = set(self.members)
+ if (self.parent):
+ deps.add(self.parent)
+ return deps
+
+class IDLEnum(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, name, values):
+ assert isinstance(parentScope, IDLScope)
+ assert isinstance(name, IDLUnresolvedIdentifier)
+
+ if len(values) != len(set(values)):
+ raise WebIDLError("Enum %s has multiple identical strings" % name.name,
+ [location])
+
+ IDLObjectWithIdentifier.__init__(self, location, parentScope, name)
+ self._values = values
+
+ def values(self):
+ return self._values
+
+ def finish(self, scope):
+ pass
+
+ def validate(self):
+ pass
+
+ def isEnum(self):
+ return True
+
+ def addExtendedAttributes(self, attrs):
+ assert len(attrs) == 0
+
+ def _getDependentObjects(self):
+ return set()
+
+class IDLType(IDLObject):
+ Tags = enum(
+ # The integer types
+ 'int8',
+ 'uint8',
+ 'int16',
+ 'uint16',
+ 'int32',
+ 'uint32',
+ 'int64',
+ 'uint64',
+ # Additional primitive types
+ 'bool',
+ 'unrestricted_float',
+ 'float',
+ 'unrestricted_double',
+ # "double" last primitive type to match IDLBuiltinType
+ 'double',
+ # Other types
+ 'any',
+ 'domstring',
+ 'bytestring',
+ 'scalarvaluestring',
+ 'object',
+ 'date',
+ 'void',
+ # Funny stuff
+ 'interface',
+ 'dictionary',
+ 'enum',
+ 'callback',
+ 'union',
+ 'sequence',
+ 'mozmap',
+ 'array'
+ )
+
+ def __init__(self, location, name):
+ IDLObject.__init__(self, location)
+ self.name = name
+ self.builtin = False
+
+ def __eq__(self, other):
+ return other and self.builtin == other.builtin and self.name == other.name
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __str__(self):
+ return str(self.name)
+
+ def isType(self):
+ return True
+
+ def nullable(self):
+ return False
+
+ def isPrimitive(self):
+ return False
+
+ def isBoolean(self):
+ return False
+
+ def isNumeric(self):
+ return False
+
+ def isString(self):
+ return False
+
+ def isByteString(self):
+ return False
+
+ def isDOMString(self):
+ return False
+
+ def isScalarValueString(self):
+ return False
+
+ def isVoid(self):
+ return self.name == "Void"
+
+ def isSequence(self):
+ return False
+
+ def isMozMap(self):
+ return False
+
+ def isArray(self):
+ return False
+
+ def isArrayBuffer(self):
+ return False
+
+ def isArrayBufferView(self):
+ return False
+
+ def isTypedArray(self):
+ return False
+
+ def isCallbackInterface(self):
+ return False
+
+ def isNonCallbackInterface(self):
+ return False
+
+ def isGeckoInterface(self):
+ """ Returns a boolean indicating whether this type is an 'interface'
+ type that is implemented in Gecko. At the moment, this returns
+ true for all interface types that are not types from the TypedArray
+ spec."""
+ return self.isInterface() and not self.isSpiderMonkeyInterface()
+
+ def isSpiderMonkeyInterface(self):
+ """ Returns a boolean indicating whether this type is an 'interface'
+ type that is implemented in Spidermonkey. At the moment, this
+ only returns true for the types from the TypedArray spec. """
+ return self.isInterface() and (self.isArrayBuffer() or \
+ self.isArrayBufferView() or \
+ self.isTypedArray())
+
+ def isDictionary(self):
+ return False
+
+ def isInterface(self):
+ return False
+
+ def isAny(self):
+ return self.tag() == IDLType.Tags.any
+
+ def isDate(self):
+ return self.tag() == IDLType.Tags.date
+
+ def isObject(self):
+ return self.tag() == IDLType.Tags.object
+
+ def isPromise(self):
+ return False
+
+ def isComplete(self):
+ return True
+
+ def includesRestrictedFloat(self):
+ return False
+
+ def isFloat(self):
+ return False
+
+ def isUnrestricted(self):
+ # Should only call this on float types
+ assert self.isFloat()
+
+ def isSerializable(self):
+ return False
+
+ def tag(self):
+ assert False # Override me!
+
+ def treatNonCallableAsNull(self):
+ assert self.tag() == IDLType.Tags.callback
+ return self.nullable() and self.inner._treatNonCallableAsNull
+
+ def treatNonObjectAsNull(self):
+ assert self.tag() == IDLType.Tags.callback
+ return self.nullable() and self.inner._treatNonObjectAsNull
+
+ def addExtendedAttributes(self, attrs):
+ assert len(attrs) == 0
+
+ def resolveType(self, parentScope):
+ pass
+
+ def unroll(self):
+ return self
+
+ def isDistinguishableFrom(self, other):
+ raise TypeError("Can't tell whether a generic type is or is not "
+ "distinguishable from other things")
+
+ def isExposedInAllOf(self, exposureSet):
+ return True
+
+class IDLUnresolvedType(IDLType):
+ """
+ Unresolved types are interface types
+ """
+
+ def __init__(self, location, name, promiseInnerType=None):
+ IDLType.__init__(self, location, name)
+ self._promiseInnerType = promiseInnerType
+
+ def isComplete(self):
+ return False
+
+ def complete(self, scope):
+ obj = None
+ try:
+ obj = scope._lookupIdentifier(self.name)
+ except:
+ raise WebIDLError("Unresolved type '%s'." % self.name,
+ [self.location])
+
+ assert obj
+ if obj.isType():
+ # obj itself might not be complete; deal with that.
+ assert obj != self
+ if not obj.isComplete():
+ obj = obj.complete(scope)
+ return obj
+
+ if self._promiseInnerType and not self._promiseInnerType.isComplete():
+ self._promiseInnerType = self._promiseInnerType.complete(scope)
+
+ name = self.name.resolve(scope, None)
+ return IDLWrapperType(self.location, obj, self._promiseInnerType)
+
+ def isDistinguishableFrom(self, other):
+ raise TypeError("Can't tell whether an unresolved type is or is not "
+ "distinguishable from other things")
+
+class IDLNullableType(IDLType):
+ def __init__(self, location, innerType):
+ assert not innerType.isVoid()
+ assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any]
+
+ IDLType.__init__(self, location, innerType.name)
+ self.inner = innerType
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLNullableType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "OrNull"
+
+ def nullable(self):
+ return True
+
+ def isCallback(self):
+ return self.inner.isCallback()
+
+ def isPrimitive(self):
+ return self.inner.isPrimitive()
+
+ def isBoolean(self):
+ return self.inner.isBoolean()
+
+ def isNumeric(self):
+ return self.inner.isNumeric()
+
+ def isString(self):
+ return self.inner.isString()
+
+ def isByteString(self):
+ return self.inner.isByteString()
+
+ def isDOMString(self):
+ return self.inner.isDOMString()
+
+ def isScalarValueString(self):
+ return self.inner.isScalarValueString()
+
+ def isFloat(self):
+ return self.inner.isFloat()
+
+ def isUnrestricted(self):
+ return self.inner.isUnrestricted()
+
+ def includesRestrictedFloat(self):
+ return self.inner.includesRestrictedFloat()
+
+ def isInteger(self):
+ return self.inner.isInteger()
+
+ def isVoid(self):
+ return False
+
+ def isSequence(self):
+ return self.inner.isSequence()
+
+ def isMozMap(self):
+ return self.inner.isMozMap()
+
+ def isArray(self):
+ return self.inner.isArray()
+
+ def isArrayBuffer(self):
+ return self.inner.isArrayBuffer()
+
+ def isArrayBufferView(self):
+ return self.inner.isArrayBufferView()
+
+ def isTypedArray(self):
+ return self.inner.isTypedArray()
+
+ def isDictionary(self):
+ return self.inner.isDictionary()
+
+ def isInterface(self):
+ return self.inner.isInterface()
+
+ def isCallbackInterface(self):
+ return self.inner.isCallbackInterface()
+
+ def isNonCallbackInterface(self):
+ return self.inner.isNonCallbackInterface()
+
+ def isEnum(self):
+ return self.inner.isEnum()
+
+ def isUnion(self):
+ return self.inner.isUnion()
+
+ def isSerializable(self):
+ return self.inner.isSerializable()
+
+ def tag(self):
+ return self.inner.tag()
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.inner.isComplete()
+
+ def complete(self, scope):
+ self.inner = self.inner.complete(scope)
+ if self.inner.nullable():
+ raise WebIDLError("The inner type of a nullable type must not be "
+ "a nullable type",
+ [self.location, self.inner.location])
+ if self.inner.isUnion():
+ if self.inner.hasNullableType:
+ raise WebIDLError("The inner type of a nullable type must not "
+ "be a union type that itself has a nullable "
+ "type as a member type", [self.location])
+
+ self.name = self.inner.name
+ return self
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def isDistinguishableFrom(self, other):
+ if (other.nullable() or (other.isUnion() and other.hasNullableType) or
+ other.isDictionary()):
+ # Can't tell which type null should become
+ return False
+ return self.inner.isDistinguishableFrom(other)
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+class IDLSequenceType(IDLType):
+ def __init__(self, location, parameterType):
+ assert not parameterType.isVoid()
+
+ IDLType.__init__(self, location, parameterType.name)
+ self.inner = parameterType
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLSequenceType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "Sequence"
+
+ def nullable(self):
+ return False
+
+ def isPrimitive(self):
+ return False;
+
+ def isString(self):
+ return False;
+
+ def isByteString(self):
+ return False
+
+ def isDOMString(self):
+ return False
+
+ def isScalarValueString(self):
+ return False
+
+ def isVoid(self):
+ return False
+
+ def isSequence(self):
+ return True
+
+ def isArray(self):
+ return False
+
+ def isDictionary(self):
+ return False
+
+ def isInterface(self):
+ return False
+
+ def isEnum(self):
+ return False
+
+ def isSerializable(self):
+ return self.inner.isSerializable()
+
+ def includesRestrictedFloat(self):
+ return self.inner.includesRestrictedFloat()
+
+ def tag(self):
+ return IDLType.Tags.sequence
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.inner.isComplete()
+
+ def complete(self, scope):
+ self.inner = self.inner.complete(scope)
+ self.name = self.inner.name
+ return self
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isDate() or other.isNonCallbackInterface() or other.isMozMap())
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+class IDLMozMapType(IDLType):
+ # XXXbz This is pretty similar to IDLSequenceType in various ways.
+ # And maybe to IDLNullableType. Should we have a superclass for
+ # "type containing this other type"? Bug 1015318.
+ def __init__(self, location, parameterType):
+ assert not parameterType.isVoid()
+
+ IDLType.__init__(self, location, parameterType.name)
+ self.inner = parameterType
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLMozMapType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "MozMap"
+
+ def isMozMap(self):
+ return True
+
+ def includesRestrictedFloat(self):
+ return self.inner.includesRestrictedFloat()
+
+ def tag(self):
+ return IDLType.Tags.mozmap
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.inner.isComplete()
+
+ def complete(self, scope):
+ self.inner = self.inner.complete(scope)
+ self.name = self.inner.name
+ return self
+
+ def unroll(self):
+ # We do not unroll our inner. Just stop at ourselves. That
+ # lets us add headers for both ourselves and our inner as
+ # needed.
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isDate() or other.isNonCallbackInterface() or other.isSequence())
+
+ def isExposedInAllOf(self, exposureSet):
+ return self.inner.unroll().isExposedInAllOf(exposureSet)
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+class IDLUnionType(IDLType):
+ def __init__(self, location, memberTypes):
+ IDLType.__init__(self, location, "")
+ self.memberTypes = memberTypes
+ self.hasNullableType = False
+ self.hasDictionaryType = False
+ self.flatMemberTypes = None
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes
+
+ def isVoid(self):
+ return False
+
+ def isUnion(self):
+ return True
+
+ def isSerializable(self):
+ return all(m.isSerializable() for m in self.memberTypes)
+
+ def includesRestrictedFloat(self):
+ return any(t.includesRestrictedFloat() for t in self.memberTypes)
+
+ def tag(self):
+ return IDLType.Tags.union
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ for t in self.memberTypes:
+ t.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.flatMemberTypes is not None
+
+ def complete(self, scope):
+ def typeName(type):
+ if isinstance(type, IDLNullableType):
+ return typeName(type.inner) + "OrNull"
+ if isinstance(type, IDLWrapperType):
+ return typeName(type._identifier.object())
+ if isinstance(type, IDLObjectWithIdentifier):
+ return typeName(type.identifier)
+ if (isinstance(type, IDLType) and
+ (type.isArray() or type.isSequence() or type.isMozMap)):
+ return str(type)
+ return type.name
+
+ for (i, type) in enumerate(self.memberTypes):
+ if not type.isComplete():
+ self.memberTypes[i] = type.complete(scope)
+
+ self.name = "Or".join(typeName(type) for type in self.memberTypes)
+ self.flatMemberTypes = list(self.memberTypes)
+ i = 0
+ while i < len(self.flatMemberTypes):
+ if self.flatMemberTypes[i].nullable():
+ if self.hasNullableType:
+ raise WebIDLError("Can't have more than one nullable types in a union",
+ [nullableType.location, self.flatMemberTypes[i].location])
+ if self.hasDictionaryType:
+ raise WebIDLError("Can't have a nullable type and a "
+ "dictionary type in a union",
+ [dictionaryType.location,
+ self.flatMemberTypes[i].location])
+ self.hasNullableType = True
+ nullableType = self.flatMemberTypes[i]
+ self.flatMemberTypes[i] = self.flatMemberTypes[i].inner
+ continue
+ if self.flatMemberTypes[i].isDictionary():
+ if self.hasNullableType:
+ raise WebIDLError("Can't have a nullable type and a "
+ "dictionary type in a union",
+ [nullableType.location,
+ self.flatMemberTypes[i].location])
+ self.hasDictionaryType = True
+ dictionaryType = self.flatMemberTypes[i]
+ elif self.flatMemberTypes[i].isUnion():
+ self.flatMemberTypes[i:i + 1] = self.flatMemberTypes[i].memberTypes
+ continue
+ i += 1
+
+ for (i, t) in enumerate(self.flatMemberTypes[:-1]):
+ for u in self.flatMemberTypes[i + 1:]:
+ if not t.isDistinguishableFrom(u):
+ raise WebIDLError("Flat member types of a union should be "
+ "distinguishable, " + str(t) + " is not "
+ "distinguishable from " + str(u),
+ [self.location, t.location, u.location])
+
+ return self
+
+ def isDistinguishableFrom(self, other):
+ if self.hasNullableType and other.nullable():
+ # Can't tell which type null should become
+ return False
+ if other.isUnion():
+ otherTypes = other.unroll().memberTypes
+ else:
+ otherTypes = [other]
+ # For every type in otherTypes, check that it's distinguishable from
+ # every type in our types
+ for u in otherTypes:
+ if any(not t.isDistinguishableFrom(u) for t in self.memberTypes):
+ return False
+ return True
+
+ def isExposedInAllOf(self, exposureSet):
+ # We could have different member types in different globals. Just make sure that each thing in exposureSet has one of our member types exposed in it.
+ for globalName in exposureSet:
+ if not any(t.unroll().isExposedInAllOf(set([globalName])) for t
+ in self.flatMemberTypes):
+ return False
+ return True
+
+ def _getDependentObjects(self):
+ return set(self.memberTypes)
+
+class IDLArrayType(IDLType):
+ def __init__(self, location, parameterType):
+ assert not parameterType.isVoid()
+ if parameterType.isSequence():
+ raise WebIDLError("Array type cannot parameterize over a sequence type",
+ [location])
+ if parameterType.isMozMap():
+ raise WebIDLError("Array type cannot parameterize over a MozMap type",
+ [location])
+ if parameterType.isDictionary():
+ raise WebIDLError("Array type cannot parameterize over a dictionary type",
+ [location])
+
+ IDLType.__init__(self, location, parameterType.name)
+ self.inner = parameterType
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLArrayType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.inner.__str__() + "Array"
+
+ def nullable(self):
+ return False
+
+ def isPrimitive(self):
+ return False
+
+ def isString(self):
+ return False
+
+ def isByteString(self):
+ return False
+
+ def isDOMString(self):
+ return False
+
+ def isScalarValueString(self):
+ return False
+
+ def isVoid(self):
+ return False
+
+ def isSequence(self):
+ assert not self.inner.isSequence()
+ return False
+
+ def isArray(self):
+ return True
+
+ def isDictionary(self):
+ assert not self.inner.isDictionary()
+ return False
+
+ def isInterface(self):
+ return False
+
+ def isEnum(self):
+ return False
+
+ def tag(self):
+ return IDLType.Tags.array
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolveType(parentScope)
+
+ def isComplete(self):
+ return self.inner.isComplete()
+
+ def complete(self, scope):
+ self.inner = self.inner.complete(scope)
+ self.name = self.inner.name
+
+ if self.inner.isDictionary():
+ raise WebIDLError("Array type must not contain "
+ "dictionary as element type.",
+ [self.inner.location])
+
+ assert not self.inner.isSequence()
+
+ return self
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isDate() or other.isNonCallbackInterface())
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+class IDLTypedefType(IDLType, IDLObjectWithIdentifier):
+ def __init__(self, location, innerType, name):
+ IDLType.__init__(self, location, innerType.name)
+
+ identifier = IDLUnresolvedIdentifier(location, name)
+
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+
+ self.inner = innerType
+ self.name = name
+ self.builtin = False
+
+ def __eq__(self, other):
+ return isinstance(other, IDLTypedefType) and self.inner == other.inner
+
+ def __str__(self):
+ return self.identifier.name
+
+ def nullable(self):
+ return self.inner.nullable()
+
+ def isPrimitive(self):
+ return self.inner.isPrimitive()
+
+ def isBoolean(self):
+ return self.inner.isBoolean()
+
+ def isNumeric(self):
+ return self.inner.isNumeric()
+
+ def isString(self):
+ return self.inner.isString()
+
+ def isByteString(self):
+ return self.inner.isByteString()
+
+ def isDOMString(self):
+ return self.inner.isDOMString()
+
+ def isScalarValueString(self):
+ return self.inner.isScalarValueString()
+
+ def isVoid(self):
+ return self.inner.isVoid()
+
+ def isSequence(self):
+ return self.inner.isSequence()
+
+ def isMozMap(self):
+ return self.inner.isMozMap()
+
+ def isArray(self):
+ return self.inner.isArray()
+
+ def isDictionary(self):
+ return self.inner.isDictionary()
+
+ def isArrayBuffer(self):
+ return self.inner.isArrayBuffer()
+
+ def isArrayBufferView(self):
+ return self.inner.isArrayBufferView()
+
+ def isTypedArray(self):
+ return self.inner.isTypedArray()
+
+ def isInterface(self):
+ return self.inner.isInterface()
+
+ def isCallbackInterface(self):
+ return self.inner.isCallbackInterface()
+
+ def isNonCallbackInterface(self):
+ return self.inner.isNonCallbackInterface()
+
+ def isComplete(self):
+ return False
+
+ def complete(self, parentScope):
+ if not self.inner.isComplete():
+ self.inner = self.inner.complete(parentScope)
+ assert self.inner.isComplete()
+ return self.inner
+
+ def finish(self, parentScope):
+ # Maybe the IDLObjectWithIdentifier for the typedef should be
+ # a separate thing from the type? If that happens, we can
+ # remove some hackery around avoiding isInterface() in
+ # Configuration.py.
+ self.complete(parentScope)
+
+ def validate(self):
+ pass
+
+ # Do we need a resolveType impl? I don't think it's particularly useful....
+
+ def tag(self):
+ return self.inner.tag()
+
+ def unroll(self):
+ return self.inner.unroll()
+
+ def isDistinguishableFrom(self, other):
+ return self.inner.isDistinguishableFrom(other)
+
+ def _getDependentObjects(self):
+ return self.inner._getDependentObjects()
+
+class IDLWrapperType(IDLType):
+ def __init__(self, location, inner, promiseInnerType=None):
+ IDLType.__init__(self, location, inner.identifier.name)
+ self.inner = inner
+ self._identifier = inner.identifier
+ self.builtin = False
+ assert not promiseInnerType or inner.identifier.name == "Promise"
+ self._promiseInnerType = promiseInnerType
+
+ def __eq__(self, other):
+ return isinstance(other, IDLWrapperType) and \
+ self._identifier == other._identifier and \
+ self.builtin == other.builtin
+
+ def __str__(self):
+ return str(self.name) + " (Wrapper)"
+
+ def nullable(self):
+ return False
+
+ def isPrimitive(self):
+ return False
+
+ def isString(self):
+ return False
+
+ def isByteString(self):
+ return False
+
+ def isDOMString(self):
+ return False
+
+ def isScalarValueString(self):
+ return False
+
+ def isVoid(self):
+ return False
+
+ def isSequence(self):
+ return False
+
+ def isArray(self):
+ return False
+
+ def isDictionary(self):
+ return isinstance(self.inner, IDLDictionary)
+
+ def isInterface(self):
+ return isinstance(self.inner, IDLInterface) or \
+ isinstance(self.inner, IDLExternalInterface)
+
+ def isCallbackInterface(self):
+ return self.isInterface() and self.inner.isCallback()
+
+ def isNonCallbackInterface(self):
+ return self.isInterface() and not self.inner.isCallback()
+
+ def isEnum(self):
+ return isinstance(self.inner, IDLEnum)
+
+ def isPromise(self):
+ return isinstance(self.inner, IDLInterface) and \
+ self.inner.identifier.name == "Promise"
+
+ def isSerializable(self):
+ if self.isInterface():
+ if self.inner.isExternal():
+ return False
+ return any(m.isMethod() and m.isJsonifier() for m in self.inner.members)
+ elif self.isEnum():
+ return True
+ elif self.isDictionary():
+ return all(m.type.isSerializable() for m in self.inner.members)
+ else:
+ raise WebIDLError("IDLWrapperType wraps type %s that we don't know if "
+ "is serializable" % type(self.inner), [self.location])
+
+ def resolveType(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.inner.resolve(parentScope)
+
+ def isComplete(self):
+ return True
+
+ def tag(self):
+ if self.isInterface():
+ return IDLType.Tags.interface
+ elif self.isEnum():
+ return IDLType.Tags.enum
+ elif self.isDictionary():
+ return IDLType.Tags.dictionary
+ else:
+ assert False
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ assert self.isInterface() or self.isEnum() or self.isDictionary()
+ if self.isEnum():
+ return (other.isPrimitive() or other.isInterface() or other.isObject() or
+ other.isCallback() or other.isDictionary() or
+ other.isSequence() or other.isMozMap() or other.isArray() or
+ other.isDate())
+ if self.isDictionary() and other.nullable():
+ return False
+ if other.isPrimitive() or other.isString() or other.isEnum() or other.isDate():
+ return True
+ if self.isDictionary():
+ return other.isNonCallbackInterface()
+
+ assert self.isInterface()
+ if other.isInterface():
+ if other.isSpiderMonkeyInterface():
+ # Just let |other| handle things
+ return other.isDistinguishableFrom(self)
+ assert self.isGeckoInterface() and other.isGeckoInterface()
+ if self.inner.isExternal() or other.unroll().inner.isExternal():
+ return self != other
+ return (len(self.inner.interfacesBasedOnSelf &
+ other.unroll().inner.interfacesBasedOnSelf) == 0 and
+ (self.isNonCallbackInterface() or
+ other.isNonCallbackInterface()))
+ if (other.isDictionary() or other.isCallback() or
+ other.isSequence() or other.isMozMap() or other.isArray()):
+ return self.isNonCallbackInterface()
+
+ # Not much else |other| can be
+ assert other.isObject()
+ return False
+
+ def isExposedInAllOf(self, exposureSet):
+ if not self.isInterface():
+ return True
+ iface = self.inner
+ if iface.isExternal():
+ # Let's say true, though ideally we'd only do this when
+ # exposureSet contains the primary global's name.
+ return True
+ if (iface.identifier.name == "Promise" and
+ # Check the internal type
+ not self._promiseInnerType.unroll().isExposedInAllOf(exposureSet)):
+ return False
+ return iface.exposureSet.issuperset(exposureSet)
+
+ def _getDependentObjects(self):
+ # NB: The codegen for an interface type depends on
+ # a) That the identifier is in fact an interface (as opposed to
+ # a dictionary or something else).
+ # b) The native type of the interface.
+ # If we depend on the interface object we will also depend on
+ # anything the interface depends on which is undesirable. We
+ # considered implementing a dependency just on the interface type
+ # file, but then every modification to an interface would cause this
+ # to be regenerated which is still undesirable. We decided not to
+ # depend on anything, reasoning that:
+ # 1) Changing the concrete type of the interface requires modifying
+ # Bindings.conf, which is still a global dependency.
+ # 2) Changing an interface to a dictionary (or vice versa) with the
+ # same identifier should be incredibly rare.
+ return set()
+
+class IDLBuiltinType(IDLType):
+
+ Types = enum(
+ # The integer types
+ 'byte',
+ 'octet',
+ 'short',
+ 'unsigned_short',
+ 'long',
+ 'unsigned_long',
+ 'long_long',
+ 'unsigned_long_long',
+ # Additional primitive types
+ 'boolean',
+ 'unrestricted_float',
+ 'float',
+ 'unrestricted_double',
+ # IMPORTANT: "double" must be the last primitive type listed
+ 'double',
+ # Other types
+ 'any',
+ 'domstring',
+ 'bytestring',
+ 'scalarvaluestring',
+ 'object',
+ 'date',
+ 'void',
+ # Funny stuff
+ 'ArrayBuffer',
+ 'ArrayBufferView',
+ 'Int8Array',
+ 'Uint8Array',
+ 'Uint8ClampedArray',
+ 'Int16Array',
+ 'Uint16Array',
+ 'Int32Array',
+ 'Uint32Array',
+ 'Float32Array',
+ 'Float64Array'
+ )
+
+ TagLookup = {
+ Types.byte: IDLType.Tags.int8,
+ Types.octet: IDLType.Tags.uint8,
+ Types.short: IDLType.Tags.int16,
+ Types.unsigned_short: IDLType.Tags.uint16,
+ Types.long: IDLType.Tags.int32,
+ Types.unsigned_long: IDLType.Tags.uint32,
+ Types.long_long: IDLType.Tags.int64,
+ Types.unsigned_long_long: IDLType.Tags.uint64,
+ Types.boolean: IDLType.Tags.bool,
+ Types.unrestricted_float: IDLType.Tags.unrestricted_float,
+ Types.float: IDLType.Tags.float,
+ Types.unrestricted_double: IDLType.Tags.unrestricted_double,
+ Types.double: IDLType.Tags.double,
+ Types.any: IDLType.Tags.any,
+ Types.domstring: IDLType.Tags.domstring,
+ Types.bytestring: IDLType.Tags.bytestring,
+ Types.scalarvaluestring: IDLType.Tags.scalarvaluestring,
+ Types.object: IDLType.Tags.object,
+ Types.date: IDLType.Tags.date,
+ Types.void: IDLType.Tags.void,
+ Types.ArrayBuffer: IDLType.Tags.interface,
+ Types.ArrayBufferView: IDLType.Tags.interface,
+ Types.Int8Array: IDLType.Tags.interface,
+ Types.Uint8Array: IDLType.Tags.interface,
+ Types.Uint8ClampedArray: IDLType.Tags.interface,
+ Types.Int16Array: IDLType.Tags.interface,
+ Types.Uint16Array: IDLType.Tags.interface,
+ Types.Int32Array: IDLType.Tags.interface,
+ Types.Uint32Array: IDLType.Tags.interface,
+ Types.Float32Array: IDLType.Tags.interface,
+ Types.Float64Array: IDLType.Tags.interface
+ }
+
+ def __init__(self, location, name, type):
+ IDLType.__init__(self, location, name)
+ self.builtin = True
+ self._typeTag = type
+
+ def isPrimitive(self):
+ return self._typeTag <= IDLBuiltinType.Types.double
+
+ def isBoolean(self):
+ return self._typeTag == IDLBuiltinType.Types.boolean
+
+ def isNumeric(self):
+ return self.isPrimitive() and not self.isBoolean()
+
+ def isString(self):
+ return self._typeTag == IDLBuiltinType.Types.domstring or \
+ self._typeTag == IDLBuiltinType.Types.bytestring or \
+ self._typeTag == IDLBuiltinType.Types.scalarvaluestring
+
+ def isByteString(self):
+ return self._typeTag == IDLBuiltinType.Types.bytestring
+
+ def isDOMString(self):
+ return self._typeTag == IDLBuiltinType.Types.domstring
+
+ def isScalarValueString(self):
+ return self._typeTag == IDLBuiltinType.Types.scalarvaluestring
+
+ def isInteger(self):
+ return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
+
+ def isArrayBuffer(self):
+ return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
+
+ def isArrayBufferView(self):
+ return self._typeTag == IDLBuiltinType.Types.ArrayBufferView
+
+ def isTypedArray(self):
+ return self._typeTag >= IDLBuiltinType.Types.Int8Array and \
+ self._typeTag <= IDLBuiltinType.Types.Float64Array
+
+ def isInterface(self):
+ # TypedArray things are interface types per the TypedArray spec,
+ # but we handle them as builtins because SpiderMonkey implements
+ # all of it internally.
+ return self.isArrayBuffer() or \
+ self.isArrayBufferView() or \
+ self.isTypedArray()
+
+ def isNonCallbackInterface(self):
+ # All the interfaces we can be are non-callback
+ return self.isInterface()
+
+ def isFloat(self):
+ return self._typeTag == IDLBuiltinType.Types.float or \
+ self._typeTag == IDLBuiltinType.Types.double or \
+ self._typeTag == IDLBuiltinType.Types.unrestricted_float or \
+ self._typeTag == IDLBuiltinType.Types.unrestricted_double
+
+ def isUnrestricted(self):
+ assert self.isFloat()
+ return self._typeTag == IDLBuiltinType.Types.unrestricted_float or \
+ self._typeTag == IDLBuiltinType.Types.unrestricted_double
+
+ def isSerializable(self):
+ return self.isPrimitive() or self.isDOMString() or self.isDate()
+
+ def includesRestrictedFloat(self):
+ return self.isFloat() and not self.isUnrestricted()
+
+ def tag(self):
+ return IDLBuiltinType.TagLookup[self._typeTag]
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ if self.isBoolean():
+ return (other.isNumeric() or other.isString() or other.isEnum() or
+ other.isInterface() or other.isObject() or
+ other.isCallback() or other.isDictionary() or
+ other.isSequence() or other.isMozMap() or other.isArray() or
+ other.isDate())
+ if self.isNumeric():
+ return (other.isBoolean() or other.isString() or other.isEnum() or
+ other.isInterface() or other.isObject() or
+ other.isCallback() or other.isDictionary() or
+ other.isSequence() or other.isMozMap() or other.isArray() or
+ other.isDate())
+ if self.isString():
+ return (other.isPrimitive() or other.isInterface() or
+ other.isObject() or
+ other.isCallback() or other.isDictionary() or
+ other.isSequence() or other.isMozMap() or other.isArray() or
+ other.isDate())
+ if self.isAny():
+ # Can't tell "any" apart from anything
+ return False
+ if self.isObject():
+ return other.isPrimitive() or other.isString() or other.isEnum()
+ if self.isDate():
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isInterface() or other.isCallback() or
+ other.isDictionary() or other.isSequence() or
+ other.isMozMap() or other.isArray())
+ if self.isVoid():
+ return not other.isVoid()
+ # Not much else we could be!
+ assert self.isSpiderMonkeyInterface()
+ # Like interfaces, but we know we're not a callback
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isCallback() or other.isDictionary() or
+ other.isSequence() or other.isMozMap() or other.isArray() or
+ other.isDate() or
+ (other.isInterface() and (
+ # ArrayBuffer is distinguishable from everything
+ # that's not an ArrayBuffer or a callback interface
+ (self.isArrayBuffer() and not other.isArrayBuffer()) or
+ # ArrayBufferView is distinguishable from everything
+ # that's not an ArrayBufferView or typed array.
+ (self.isArrayBufferView() and not other.isArrayBufferView() and
+ not other.isTypedArray()) or
+ # Typed arrays are distinguishable from everything
+ # except ArrayBufferView and the same type of typed
+ # array
+ (self.isTypedArray() and not other.isArrayBufferView() and not
+ (other.isTypedArray() and other.name == self.name)))))
+
+ def _getDependentObjects(self):
+ return set()
+
+BuiltinTypes = {
+ IDLBuiltinType.Types.byte:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Byte",
+ IDLBuiltinType.Types.byte),
+ IDLBuiltinType.Types.octet:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Octet",
+ IDLBuiltinType.Types.octet),
+ IDLBuiltinType.Types.short:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Short",
+ IDLBuiltinType.Types.short),
+ IDLBuiltinType.Types.unsigned_short:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedShort",
+ IDLBuiltinType.Types.unsigned_short),
+ IDLBuiltinType.Types.long:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Long",
+ IDLBuiltinType.Types.long),
+ IDLBuiltinType.Types.unsigned_long:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedLong",
+ IDLBuiltinType.Types.unsigned_long),
+ IDLBuiltinType.Types.long_long:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "LongLong",
+ IDLBuiltinType.Types.long_long),
+ IDLBuiltinType.Types.unsigned_long_long:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnsignedLongLong",
+ IDLBuiltinType.Types.unsigned_long_long),
+ IDLBuiltinType.Types.boolean:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Boolean",
+ IDLBuiltinType.Types.boolean),
+ IDLBuiltinType.Types.float:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float",
+ IDLBuiltinType.Types.float),
+ IDLBuiltinType.Types.unrestricted_float:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnrestrictedFloat",
+ IDLBuiltinType.Types.unrestricted_float),
+ IDLBuiltinType.Types.double:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Double",
+ IDLBuiltinType.Types.double),
+ IDLBuiltinType.Types.unrestricted_double:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "UnrestrictedDouble",
+ IDLBuiltinType.Types.unrestricted_double),
+ IDLBuiltinType.Types.any:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Any",
+ IDLBuiltinType.Types.any),
+ IDLBuiltinType.Types.domstring:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "String",
+ IDLBuiltinType.Types.domstring),
+ IDLBuiltinType.Types.bytestring:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ByteString",
+ IDLBuiltinType.Types.bytestring),
+ IDLBuiltinType.Types.scalarvaluestring:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ScalarValueString",
+ IDLBuiltinType.Types.scalarvaluestring),
+ IDLBuiltinType.Types.object:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Object",
+ IDLBuiltinType.Types.object),
+ IDLBuiltinType.Types.date:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Date",
+ IDLBuiltinType.Types.date),
+ IDLBuiltinType.Types.void:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Void",
+ IDLBuiltinType.Types.void),
+ IDLBuiltinType.Types.ArrayBuffer:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBuffer",
+ IDLBuiltinType.Types.ArrayBuffer),
+ IDLBuiltinType.Types.ArrayBufferView:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ArrayBufferView",
+ IDLBuiltinType.Types.ArrayBufferView),
+ IDLBuiltinType.Types.Int8Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int8Array",
+ IDLBuiltinType.Types.Int8Array),
+ IDLBuiltinType.Types.Uint8Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint8Array",
+ IDLBuiltinType.Types.Uint8Array),
+ IDLBuiltinType.Types.Uint8ClampedArray:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint8ClampedArray",
+ IDLBuiltinType.Types.Uint8ClampedArray),
+ IDLBuiltinType.Types.Int16Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int16Array",
+ IDLBuiltinType.Types.Int16Array),
+ IDLBuiltinType.Types.Uint16Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint16Array",
+ IDLBuiltinType.Types.Uint16Array),
+ IDLBuiltinType.Types.Int32Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Int32Array",
+ IDLBuiltinType.Types.Int32Array),
+ IDLBuiltinType.Types.Uint32Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Uint32Array",
+ IDLBuiltinType.Types.Uint32Array),
+ IDLBuiltinType.Types.Float32Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float32Array",
+ IDLBuiltinType.Types.Float32Array),
+ IDLBuiltinType.Types.Float64Array:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float64Array",
+ IDLBuiltinType.Types.Float64Array)
+ }
+
+
+integerTypeSizes = {
+ IDLBuiltinType.Types.byte: (-128, 127),
+ IDLBuiltinType.Types.octet: (0, 255),
+ IDLBuiltinType.Types.short: (-32768, 32767),
+ IDLBuiltinType.Types.unsigned_short: (0, 65535),
+ IDLBuiltinType.Types.long: (-2147483648, 2147483647),
+ IDLBuiltinType.Types.unsigned_long: (0, 4294967295),
+ IDLBuiltinType.Types.long_long: (-9223372036854775808,
+ 9223372036854775807),
+ IDLBuiltinType.Types.unsigned_long_long: (0, 18446744073709551615)
+ }
+
+def matchIntegerValueToType(value):
+ for type, extremes in integerTypeSizes.items():
+ (min, max) = extremes
+ if value <= max and value >= min:
+ return BuiltinTypes[type]
+
+ return None
+
+class IDLValue(IDLObject):
+ def __init__(self, location, type, value):
+ IDLObject.__init__(self, location)
+ self.type = type
+ assert isinstance(type, IDLType)
+
+ self.value = value
+
+ def coerceToType(self, type, location):
+ if type == self.type:
+ return self # Nothing to do
+
+ # We first check for unions to ensure that even if the union is nullable
+ # we end up with the right flat member type, not the union's type.
+ if type.isUnion():
+ # We use the flat member types here, because if we have a nullable
+ # member type, or a nested union, we want the type the value
+ # actually coerces to, not the nullable or nested union type.
+ for subtype in type.unroll().flatMemberTypes:
+ try:
+ coercedValue = self.coerceToType(subtype, location)
+ # Create a new IDLValue to make sure that we have the
+ # correct float/double type. This is necessary because we
+ # use the value's type when it is a default value of a
+ # union, and the union cares about the exact float type.
+ return IDLValue(self.location, subtype, coercedValue.value)
+ except:
+ pass
+ # If the type allows null, rerun this matching on the inner type, except
+ # nullable enums. We handle those specially, because we want our
+ # default string values to stay strings even when assigned to a nullable
+ # enum.
+ elif type.nullable() and not type.isEnum():
+ innerValue = self.coerceToType(type.inner, location)
+ return IDLValue(self.location, type, innerValue.value)
+
+ elif self.type.isInteger() and type.isInteger():
+ # We're both integer types. See if we fit.
+
+ (min, max) = integerTypeSizes[type._typeTag]
+ if self.value <= max and self.value >= min:
+ # Promote
+ return IDLValue(self.location, type, self.value)
+ else:
+ raise WebIDLError("Value %s is out of range for type %s." %
+ (self.value, type), [location])
+ elif self.type.isInteger() and type.isFloat():
+ # Convert an integer literal into float
+ if -2**24 <= self.value <= 2**24:
+ floatType = BuiltinTypes[IDLBuiltinType.Types.float]
+ return IDLValue(self.location, floatType, float(self.value))
+ else:
+ raise WebIDLError("Converting value %s to %s will lose precision." %
+ (self.value, type), [location])
+ elif self.type.isString() and type.isEnum():
+ # Just keep our string, but make sure it's a valid value for this enum
+ enum = type.unroll().inner
+ if self.value not in enum.values():
+ raise WebIDLError("'%s' is not a valid default value for enum %s"
+ % (self.value, enum.identifier.name),
+ [location, enum.location])
+ return self
+ elif self.type.isFloat() and type.isFloat():
+ if (not type.isUnrestricted() and
+ (self.value == float("inf") or self.value == float("-inf") or
+ math.isnan(self.value))):
+ raise WebIDLError("Trying to convert unrestricted value %s to non-unrestricted"
+ % self.value, [location]);
+ return self
+ elif self.type.isString() and type.isScalarValueString():
+ # Allow ScalarValueStrings to use default value just like
+ # DOMString. No coercion is required in this case as Codegen.py
+ # treats ScalarValueString just like DOMString, but with an
+ # extra normalization step.
+ assert self.type.isDOMString()
+ return self
+ raise WebIDLError("Cannot coerce type %s to type %s." %
+ (self.type, type), [location])
+
+ def _getDependentObjects(self):
+ return set()
+
+class IDLNullValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if (not isinstance(type, IDLNullableType) and
+ not (type.isUnion() and type.hasNullableType) and
+ not (type.isUnion() and type.hasDictionaryType) and
+ not type.isDictionary() and
+ not type.isAny()):
+ raise WebIDLError("Cannot coerce null value to type %s." % type,
+ [location])
+
+ nullValue = IDLNullValue(self.location)
+ if type.isUnion() and not type.nullable() and type.hasDictionaryType:
+ # We're actually a default value for the union's dictionary member.
+ # Use its type.
+ for t in type.flatMemberTypes:
+ if t.isDictionary():
+ nullValue.type = t
+ return nullValue
+ nullValue.type = type
+ return nullValue
+
+ def _getDependentObjects(self):
+ return set()
+
+class IDLEmptySequenceValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if type.isUnion():
+ # We use the flat member types here, because if we have a nullable
+ # member type, or a nested union, we want the type the value
+ # actually coerces to, not the nullable or nested union type.
+ for subtype in type.unroll().flatMemberTypes:
+ try:
+ return self.coerceToType(subtype, location)
+ except:
+ pass
+
+ if not type.isSequence():
+ raise WebIDLError("Cannot coerce empty sequence value to type %s." % type,
+ [location])
+
+ emptySequenceValue = IDLEmptySequenceValue(self.location)
+ emptySequenceValue.type = type
+ return emptySequenceValue
+
+ def _getDependentObjects(self):
+ return set()
+
+class IDLUndefinedValue(IDLObject):
+ def __init__(self, location):
+ IDLObject.__init__(self, location)
+ self.type = None
+ self.value = None
+
+ def coerceToType(self, type, location):
+ if not type.isAny():
+ raise WebIDLError("Cannot coerce undefined value to type %s." % type,
+ [location])
+
+ undefinedValue = IDLUndefinedValue(self.location)
+ undefinedValue.type = type
+ return undefinedValue
+
+ def _getDependentObjects(self):
+ return set()
+
+class IDLInterfaceMember(IDLObjectWithIdentifier):
+
+ Tags = enum(
+ 'Const',
+ 'Attr',
+ 'Method'
+ )
+
+ Special = enum(
+ 'Static',
+ 'Stringifier'
+ )
+
+ def __init__(self, location, identifier, tag):
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+ self.tag = tag
+ self._extendedAttrDict = {}
+ # _exposureGlobalNames are the global names listed in our [Exposed]
+ # extended attribute. exposureSet is the exposure set as defined in the
+ # Web IDL spec: it contains interface names.
+ self._exposureGlobalNames = set()
+ self.exposureSet = set()
+
+ def isMethod(self):
+ return self.tag == IDLInterfaceMember.Tags.Method
+
+ def isAttr(self):
+ return self.tag == IDLInterfaceMember.Tags.Attr
+
+ def isConst(self):
+ return self.tag == IDLInterfaceMember.Tags.Const
+
+ def addExtendedAttributes(self, attrs):
+ for attr in attrs:
+ self.handleExtendedAttribute(attr)
+ attrlist = attr.listValue()
+ self._extendedAttrDict[attr.identifier()] = attrlist if len(attrlist) else True
+
+ def handleExtendedAttribute(self, attr):
+ pass
+
+ def getExtendedAttribute(self, name):
+ return self._extendedAttrDict.get(name, None)
+
+ def finish(self, scope):
+ for globalName in self._exposureGlobalNames:
+ if globalName not in scope.globalNames:
+ raise WebIDLError("Unknown [Exposed] value %s" % globalName,
+ [self.location])
+ globalNameSetToExposureSet(scope, self._exposureGlobalNames,
+ self.exposureSet)
+ self._scope = scope
+
+ def validate(self):
+ if (self.getExtendedAttribute("Pref") and
+ self.exposureSet != set([self._scope.primaryGlobalName])):
+ raise WebIDLError("[Pref] used on an interface member that is not "
+ "%s-only" % self._scope.primaryGlobalName,
+ [self.location])
+
+class IDLConst(IDLInterfaceMember):
+ def __init__(self, location, identifier, type, value):
+ IDLInterfaceMember.__init__(self, location, identifier,
+ IDLInterfaceMember.Tags.Const)
+
+ assert isinstance(type, IDLType)
+ if type.isDictionary():
+ raise WebIDLError("A constant cannot be of a dictionary type",
+ [self.location])
+ self.type = type
+ self.value = value
+
+ if identifier.name == "prototype":
+ raise WebIDLError("The identifier of a constant must not be 'prototype'",
+ [location])
+
+ def __str__(self):
+ return "'%s' const '%s'" % (self.type, self.identifier)
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ if not self.type.isComplete():
+ type = self.type.complete(scope)
+ if not type.isPrimitive() and not type.isString():
+ locations = [self.type.location, type.location]
+ try:
+ locations.append(type.inner.location)
+ except:
+ pass
+ raise WebIDLError("Incorrect type for constant", locations)
+ self.type = type
+
+ # The value might not match the type
+ coercedValue = self.value.coerceToType(self.type, self.location)
+ assert coercedValue
+
+ self.value = coercedValue
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (identifier == "Pref" or
+ identifier == "ChromeOnly" or
+ identifier == "Func" or
+ identifier == "AvailableIn" or
+ identifier == "CheckPermissions"):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError("Unknown extended attribute %s on constant" % identifier,
+ [attr.location])
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def _getDependentObjects(self):
+ return set([self.type, self.value])
+
+class IDLAttribute(IDLInterfaceMember):
+ def __init__(self, location, identifier, type, readonly, inherit=False,
+ static=False, stringifier=False):
+ IDLInterfaceMember.__init__(self, location, identifier,
+ IDLInterfaceMember.Tags.Attr)
+
+ assert isinstance(type, IDLType)
+ self.type = type
+ self.readonly = readonly
+ self.inherit = inherit
+ self.static = static
+ self.lenientThis = False
+ self._unforgeable = False
+ self.stringifier = stringifier
+ self.enforceRange = False
+ self.clamp = False
+ self.slotIndex = None
+
+ if static and identifier.name == "prototype":
+ raise WebIDLError("The identifier of a static attribute must not be 'prototype'",
+ [location])
+
+ if readonly and inherit:
+ raise WebIDLError("An attribute cannot be both 'readonly' and 'inherit'",
+ [self.location])
+
+ def isStatic(self):
+ return self.static
+
+ def __str__(self):
+ return "'%s' attribute '%s'" % (self.type, self.identifier)
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ if not self.type.isComplete():
+ t = self.type.complete(scope)
+
+ assert not isinstance(t, IDLUnresolvedType)
+ assert not isinstance(t, IDLTypedefType)
+ assert not isinstance(t.name, IDLUnresolvedIdentifier)
+ self.type = t
+
+ if self.type.isDictionary() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError("An attribute cannot be of a dictionary type",
+ [self.location])
+ if self.type.isSequence() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError("A non-cached attribute cannot be of a sequence "
+ "type", [self.location])
+ if self.type.isMozMap() and not self.getExtendedAttribute("Cached"):
+ raise WebIDLError("A non-cached attribute cannot be of a MozMap "
+ "type", [self.location])
+ if self.type.isUnion():
+ for f in self.type.unroll().flatMemberTypes:
+ if f.isDictionary():
+ raise WebIDLError("An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a dictionary "
+ "type", [self.location, f.location])
+ if f.isSequence():
+ raise WebIDLError("An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a sequence "
+ "type", [self.location, f.location])
+ if f.isMozMap():
+ raise WebIDLError("An attribute cannot be of a union "
+ "type if one of its member types (or "
+ "one of its member types's member "
+ "types, and so on) is a MozMap "
+ "type", [self.location, f.location])
+ if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"):
+ raise WebIDLError("An attribute with [PutForwards] must have an "
+ "interface type as its type", [self.location])
+
+ if not self.type.isInterface() and self.getExtendedAttribute("SameObject"):
+ raise WebIDLError("An attribute with [SameObject] must have an "
+ "interface type as its type", [self.location])
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ if ((self.getExtendedAttribute("Cached") or
+ self.getExtendedAttribute("StoreInSlot")) and
+ not self.getExtendedAttribute("Constant") and
+ not self.getExtendedAttribute("Pure")):
+ raise WebIDLError("Cached attributes and attributes stored in "
+ "slots must be constant or pure, since the "
+ "getter won't always be called.",
+ [self.location])
+ if self.getExtendedAttribute("Frozen"):
+ if (not self.type.isSequence() and not self.type.isDictionary() and
+ not self.type.isMozMap()):
+ raise WebIDLError("[Frozen] is only allowed on "
+ "sequence-valued, dictionary-valued, and "
+ "MozMap-valued attributes",
+ [self.location])
+ if not self.type.unroll().isExposedInAllOf(self.exposureSet):
+ raise WebIDLError("Attribute returns a type that is not exposed "
+ "everywhere where the attribute is exposed",
+ [self.location])
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if identifier == "SetterThrows" and self.readonly:
+ raise WebIDLError("Readonly attributes must not be flagged as "
+ "[SetterThrows]",
+ [self.location])
+ elif (((identifier == "Throws" or identifier == "GetterThrows") and
+ self.getExtendedAttribute("StoreInSlot")) or
+ (identifier == "StoreInSlot" and
+ (self.getExtendedAttribute("Throws") or
+ self.getExtendedAttribute("GetterThrows")))):
+ raise WebIDLError("Throwing things can't be [Pure] or [Constant] "
+ "or [SameObject] or [StoreInSlot]",
+ [attr.location])
+ elif identifier == "LenientThis":
+ if not attr.noArguments():
+ raise WebIDLError("[LenientThis] must take no arguments",
+ [attr.location])
+ if self.isStatic():
+ raise WebIDLError("[LenientThis] is only allowed on non-static "
+ "attributes", [attr.location, self.location])
+ if self.getExtendedAttribute("CrossOriginReadable"):
+ raise WebIDLError("[LenientThis] is not allowed in combination "
+ "with [CrossOriginReadable]",
+ [attr.location, self.location])
+ if self.getExtendedAttribute("CrossOriginWritable"):
+ raise WebIDLError("[LenientThis] is not allowed in combination "
+ "with [CrossOriginWritable]",
+ [attr.location, self.location])
+ self.lenientThis = True
+ elif identifier == "Unforgeable":
+ if self.isStatic():
+ raise WebIDLError("[Unforgeable] is only allowed on non-static "
+ "attributes", [attr.location, self.location])
+ self._unforgeable = True
+ elif identifier == "SameObject" and not self.readonly:
+ raise WebIDLError("[SameObject] only allowed on readonly attributes",
+ [attr.location, self.location])
+ elif identifier == "Constant" and not self.readonly:
+ raise WebIDLError("[Constant] only allowed on readonly attributes",
+ [attr.location, self.location])
+ elif identifier == "PutForwards":
+ if not self.readonly:
+ raise WebIDLError("[PutForwards] is only allowed on readonly "
+ "attributes", [attr.location, self.location])
+ if self.isStatic():
+ raise WebIDLError("[PutForwards] is only allowed on non-static "
+ "attributes", [attr.location, self.location])
+ if self.getExtendedAttribute("Replaceable") is not None:
+ raise WebIDLError("[PutForwards] and [Replaceable] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location])
+ if not attr.hasValue():
+ raise WebIDLError("[PutForwards] takes an identifier",
+ [attr.location, self.location])
+ elif identifier == "Replaceable":
+ if self.getExtendedAttribute("PutForwards") is not None:
+ raise WebIDLError("[PutForwards] and [Replaceable] can't both "
+ "appear on the same attribute",
+ [attr.location, self.location])
+ elif identifier == "LenientFloat":
+ if self.readonly:
+ raise WebIDLError("[LenientFloat] used on a readonly attribute",
+ [attr.location, self.location])
+ if not self.type.includesRestrictedFloat():
+ raise WebIDLError("[LenientFloat] used on an attribute with a "
+ "non-restricted-float type",
+ [attr.location, self.location])
+ elif identifier == "EnforceRange":
+ if self.readonly:
+ raise WebIDLError("[EnforceRange] used on a readonly attribute",
+ [attr.location, self.location])
+ self.enforceRange = True
+ elif identifier == "Clamp":
+ if self.readonly:
+ raise WebIDLError("[Clamp] used on a readonly attribute",
+ [attr.location, self.location])
+ self.clamp = True
+ elif identifier == "StoreInSlot":
+ if self.getExtendedAttribute("Cached"):
+ raise WebIDLError("[StoreInSlot] and [Cached] must not be "
+ "specified on the same attribute",
+ [attr.location, self.location])
+ elif identifier == "Cached":
+ if self.getExtendedAttribute("StoreInSlot"):
+ raise WebIDLError("[Cached] and [StoreInSlot] must not be "
+ "specified on the same attribute",
+ [attr.location, self.location])
+ elif (identifier == "CrossOriginReadable" or
+ identifier == "CrossOriginWritable"):
+ if not attr.noArguments() and identifier == "CrossOriginReadable":
+ raise WebIDLError("[%s] must take no arguments" % identifier,
+ [attr.location])
+ if self.isStatic():
+ raise WebIDLError("[%s] is only allowed on non-static "
+ "attributes" % identifier,
+ [attr.location, self.location])
+ if self.getExtendedAttribute("LenientThis"):
+ raise WebIDLError("[LenientThis] is not allowed in combination "
+ "with [%s]" % identifier,
+ [attr.location, self.location])
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (identifier == "Pref" or
+ identifier == "SetterThrows" or
+ identifier == "Pure" or
+ identifier == "Throws" or
+ identifier == "GetterThrows" or
+ identifier == "ChromeOnly" or
+ identifier == "SameObject" or
+ identifier == "Constant" or
+ identifier == "Func" or
+ identifier == "Frozen" or
+ identifier == "AvailableIn" or
+ identifier == "NewObject" or
+ identifier == "CheckPermissions"):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError("Unknown extended attribute %s on attribute" % identifier,
+ [attr.location])
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ self.type.resolveType(parentScope)
+ IDLObjectWithIdentifier.resolve(self, parentScope)
+
+ def addExtendedAttributes(self, attrs):
+ attrs = self.checkForStringHandlingExtendedAttributes(attrs)
+ IDLInterfaceMember.addExtendedAttributes(self, attrs)
+
+ def hasLenientThis(self):
+ return self.lenientThis
+
+ def isUnforgeable(self):
+ return self._unforgeable
+
+ def _getDependentObjects(self):
+ return set([self.type])
+
+class IDLArgument(IDLObjectWithIdentifier):
+ def __init__(self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False):
+ IDLObjectWithIdentifier.__init__(self, location, None, identifier)
+
+ assert isinstance(type, IDLType)
+ self.type = type
+
+ self.optional = optional
+ self.defaultValue = defaultValue
+ self.variadic = variadic
+ self.dictionaryMember = dictionaryMember
+ self._isComplete = False
+ self.enforceRange = False
+ self.clamp = False
+ self._allowTreatNonCallableAsNull = False
+
+ assert not variadic or optional
+
+ def addExtendedAttributes(self, attrs):
+ attrs = self.checkForStringHandlingExtendedAttributes(
+ attrs,
+ isDictionaryMember=self.dictionaryMember,
+ isOptional=self.optional)
+ for attribute in attrs:
+ identifier = attribute.identifier()
+ if identifier == "Clamp":
+ if not attribute.noArguments():
+ raise WebIDLError("[Clamp] must take no arguments",
+ [attribute.location])
+ if self.enforceRange:
+ raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+ [self.location]);
+ self.clamp = True
+ elif identifier == "EnforceRange":
+ if not attribute.noArguments():
+ raise WebIDLError("[EnforceRange] must take no arguments",
+ [attribute.location])
+ if self.clamp:
+ raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+ [self.location]);
+ self.enforceRange = True
+ elif identifier == "TreatNonCallableAsNull":
+ self._allowTreatNonCallableAsNull = True
+ else:
+ raise WebIDLError("Unhandled extended attribute on an argument",
+ [attribute.location])
+
+ def isComplete(self):
+ return self._isComplete
+
+ def complete(self, scope):
+ if self._isComplete:
+ return
+
+ self._isComplete = True
+
+ if not self.type.isComplete():
+ type = self.type.complete(scope)
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ self.type = type
+
+ if ((self.type.isDictionary() or
+ self.type.isUnion() and self.type.unroll().hasDictionaryType) and
+ self.optional and not self.defaultValue):
+ # Default optional dictionaries to null, for simplicity,
+ # so the codegen doesn't have to special-case this.
+ self.defaultValue = IDLNullValue(self.location)
+ elif self.type.isAny():
+ assert (self.defaultValue is None or
+ isinstance(self.defaultValue, IDLNullValue))
+ # optional 'any' values always have a default value
+ if self.optional and not self.defaultValue and not self.variadic:
+ # Set the default value to undefined, for simplicity, so the
+ # codegen doesn't have to special-case this.
+ self.defaultValue = IDLUndefinedValue(self.location)
+
+ # Now do the coercing thing; this needs to happen after the
+ # above creation of a default value.
+ if self.defaultValue:
+ self.defaultValue = self.defaultValue.coerceToType(self.type,
+ self.location)
+ assert self.defaultValue
+
+ def allowTreatNonCallableAsNull(self):
+ return self._allowTreatNonCallableAsNull
+
+ def _getDependentObjects(self):
+ deps = set([self.type])
+ if self.defaultValue:
+ deps.add(self.defaultValue)
+ return deps
+
+class IDLCallbackType(IDLType, IDLObjectWithScope):
+ def __init__(self, location, parentScope, identifier, returnType, arguments):
+ assert isinstance(returnType, IDLType)
+
+ IDLType.__init__(self, location, identifier.name)
+
+ self._returnType = returnType
+ # Clone the list
+ self._arguments = list(arguments)
+
+ IDLObjectWithScope.__init__(self, location, parentScope, identifier)
+
+ for (returnType, arguments) in self.signatures():
+ for argument in arguments:
+ argument.resolve(self)
+
+ self._treatNonCallableAsNull = False
+ self._treatNonObjectAsNull = False
+
+ def module(self):
+ return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding'
+
+ def isCallback(self):
+ return True
+
+ def signatures(self):
+ return [(self._returnType, self._arguments)]
+
+ def tag(self):
+ return IDLType.Tags.callback
+
+ def finish(self, scope):
+ if not self._returnType.isComplete():
+ type = self._returnType.complete(scope)
+
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ self._returnType = type
+
+ for argument in self._arguments:
+ if argument.type.isComplete():
+ continue
+
+ type = argument.type.complete(scope)
+
+ assert not isinstance(type, IDLUnresolvedType)
+ assert not isinstance(type, IDLTypedefType)
+ assert not isinstance(type.name, IDLUnresolvedIdentifier)
+ argument.type = type
+
+ def validate(self):
+ pass
+
+ def isDistinguishableFrom(self, other):
+ if other.isUnion():
+ # Just forward to the union; it'll deal
+ return other.isDistinguishableFrom(self)
+ return (other.isPrimitive() or other.isString() or other.isEnum() or
+ other.isNonCallbackInterface() or other.isDate())
+
+ def addExtendedAttributes(self, attrs):
+ unhandledAttrs = []
+ for attr in attrs:
+ if attr.identifier() == "TreatNonCallableAsNull":
+ self._treatNonCallableAsNull = True
+ elif attr.identifier() == "TreatNonObjectAsNull":
+ self._treatNonObjectAsNull = True
+ else:
+ unhandledAttrs.append(attr)
+ if self._treatNonCallableAsNull and self._treatNonObjectAsNull:
+ raise WebIDLError("Cannot specify both [TreatNonCallableAsNull] "
+ "and [TreatNonObjectAsNull]", [self.location])
+ if len(unhandledAttrs) != 0:
+ IDLType.addExtendedAttributes(self, unhandledAttrs)
+
+ def _getDependentObjects(self):
+ return set([self._returnType] + self._arguments)
+
+class IDLMethodOverload:
+ """
+ A class that represents a single overload of a WebIDL method. This is not
+ quite the same as an element of the "effective overload set" in the spec,
+ because separate IDLMethodOverloads are not created based on arguments being
+ optional. Rather, when multiple methods have the same name, there is an
+ IDLMethodOverload for each one, all hanging off an IDLMethod representing
+ the full set of overloads.
+ """
+ def __init__(self, returnType, arguments, location):
+ self.returnType = returnType
+ # Clone the list of arguments, just in case
+ self.arguments = list(arguments)
+ self.location = location
+
+ def _getDependentObjects(self):
+ deps = set(self.arguments)
+ deps.add(self.returnType)
+ return deps
+
+class IDLMethod(IDLInterfaceMember, IDLScope):
+
+ Special = enum(
+ 'Getter',
+ 'Setter',
+ 'Creator',
+ 'Deleter',
+ 'LegacyCaller',
+ base=IDLInterfaceMember.Special
+ )
+
+ TypeSuffixModifier = enum(
+ 'None',
+ 'QMark',
+ 'Brackets'
+ )
+
+ NamedOrIndexed = enum(
+ 'Neither',
+ 'Named',
+ 'Indexed'
+ )
+
+ def __init__(self, location, identifier, returnType, arguments,
+ static=False, getter=False, setter=False, creator=False,
+ deleter=False, specialType=NamedOrIndexed.Neither,
+ legacycaller=False, stringifier=False, jsonifier=False):
+ # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up.
+ IDLInterfaceMember.__init__(self, location, identifier,
+ IDLInterfaceMember.Tags.Method)
+
+ self._hasOverloads = False
+
+ assert isinstance(returnType, IDLType)
+
+ # self._overloads is a list of IDLMethodOverloads
+ self._overloads = [IDLMethodOverload(returnType, arguments, location)]
+
+ assert isinstance(static, bool)
+ self._static = static
+ assert isinstance(getter, bool)
+ self._getter = getter
+ assert isinstance(setter, bool)
+ self._setter = setter
+ assert isinstance(creator, bool)
+ self._creator = creator
+ assert isinstance(deleter, bool)
+ self._deleter = deleter
+ assert isinstance(legacycaller, bool)
+ self._legacycaller = legacycaller
+ assert isinstance(stringifier, bool)
+ self._stringifier = stringifier
+ assert isinstance(jsonifier, bool)
+ self._jsonifier = jsonifier
+ self._specialType = specialType
+ self._unforgeable = False
+
+ if static and identifier.name == "prototype":
+ raise WebIDLError("The identifier of a static operation must not be 'prototype'",
+ [location])
+
+ self.assertSignatureConstraints()
+
+ def __str__(self):
+ return "Method '%s'" % self.identifier
+
+ def assertSignatureConstraints(self):
+ if self._getter or self._deleter:
+ assert len(self._overloads) == 1
+ overload = self._overloads[0]
+ arguments = overload.arguments
+ assert len(arguments) == 1
+ assert arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or \
+ arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]
+ assert not arguments[0].optional and not arguments[0].variadic
+ assert not self._getter or not overload.returnType.isVoid()
+
+ if self._setter or self._creator:
+ assert len(self._overloads) == 1
+ arguments = self._overloads[0].arguments
+ assert len(arguments) == 2
+ assert arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or \
+ arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]
+ assert not arguments[0].optional and not arguments[0].variadic
+ assert not arguments[1].optional and not arguments[1].variadic
+
+ if self._stringifier:
+ assert len(self._overloads) == 1
+ overload = self._overloads[0]
+ assert len(overload.arguments) == 0
+ assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring]
+
+ if self._jsonifier:
+ assert len(self._overloads) == 1
+ overload = self._overloads[0]
+ assert len(overload.arguments) == 0
+ assert overload.returnType == BuiltinTypes[IDLBuiltinType.Types.object]
+
+ def isStatic(self):
+ return self._static
+
+ def isGetter(self):
+ return self._getter
+
+ def isSetter(self):
+ return self._setter
+
+ def isCreator(self):
+ return self._creator
+
+ def isDeleter(self):
+ return self._deleter
+
+ def isNamed(self):
+ assert self._specialType == IDLMethod.NamedOrIndexed.Named or \
+ self._specialType == IDLMethod.NamedOrIndexed.Indexed
+ return self._specialType == IDLMethod.NamedOrIndexed.Named
+
+ def isIndexed(self):
+ assert self._specialType == IDLMethod.NamedOrIndexed.Named or \
+ self._specialType == IDLMethod.NamedOrIndexed.Indexed
+ return self._specialType == IDLMethod.NamedOrIndexed.Indexed
+
+ def isLegacycaller(self):
+ return self._legacycaller
+
+ def isStringifier(self):
+ return self._stringifier
+
+ def isJsonifier(self):
+ return self._jsonifier
+
+ def hasOverloads(self):
+ return self._hasOverloads
+
+ def isIdentifierLess(self):
+ return self.identifier.name[:2] == "__" and self.identifier.name != "__noSuchMethod__"
+
+ def resolve(self, parentScope):
+ assert isinstance(parentScope, IDLScope)
+ IDLObjectWithIdentifier.resolve(self, parentScope)
+ IDLScope.__init__(self, self.location, parentScope, self.identifier)
+ for (returnType, arguments) in self.signatures():
+ for argument in arguments:
+ argument.resolve(self)
+
+ def addOverload(self, method):
+ assert len(method._overloads) == 1
+
+ if self._extendedAttrDict != method ._extendedAttrDict:
+ raise WebIDLError("Extended attributes differ on different "
+ "overloads of %s" % method.identifier,
+ [self.location, method.location])
+
+ self._overloads.extend(method._overloads)
+
+ self._hasOverloads = True
+
+ if self.isStatic() != method.isStatic():
+ raise WebIDLError("Overloaded identifier %s appears with different values of the 'static' attribute" % method.identifier,
+ [method.location])
+
+ if self.isLegacycaller() != method.isLegacycaller():
+ raise WebIDLError("Overloaded identifier %s appears with different values of the 'legacycaller' attribute" % method.identifier,
+ [method.location])
+
+ # Can't overload special things!
+ assert not self.isGetter()
+ assert not method.isGetter()
+ assert not self.isSetter()
+ assert not method.isSetter()
+ assert not self.isCreator()
+ assert not method.isCreator()
+ assert not self.isDeleter()
+ assert not method.isDeleter()
+ assert not self.isStringifier()
+ assert not method.isStringifier()
+ assert not self.isJsonifier()
+ assert not method.isJsonifier()
+
+ return self
+
+ def signatures(self):
+ return [(overload.returnType, overload.arguments) for overload in
+ self._overloads]
+
+ def finish(self, scope):
+ IDLInterfaceMember.finish(self, scope)
+
+ overloadWithPromiseReturnType = None
+ overloadWithoutPromiseReturnType = None
+ for overload in self._overloads:
+ variadicArgument = None
+
+ arguments = overload.arguments
+ for (idx, argument) in enumerate(arguments):
+ if not argument.isComplete():
+ argument.complete(scope)
+ assert argument.type.isComplete()
+
+ if (argument.type.isDictionary() or
+ (argument.type.isUnion() and
+ argument.type.unroll().hasDictionaryType)):
+ # Dictionaries and unions containing dictionaries at the
+ # end of the list or followed by optional arguments must be
+ # optional.
+ if (not argument.optional and
+ all(arg.optional for arg in arguments[idx+1:])):
+ raise WebIDLError("Dictionary argument or union "
+ "argument containing a dictionary "
+ "not followed by a required argument "
+ "must be optional",
+ [argument.location])
+
+ # An argument cannot be a Nullable Dictionary
+ if argument.type.nullable():
+ raise WebIDLError("An argument cannot be a nullable "
+ "dictionary or nullable union "
+ "containing a dictionary",
+ [argument.location])
+
+ # Only the last argument can be variadic
+ if variadicArgument:
+ raise WebIDLError("Variadic argument is not last argument",
+ [variadicArgument.location])
+ if argument.variadic:
+ variadicArgument = argument
+
+ returnType = overload.returnType
+ if not returnType.isComplete():
+ returnType = returnType.complete(scope)
+ assert not isinstance(returnType, IDLUnresolvedType)
+ assert not isinstance(returnType, IDLTypedefType)
+ assert not isinstance(returnType.name, IDLUnresolvedIdentifier)
+ overload.returnType = returnType
+
+ if returnType.isPromise():
+ overloadWithPromiseReturnType = overload
+ else:
+ overloadWithoutPromiseReturnType = overload
+
+ # Make sure either all our overloads return Promises or none do
+ if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType:
+ raise WebIDLError("We have overloads with both Promise and "
+ "non-Promise return types",
+ [overloadWithPromiseReturnType.location,
+ overloadWithoutPromiseReturnType.location])
+
+ if overloadWithPromiseReturnType and self._legacycaller:
+ raise WebIDLError("May not have a Promise return type for a "
+ "legacycaller.",
+ [overloadWithPromiseReturnType.location])
+
+ # Now compute various information that will be used by the
+ # WebIDL overload resolution algorithm.
+ self.maxArgCount = max(len(s[1]) for s in self.signatures())
+ self.allowedArgCounts = [ i for i in range(self.maxArgCount+1)
+ if len(self.signaturesForArgCount(i)) != 0 ]
+
+ def validate(self):
+ IDLInterfaceMember.validate(self)
+
+ # Make sure our overloads are properly distinguishable and don't have
+ # different argument types before the distinguishing args.
+ for argCount in self.allowedArgCounts:
+ possibleOverloads = self.overloadsForArgCount(argCount)
+ if len(possibleOverloads) == 1:
+ continue
+ distinguishingIndex = self.distinguishingIndexForArgCount(argCount)
+ for idx in range(distinguishingIndex):
+ firstSigType = possibleOverloads[0].arguments[idx].type
+ for overload in possibleOverloads[1:]:
+ if overload.arguments[idx].type != firstSigType:
+ raise WebIDLError(
+ "Signatures for method '%s' with %d arguments have "
+ "different types of arguments at index %d, which "
+ "is before distinguishing index %d" %
+ (self.identifier.name, argCount, idx,
+ distinguishingIndex),
+ [self.location, overload.location])
+
+ for overload in self._overloads:
+ if not overload.returnType.unroll().isExposedInAllOf(self.exposureSet):
+ raise WebIDLError("Overload returns a type that is not exposed "
+ "everywhere where the method is exposed",
+ [overload.location])
+
+ def overloadsForArgCount(self, argc):
+ return [overload for overload in self._overloads if
+ len(overload.arguments) == argc or
+ (len(overload.arguments) > argc and
+ all(arg.optional for arg in overload.arguments[argc:])) or
+ (len(overload.arguments) < argc and
+ len(overload.arguments) > 0 and
+ overload.arguments[-1].variadic)]
+
+ def signaturesForArgCount(self, argc):
+ return [(overload.returnType, overload.arguments) for overload
+ in self.overloadsForArgCount(argc)]
+
+ def locationsForArgCount(self, argc):
+ return [overload.location for overload in self.overloadsForArgCount(argc)]
+
+ def distinguishingIndexForArgCount(self, argc):
+ def isValidDistinguishingIndex(idx, signatures):
+ for (firstSigIndex, (firstRetval, firstArgs)) in enumerate(signatures[:-1]):
+ for (secondRetval, secondArgs) in signatures[firstSigIndex+1:]:
+ if idx < len(firstArgs):
+ firstType = firstArgs[idx].type
+ else:
+ assert(firstArgs[-1].variadic)
+ firstType = firstArgs[-1].type
+ if idx < len(secondArgs):
+ secondType = secondArgs[idx].type
+ else:
+ assert(secondArgs[-1].variadic)
+ secondType = secondArgs[-1].type
+ if not firstType.isDistinguishableFrom(secondType):
+ return False
+ return True
+ signatures = self.signaturesForArgCount(argc)
+ for idx in range(argc):
+ if isValidDistinguishingIndex(idx, signatures):
+ return idx
+ # No valid distinguishing index. Time to throw
+ locations = self.locationsForArgCount(argc)
+ raise WebIDLError("Signatures with %d arguments for method '%s' are not "
+ "distinguishable" % (argc, self.identifier.name),
+ locations)
+
+ def handleExtendedAttribute(self, attr):
+ identifier = attr.identifier()
+ if identifier == "GetterThrows":
+ raise WebIDLError("Methods must not be flagged as "
+ "[GetterThrows]",
+ [attr.location, self.location])
+ elif identifier == "SetterThrows":
+ raise WebIDLError("Methods must not be flagged as "
+ "[SetterThrows]",
+ [attr.location, self.location])
+ elif identifier == "Unforgeable":
+ if self.isStatic():
+ raise WebIDLError("[Unforgeable] is only allowed on non-static "
+ "methods", [attr.location, self.location])
+ self._unforgeable = True
+ elif identifier == "SameObject":
+ raise WebIDLError("Methods must not be flagged as [SameObject]",
+ [attr.location, self.location]);
+ elif identifier == "Constant":
+ raise WebIDLError("Methods must not be flagged as [Constant]",
+ [attr.location, self.location]);
+ elif identifier == "PutForwards":
+ raise WebIDLError("Only attributes support [PutForwards]",
+ [attr.location, self.location])
+ elif identifier == "LenientFloat":
+ # This is called before we've done overload resolution
+ assert len(self.signatures()) == 1
+ sig = self.signatures()[0]
+ if not sig[0].isVoid():
+ raise WebIDLError("[LenientFloat] used on a non-void method",
+ [attr.location, self.location])
+ if not any(arg.type.includesRestrictedFloat() for arg in sig[1]):
+ raise WebIDLError("[LenientFloat] used on an operation with no "
+ "restricted float type arguments",
+ [attr.location, self.location])
+ elif identifier == "Exposed":
+ convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames)
+ elif (identifier == "Pure" or
+ identifier == "CrossOriginCallable" or
+ identifier == "WebGLHandlesContextLoss"):
+ # Known no-argument attributes.
+ if not attr.noArguments():
+ raise WebIDLError("[%s] must take no arguments" % identifier,
+ [attr.location])
+ elif (identifier == "Throws" or
+ identifier == "NewObject" or
+ identifier == "ChromeOnly" or
+ identifier == "Pref" or
+ identifier == "Func" or
+ identifier == "AvailableIn" or
+ identifier == "CheckPermissions"):
+ # Known attributes that we don't need to do anything with here
+ pass
+ else:
+ raise WebIDLError("Unknown extended attribute %s on method" % identifier,
+ [attr.location])
+ IDLInterfaceMember.handleExtendedAttribute(self, attr)
+
+ def returnsPromise(self):
+ return self._overloads[0].returnType.isPromise()
+
+ def isUnforgeable(self):
+ return self._unforgeable
+
+ def _getDependentObjects(self):
+ deps = set()
+ for overload in self._overloads:
+ deps.union(overload._getDependentObjects())
+ return deps
+
+class IDLImplementsStatement(IDLObject):
+ def __init__(self, location, implementor, implementee):
+ IDLObject.__init__(self, location)
+ self.implementor = implementor;
+ self.implementee = implementee
+
+ def finish(self, scope):
+ assert(isinstance(self.implementor, IDLIdentifierPlaceholder))
+ assert(isinstance(self.implementee, IDLIdentifierPlaceholder))
+ implementor = self.implementor.finish(scope)
+ implementee = self.implementee.finish(scope)
+ # NOTE: we depend on not setting self.implementor and
+ # self.implementee here to keep track of the original
+ # locations.
+ if not isinstance(implementor, IDLInterface):
+ raise WebIDLError("Left-hand side of 'implements' is not an "
+ "interface",
+ [self.implementor.location])
+ if implementor.isCallback():
+ raise WebIDLError("Left-hand side of 'implements' is a callback "
+ "interface",
+ [self.implementor.location])
+ if not isinstance(implementee, IDLInterface):
+ raise WebIDLError("Right-hand side of 'implements' is not an "
+ "interface",
+ [self.implementee.location])
+ if implementee.isCallback():
+ raise WebIDLError("Right-hand side of 'implements' is a callback "
+ "interface",
+ [self.implementee.location])
+ implementor.addImplementedInterface(implementee)
+
+ def validate(self):
+ pass
+
+ def addExtendedAttributes(self, attrs):
+ assert len(attrs) == 0
+
+class IDLExtendedAttribute(IDLObject):
+ """
+ A class to represent IDL extended attributes so we can give them locations
+ """
+ def __init__(self, location, tuple):
+ IDLObject.__init__(self, location)
+ self._tuple = tuple
+
+ def identifier(self):
+ return self._tuple[0]
+
+ def noArguments(self):
+ return len(self._tuple) == 1
+
+ def hasValue(self):
+ return len(self._tuple) >= 2 and isinstance(self._tuple[1], str)
+
+ def value(self):
+ assert(self.hasValue())
+ return self._tuple[1]
+
+ def hasArgs(self):
+ return (len(self._tuple) == 2 and isinstance(self._tuple[1], list) or
+ len(self._tuple) == 3)
+
+ def args(self):
+ assert(self.hasArgs())
+ # Our args are our last element
+ return self._tuple[-1]
+
+ def listValue(self):
+ """
+ Backdoor for storing random data in _extendedAttrDict
+ """
+ return list(self._tuple)[1:]
+
+# Parser
+
+class Tokenizer(object):
+ tokens = [
+ "INTEGER",
+ "FLOATLITERAL",
+ "IDENTIFIER",
+ "STRING",
+ "WHITESPACE",
+ "OTHER"
+ ]
+
+ def t_FLOATLITERAL(self, t):
+ r'(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN'
+ t.value = float(t.value)
+ return t
+
+ def t_INTEGER(self, t):
+ r'-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)'
+ try:
+ # Can't use int(), because that doesn't handle octal properly.
+ t.value = parseInt(t.value)
+ except:
+ raise WebIDLError("Invalid integer literal",
+ [Location(lexer=self.lexer,
+ lineno=self.lexer.lineno,
+ lexpos=self.lexer.lexpos,
+ filename=self._filename)])
+ return t
+
+ def t_IDENTIFIER(self, t):
+ r'[A-Z_a-z][0-9A-Z_a-z-]*'
+ t.type = self.keywords.get(t.value, 'IDENTIFIER')
+ return t
+
+ def t_STRING(self, t):
+ r'"[^"]*"'
+ t.value = t.value[1:-1]
+ return t
+
+ def t_WHITESPACE(self, t):
+ r'[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+'
+ pass
+
+ def t_ELLIPSIS(self, t):
+ r'\.\.\.'
+ t.type = self.keywords.get(t.value)
+ return t
+
+ def t_OTHER(self, t):
+ r'[^\t\n\r 0-9A-Z_a-z]'
+ t.type = self.keywords.get(t.value, 'OTHER')
+ return t
+
+ keywords = {
+ "module": "MODULE",
+ "interface": "INTERFACE",
+ "partial": "PARTIAL",
+ "dictionary": "DICTIONARY",
+ "exception": "EXCEPTION",
+ "enum": "ENUM",
+ "callback": "CALLBACK",
+ "typedef": "TYPEDEF",
+ "implements": "IMPLEMENTS",
+ "const": "CONST",
+ "null": "NULL",
+ "true": "TRUE",
+ "false": "FALSE",
+ "serializer": "SERIALIZER",
+ "stringifier": "STRINGIFIER",
+ "jsonifier": "JSONIFIER",
+ "unrestricted": "UNRESTRICTED",
+ "attribute": "ATTRIBUTE",
+ "readonly": "READONLY",
+ "inherit": "INHERIT",
+ "static": "STATIC",
+ "getter": "GETTER",
+ "setter": "SETTER",
+ "creator": "CREATOR",
+ "deleter": "DELETER",
+ "legacycaller": "LEGACYCALLER",
+ "optional": "OPTIONAL",
+ "...": "ELLIPSIS",
+ "::": "SCOPE",
+ "Date": "DATE",
+ "DOMString": "DOMSTRING",
+ "ByteString": "BYTESTRING",
+ "ScalarValueString": "SCALARVALUESTRING",
+ "any": "ANY",
+ "boolean": "BOOLEAN",
+ "byte": "BYTE",
+ "double": "DOUBLE",
+ "float": "FLOAT",
+ "long": "LONG",
+ "object": "OBJECT",
+ "octet": "OCTET",
+ "optional": "OPTIONAL",
+ "Promise": "PROMISE",
+ "sequence": "SEQUENCE",
+ "MozMap": "MOZMAP",
+ "short": "SHORT",
+ "unsigned": "UNSIGNED",
+ "void": "VOID",
+ ":": "COLON",
+ ";": "SEMICOLON",
+ "{": "LBRACE",
+ "}": "RBRACE",
+ "(": "LPAREN",
+ ")": "RPAREN",
+ "[": "LBRACKET",
+ "]": "RBRACKET",
+ "?": "QUESTIONMARK",
+ ",": "COMMA",
+ "=": "EQUALS",
+ "<": "LT",
+ ">": "GT",
+ "ArrayBuffer": "ARRAYBUFFER",
+ "or": "OR"
+ }
+
+ tokens.extend(keywords.values())
+
+ def t_error(self, t):
+ raise WebIDLError("Unrecognized Input",
+ [Location(lexer=self.lexer,
+ lineno=self.lexer.lineno,
+ lexpos=self.lexer.lexpos,
+ filename = self.filename)])
+
+ def __init__(self, outputdir, lexer=None):
+ if lexer:
+ self.lexer = lexer
+ else:
+ self.lexer = lex.lex(object=self,
+ outputdir=outputdir,
+ lextab='webidllex',
+ reflags=re.DOTALL)
+
+class SqueakyCleanLogger(object):
+ errorWhitelist = [
+ # Web IDL defines the WHITESPACE token, but doesn't actually
+ # use it ... so far.
+ "Token 'WHITESPACE' defined, but not used",
+ # And that means we have an unused token
+ "There is 1 unused token",
+ # Web IDL defines a OtherOrComma rule that's only used in
+ # ExtendedAttributeInner, which we don't use yet.
+ "Rule 'OtherOrComma' defined, but not used",
+ # And an unused rule
+ "There is 1 unused rule",
+ # And the OtherOrComma grammar symbol is unreachable.
+ "Symbol 'OtherOrComma' is unreachable",
+ # Which means the Other symbol is unreachable.
+ "Symbol 'Other' is unreachable",
+ ]
+ def __init__(self):
+ self.errors = []
+ def debug(self, msg, *args, **kwargs):
+ pass
+ info = debug
+ def warning(self, msg, *args, **kwargs):
+ if msg == "%s:%d: Rule '%s' defined, but not used":
+ # Munge things so we don't have to hardcode filenames and
+ # line numbers in our whitelist.
+ whitelistmsg = "Rule '%s' defined, but not used"
+ whitelistargs = args[2:]
+ else:
+ whitelistmsg = msg
+ whitelistargs = args
+ if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist:
+ self.errors.append(msg % args)
+ error = warning
+
+ def reportGrammarErrors(self):
+ if self.errors:
+ raise WebIDLError("\n".join(self.errors), [])
+
+class Parser(Tokenizer):
+ def getLocation(self, p, i):
+ return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename)
+
+ def globalScope(self):
+ return self._globalScope
+
+ # The p_Foo functions here must match the WebIDL spec's grammar.
+ # It's acceptable to split things at '|' boundaries.
+ def p_Definitions(self, p):
+ """
+ Definitions : ExtendedAttributeList Definition Definitions
+ """
+ if p[2]:
+ p[0] = [p[2]]
+ p[2].addExtendedAttributes(p[1])
+ else:
+ assert not p[1]
+ p[0] = []
+
+ p[0].extend(p[3])
+
+ def p_DefinitionsEmpty(self, p):
+ """
+ Definitions :
+ """
+ p[0] = []
+
+ def p_Definition(self, p):
+ """
+ Definition : CallbackOrInterface
+ | PartialInterface
+ | Dictionary
+ | Exception
+ | Enum
+ | Typedef
+ | ImplementsStatement
+ """
+ p[0] = p[1]
+ assert p[1] # We might not have implemented something ...
+
+ def p_CallbackOrInterfaceCallback(self, p):
+ """
+ CallbackOrInterface : CALLBACK CallbackRestOrInterface
+ """
+ if p[2].isInterface():
+ assert isinstance(p[2], IDLInterface)
+ p[2].setCallback(True)
+
+ p[0] = p[2]
+
+ def p_CallbackOrInterfaceInterface(self, p):
+ """
+ CallbackOrInterface : Interface
+ """
+ p[0] = p[1]
+
+ def p_CallbackRestOrInterface(self, p):
+ """
+ CallbackRestOrInterface : CallbackRest
+ | Interface
+ """
+ assert p[1]
+ p[0] = p[1]
+
+ def p_Interface(self, p):
+ """
+ Interface : INTERFACE IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[5]
+ parent = p[3]
+
+ try:
+ existingObj = self.globalScope()._lookupIdentifier(identifier)
+ if existingObj:
+ p[0] = existingObj
+ if not isinstance(p[0], IDLInterface):
+ raise WebIDLError("Interface has the same name as "
+ "non-interface object",
+ [location, p[0].location])
+ p[0].setNonPartial(location, parent, members)
+ return
+ except Exception, ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ p[0] = IDLInterface(location, self.globalScope(), identifier, parent,
+ members, isKnownNonPartial=True)
+
+ def p_InterfaceForwardDecl(self, p):
+ """
+ Interface : INTERFACE IDENTIFIER SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+
+ try:
+ if self.globalScope()._lookupIdentifier(identifier):
+ p[0] = self.globalScope()._lookupIdentifier(identifier)
+ if not isinstance(p[0], IDLExternalInterface):
+ raise WebIDLError("Name collision between external "
+ "interface declaration for identifier "
+ "%s and %s" % (identifier.name, p[0]),
+ [location, p[0].location])
+ return
+ except Exception, ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ p[0] = IDLExternalInterface(location, self.globalScope(), identifier)
+
+ def p_PartialInterface(self, p):
+ """
+ PartialInterface : PARTIAL INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 2)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
+ members = p[5]
+
+ nonPartialInterface = None
+ try:
+ nonPartialInterface = self.globalScope()._lookupIdentifier(identifier)
+ if nonPartialInterface:
+ if not isinstance(nonPartialInterface, IDLInterface):
+ raise WebIDLError("Partial interface has the same name as "
+ "non-interface object",
+ [location, nonPartialInterface.location])
+ except Exception, ex:
+ if isinstance(ex, WebIDLError):
+ raise ex
+ pass
+
+ if not nonPartialInterface:
+ nonPartialInterface = IDLInterface(location, self.globalScope(),
+ identifier, None,
+ [], isKnownNonPartial=False)
+ partialInterface = IDLPartialInterface(location, identifier, members,
+ nonPartialInterface)
+ p[0] = partialInterface
+
+ def p_Inheritance(self, p):
+ """
+ Inheritance : COLON ScopedName
+ """
+ p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2])
+
+ def p_InheritanceEmpty(self, p):
+ """
+ Inheritance :
+ """
+ pass
+
+ def p_InterfaceMembers(self, p):
+ """
+ InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers
+ """
+ p[0] = [p[2]] if p[2] else []
+
+ assert not p[1] or p[2]
+ p[2].addExtendedAttributes(p[1])
+
+ p[0].extend(p[3])
+
+ def p_InterfaceMembersEmpty(self, p):
+ """
+ InterfaceMembers :
+ """
+ p[0] = []
+
+ def p_InterfaceMember(self, p):
+ """
+ InterfaceMember : Const
+ | AttributeOrOperation
+ """
+ p[0] = p[1]
+
+ def p_Dictionary(self, p):
+ """
+ Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ members = p[5]
+ p[0] = IDLDictionary(location, self.globalScope(), identifier, p[3], members)
+
+ def p_DictionaryMembers(self, p):
+ """
+ DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers
+ |
+ """
+ if len(p) == 1:
+ # We're at the end of the list
+ p[0] = []
+ return
+ # Add our extended attributes
+ p[2].addExtendedAttributes(p[1])
+ p[0] = [p[2]]
+ p[0].extend(p[3])
+
+ def p_DictionaryMember(self, p):
+ """
+ DictionaryMember : Type IDENTIFIER Default SEMICOLON
+ """
+ # These quack a lot like optional arguments, so just treat them that way.
+ t = p[1]
+ assert isinstance(t, IDLType)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+ defaultValue = p[3]
+
+ p[0] = IDLArgument(self.getLocation(p, 2), identifier, t, optional=True,
+ defaultValue=defaultValue, variadic=False,
+ dictionaryMember=True)
+
+ def p_Default(self, p):
+ """
+ Default : EQUALS DefaultValue
+ |
+ """
+ if len(p) > 1:
+ p[0] = p[2]
+ else:
+ p[0] = None
+
+ def p_DefaultValue(self, p):
+ """
+ DefaultValue : ConstValue
+ | LBRACKET RBRACKET
+ """
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ assert len(p) == 3 # Must be []
+ p[0] = IDLEmptySequenceValue(self.getLocation(p, 1))
+
+ def p_Exception(self, p):
+ """
+ Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON
+ """
+ pass
+
+ def p_Enum(self, p):
+ """
+ Enum : ENUM IDENTIFIER LBRACE EnumValueList RBRACE SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+
+ values = p[4]
+ assert values
+ p[0] = IDLEnum(location, self.globalScope(), identifier, values)
+
+ def p_EnumValueList(self, p):
+ """
+ EnumValueList : STRING EnumValueListComma
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_EnumValueListComma(self, p):
+ """
+ EnumValueListComma : COMMA EnumValueListString
+ """
+ p[0] = p[2]
+
+ def p_EnumValueListCommaEmpty(self, p):
+ """
+ EnumValueListComma :
+ """
+ p[0] = []
+
+ def p_EnumValueListString(self, p):
+ """
+ EnumValueListString : STRING EnumValueListComma
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_EnumValueListStringEmpty(self, p):
+ """
+ EnumValueListString :
+ """
+ p[0] = []
+
+ def p_CallbackRest(self, p):
+ """
+ CallbackRest : IDENTIFIER EQUALS ReturnType LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+ p[0] = IDLCallbackType(self.getLocation(p, 1), self.globalScope(),
+ identifier, p[3], p[5])
+
+ def p_ExceptionMembers(self, p):
+ """
+ ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers
+ |
+ """
+ pass
+
+ def p_Typedef(self, p):
+ """
+ Typedef : TYPEDEF Type IDENTIFIER SEMICOLON
+ """
+ typedef = IDLTypedefType(self.getLocation(p, 1), p[2], p[3])
+ typedef.resolve(self.globalScope())
+ p[0] = typedef
+
+ def p_ImplementsStatement(self, p):
+ """
+ ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON
+ """
+ assert(p[2] == "implements")
+ implementor = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1])
+ implementee = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3])
+ p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor,
+ implementee)
+
+ def p_Const(self, p):
+ """
+ Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON
+ """
+ location = self.getLocation(p, 1)
+ type = p[2]
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
+ value = p[5]
+ p[0] = IDLConst(location, identifier, type, value)
+
+ def p_ConstValueBoolean(self, p):
+ """
+ ConstValue : BooleanLiteral
+ """
+ location = self.getLocation(p, 1)
+ booleanType = BuiltinTypes[IDLBuiltinType.Types.boolean]
+ p[0] = IDLValue(location, booleanType, p[1])
+
+ def p_ConstValueInteger(self, p):
+ """
+ ConstValue : INTEGER
+ """
+ location = self.getLocation(p, 1)
+
+ # We don't know ahead of time what type the integer literal is.
+ # Determine the smallest type it could possibly fit in and use that.
+ integerType = matchIntegerValueToType(p[1])
+ if integerType == None:
+ raise WebIDLError("Integer literal out of range", [location])
+
+ p[0] = IDLValue(location, integerType, p[1])
+
+ def p_ConstValueFloat(self, p):
+ """
+ ConstValue : FLOATLITERAL
+ """
+ location = self.getLocation(p, 1)
+ p[0] = IDLValue(location, BuiltinTypes[IDLBuiltinType.Types.unrestricted_float], p[1])
+
+ def p_ConstValueString(self, p):
+ """
+ ConstValue : STRING
+ """
+ location = self.getLocation(p, 1)
+ stringType = BuiltinTypes[IDLBuiltinType.Types.domstring]
+ p[0] = IDLValue(location, stringType, p[1])
+
+ def p_ConstValueNull(self, p):
+ """
+ ConstValue : NULL
+ """
+ p[0] = IDLNullValue(self.getLocation(p, 1))
+
+ def p_BooleanLiteralTrue(self, p):
+ """
+ BooleanLiteral : TRUE
+ """
+ p[0] = True
+
+ def p_BooleanLiteralFalse(self, p):
+ """
+ BooleanLiteral : FALSE
+ """
+ p[0] = False
+
+ def p_AttributeOrOperation(self, p):
+ """
+ AttributeOrOperation : Attribute
+ | Operation
+ """
+ p[0] = p[1]
+
+ def p_AttributeWithQualifier(self, p):
+ """
+ Attribute : Qualifier AttributeRest
+ """
+ static = IDLInterfaceMember.Special.Static in p[1]
+ stringifier = IDLInterfaceMember.Special.Stringifier in p[1]
+ (location, identifier, type, readonly) = p[2]
+ p[0] = IDLAttribute(location, identifier, type, readonly, static=static,
+ stringifier=stringifier)
+
+ def p_Attribute(self, p):
+ """
+ Attribute : Inherit AttributeRest
+ """
+ (location, identifier, type, readonly) = p[2]
+ p[0] = IDLAttribute(location, identifier, type, readonly, inherit=p[1])
+
+ def p_AttributeRest(self, p):
+ """
+ AttributeRest : ReadOnly ATTRIBUTE Type IDENTIFIER SEMICOLON
+ """
+ location = self.getLocation(p, 2)
+ readonly = p[1]
+ t = p[3]
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4])
+ p[0] = (location, identifier, t, readonly)
+
+ def p_ReadOnly(self, p):
+ """
+ ReadOnly : READONLY
+ """
+ p[0] = True
+
+ def p_ReadOnlyEmpty(self, p):
+ """
+ ReadOnly :
+ """
+ p[0] = False
+
+ def p_Inherit(self, p):
+ """
+ Inherit : INHERIT
+ """
+ p[0] = True
+
+ def p_InheritEmpty(self, p):
+ """
+ Inherit :
+ """
+ p[0] = False
+
+ def p_Operation(self, p):
+ """
+ Operation : Qualifiers OperationRest
+ """
+ qualifiers = p[1]
+
+ # Disallow duplicates in the qualifier set
+ if not len(set(qualifiers)) == len(qualifiers):
+ raise WebIDLError("Duplicate qualifiers are not allowed",
+ [self.getLocation(p, 1)])
+
+ static = IDLInterfaceMember.Special.Static in p[1]
+ # If static is there that's all that's allowed. This is disallowed
+ # by the parser, so we can assert here.
+ assert not static or len(qualifiers) == 1
+
+ stringifier = IDLInterfaceMember.Special.Stringifier in p[1]
+ # If stringifier is there that's all that's allowed. This is disallowed
+ # by the parser, so we can assert here.
+ assert not stringifier or len(qualifiers) == 1
+
+ getter = True if IDLMethod.Special.Getter in p[1] else False
+ setter = True if IDLMethod.Special.Setter in p[1] else False
+ creator = True if IDLMethod.Special.Creator in p[1] else False
+ deleter = True if IDLMethod.Special.Deleter in p[1] else False
+ legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False
+
+ if getter or deleter:
+ if setter or creator:
+ raise WebIDLError("getter and deleter are incompatible with setter and creator",
+ [self.getLocation(p, 1)])
+
+ (returnType, identifier, arguments) = p[2]
+
+ assert isinstance(returnType, IDLType)
+
+ specialType = IDLMethod.NamedOrIndexed.Neither
+
+ if getter or deleter:
+ if len(arguments) != 1:
+ raise WebIDLError("%s has wrong number of arguments" %
+ ("getter" if getter else "deleter"),
+ [self.getLocation(p, 2)])
+ argType = arguments[0].type
+ if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]:
+ specialType = IDLMethod.NamedOrIndexed.Named
+ elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]:
+ specialType = IDLMethod.NamedOrIndexed.Indexed
+ else:
+ raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" %
+ ("getter" if getter else "deleter"),
+ [arguments[0].location])
+ if arguments[0].optional or arguments[0].variadic:
+ raise WebIDLError("%s cannot have %s argument" %
+ ("getter" if getter else "deleter",
+ "optional" if arguments[0].optional else "variadic"),
+ [arguments[0].location])
+ if getter:
+ if returnType.isVoid():
+ raise WebIDLError("getter cannot have void return type",
+ [self.getLocation(p, 2)])
+ if setter or creator:
+ if len(arguments) != 2:
+ raise WebIDLError("%s has wrong number of arguments" %
+ ("setter" if setter else "creator"),
+ [self.getLocation(p, 2)])
+ argType = arguments[0].type
+ if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]:
+ specialType = IDLMethod.NamedOrIndexed.Named
+ elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]:
+ specialType = IDLMethod.NamedOrIndexed.Indexed
+ else:
+ raise WebIDLError("%s has wrong argument type (must be DOMString or UnsignedLong)" %
+ ("setter" if setter else "creator"),
+ [arguments[0].location])
+ if arguments[0].optional or arguments[0].variadic:
+ raise WebIDLError("%s cannot have %s argument" %
+ ("setter" if setter else "creator",
+ "optional" if arguments[0].optional else "variadic"),
+ [arguments[0].location])
+ if arguments[1].optional or arguments[1].variadic:
+ raise WebIDLError("%s cannot have %s argument" %
+ ("setter" if setter else "creator",
+ "optional" if arguments[1].optional else "variadic"),
+ [arguments[1].location])
+
+ if stringifier:
+ if len(arguments) != 0:
+ raise WebIDLError("stringifier has wrong number of arguments",
+ [self.getLocation(p, 2)])
+ if not returnType.isDOMString():
+ raise WebIDLError("stringifier must have DOMString return type",
+ [self.getLocation(p, 2)])
+
+ # identifier might be None. This is only permitted for special methods.
+ if not identifier:
+ if not getter and not setter and not creator and \
+ not deleter and not legacycaller and not stringifier:
+ raise WebIDLError("Identifier required for non-special methods",
+ [self.getLocation(p, 2)])
+
+ location = BuiltinLocation("<auto-generated-identifier>")
+ identifier = IDLUnresolvedIdentifier(location, "__%s%s%s%s%s%s%s" %
+ ("named" if specialType == IDLMethod.NamedOrIndexed.Named else \
+ "indexed" if specialType == IDLMethod.NamedOrIndexed.Indexed else "",
+ "getter" if getter else "",
+ "setter" if setter else "",
+ "deleter" if deleter else "",
+ "creator" if creator else "",
+ "legacycaller" if legacycaller else "",
+ "stringifier" if stringifier else ""), allowDoubleUnderscore=True)
+
+ method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments,
+ static=static, getter=getter, setter=setter, creator=creator,
+ deleter=deleter, specialType=specialType,
+ legacycaller=legacycaller, stringifier=stringifier)
+ p[0] = method
+
+ def p_Stringifier(self, p):
+ """
+ Operation : STRINGIFIER SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
+ "__stringifier",
+ allowDoubleUnderscore=True)
+ method = IDLMethod(self.getLocation(p, 1),
+ identifier,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.domstring],
+ arguments=[],
+ stringifier=True)
+ p[0] = method
+
+ def p_Jsonifier(self, p):
+ """
+ Operation : JSONIFIER SEMICOLON
+ """
+ identifier = IDLUnresolvedIdentifier(BuiltinLocation("<auto-generated-identifier>"),
+ "__jsonifier", allowDoubleUnderscore=True)
+ method = IDLMethod(self.getLocation(p, 1),
+ identifier,
+ returnType=BuiltinTypes[IDLBuiltinType.Types.object],
+ arguments=[],
+ jsonifier=True)
+ p[0] = method
+
+ def p_QualifierStatic(self, p):
+ """
+ Qualifier : STATIC
+ """
+ p[0] = [IDLInterfaceMember.Special.Static]
+
+ def p_QualifierStringifier(self, p):
+ """
+ Qualifier : STRINGIFIER
+ """
+ p[0] = [IDLInterfaceMember.Special.Stringifier]
+
+ def p_Qualifiers(self, p):
+ """
+ Qualifiers : Qualifier
+ | Specials
+ """
+ p[0] = p[1]
+
+ def p_Specials(self, p):
+ """
+ Specials : Special Specials
+ """
+ p[0] = [p[1]]
+ p[0].extend(p[2])
+
+ def p_SpecialsEmpty(self, p):
+ """
+ Specials :
+ """
+ p[0] = []
+
+ def p_SpecialGetter(self, p):
+ """
+ Special : GETTER
+ """
+ p[0] = IDLMethod.Special.Getter
+
+ def p_SpecialSetter(self, p):
+ """
+ Special : SETTER
+ """
+ p[0] = IDLMethod.Special.Setter
+
+ def p_SpecialCreator(self, p):
+ """
+ Special : CREATOR
+ """
+ p[0] = IDLMethod.Special.Creator
+
+ def p_SpecialDeleter(self, p):
+ """
+ Special : DELETER
+ """
+ p[0] = IDLMethod.Special.Deleter
+
+ def p_SpecialLegacyCaller(self, p):
+ """
+ Special : LEGACYCALLER
+ """
+ p[0] = IDLMethod.Special.LegacyCaller
+
+ def p_OperationRest(self, p):
+ """
+ OperationRest : ReturnType OptionalIdentifier LPAREN ArgumentList RPAREN SEMICOLON
+ """
+ p[0] = (p[1], p[2], p[4])
+
+ def p_OptionalIdentifier(self, p):
+ """
+ OptionalIdentifier : IDENTIFIER
+ """
+ p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ def p_OptionalIdentifierEmpty(self, p):
+ """
+ OptionalIdentifier :
+ """
+ pass
+
+ def p_ArgumentList(self, p):
+ """
+ ArgumentList : Argument Arguments
+ """
+ p[0] = [p[1]] if p[1] else []
+ p[0].extend(p[2])
+
+ def p_ArgumentListEmpty(self, p):
+ """
+ ArgumentList :
+ """
+ p[0] = []
+
+ def p_Arguments(self, p):
+ """
+ Arguments : COMMA Argument Arguments
+ """
+ p[0] = [p[2]] if p[2] else []
+ p[0].extend(p[3])
+
+ def p_ArgumentsEmpty(self, p):
+ """
+ Arguments :
+ """
+ p[0] = []
+
+ def p_Argument(self, p):
+ """
+ Argument : ExtendedAttributeList Optional Type Ellipsis ArgumentName Default
+ """
+ t = p[3]
+ assert isinstance(t, IDLType)
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 5), p[5])
+
+ optional = p[2]
+ variadic = p[4]
+ defaultValue = p[6]
+
+ if not optional and defaultValue:
+ raise WebIDLError("Mandatory arguments can't have a default value.",
+ [self.getLocation(p, 6)])
+
+ # We can't test t.isAny() here and give it a default value as needed,
+ # since at this point t is not a fully resolved type yet (e.g. it might
+ # be a typedef). We'll handle the 'any' case in IDLArgument.complete.
+
+ if variadic:
+ if optional:
+ raise WebIDLError("Variadic arguments should not be marked optional.",
+ [self.getLocation(p, 2)])
+ optional = variadic
+
+ p[0] = IDLArgument(self.getLocation(p, 5), identifier, t, optional, defaultValue, variadic)
+ p[0].addExtendedAttributes(p[1])
+
+ def p_ArgumentName(self, p):
+ """
+ ArgumentName : IDENTIFIER
+ | ATTRIBUTE
+ | CALLBACK
+ | CONST
+ | CREATOR
+ | DELETER
+ | DICTIONARY
+ | ENUM
+ | EXCEPTION
+ | GETTER
+ | IMPLEMENTS
+ | INHERIT
+ | INTERFACE
+ | LEGACYCALLER
+ | PARTIAL
+ | SERIALIZER
+ | SETTER
+ | STATIC
+ | STRINGIFIER
+ | JSONIFIER
+ | TYPEDEF
+ | UNRESTRICTED
+ """
+ p[0] = p[1]
+
+ def p_Optional(self, p):
+ """
+ Optional : OPTIONAL
+ """
+ p[0] = True
+
+ def p_OptionalEmpty(self, p):
+ """
+ Optional :
+ """
+ p[0] = False
+
+ def p_Ellipsis(self, p):
+ """
+ Ellipsis : ELLIPSIS
+ """
+ p[0] = True
+
+ def p_EllipsisEmpty(self, p):
+ """
+ Ellipsis :
+ """
+ p[0] = False
+
+ def p_ExceptionMember(self, p):
+ """
+ ExceptionMember : Const
+ | ExceptionField
+ """
+ pass
+
+ def p_ExceptionField(self, p):
+ """
+ ExceptionField : Type IDENTIFIER SEMICOLON
+ """
+ pass
+
+ def p_ExtendedAttributeList(self, p):
+ """
+ ExtendedAttributeList : LBRACKET ExtendedAttribute ExtendedAttributes RBRACKET
+ """
+ p[0] = [p[2]]
+ if p[3]:
+ p[0].extend(p[3])
+
+ def p_ExtendedAttributeListEmpty(self, p):
+ """
+ ExtendedAttributeList :
+ """
+ p[0] = []
+
+ def p_ExtendedAttribute(self, p):
+ """
+ ExtendedAttribute : ExtendedAttributeNoArgs
+ | ExtendedAttributeArgList
+ | ExtendedAttributeIdent
+ | ExtendedAttributeNamedArgList
+ | ExtendedAttributeIdentList
+ """
+ p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1])
+
+ def p_ExtendedAttributeEmpty(self, p):
+ """
+ ExtendedAttribute :
+ """
+ pass
+
+ def p_ExtendedAttributes(self, p):
+ """
+ ExtendedAttributes : COMMA ExtendedAttribute ExtendedAttributes
+ """
+ p[0] = [p[2]] if p[2] else []
+ p[0].extend(p[3])
+
+ def p_ExtendedAttributesEmpty(self, p):
+ """
+ ExtendedAttributes :
+ """
+ p[0] = []
+
+ def p_Other(self, p):
+ """
+ Other : INTEGER
+ | FLOATLITERAL
+ | IDENTIFIER
+ | STRING
+ | OTHER
+ | ELLIPSIS
+ | COLON
+ | SCOPE
+ | SEMICOLON
+ | LT
+ | EQUALS
+ | GT
+ | QUESTIONMARK
+ | DATE
+ | DOMSTRING
+ | BYTESTRING
+ | SCALARVALUESTRING
+ | ANY
+ | ATTRIBUTE
+ | BOOLEAN
+ | BYTE
+ | LEGACYCALLER
+ | CONST
+ | CREATOR
+ | DELETER
+ | DOUBLE
+ | EXCEPTION
+ | FALSE
+ | FLOAT
+ | GETTER
+ | IMPLEMENTS
+ | INHERIT
+ | INTERFACE
+ | LONG
+ | MODULE
+ | NULL
+ | OBJECT
+ | OCTET
+ | OPTIONAL
+ | SEQUENCE
+ | MOZMAP
+ | SETTER
+ | SHORT
+ | STATIC
+ | STRINGIFIER
+ | JSONIFIER
+ | TRUE
+ | TYPEDEF
+ | UNSIGNED
+ | VOID
+ """
+ pass
+
+ def p_OtherOrComma(self, p):
+ """
+ OtherOrComma : Other
+ | COMMA
+ """
+ pass
+
+ def p_TypeSingleType(self, p):
+ """
+ Type : SingleType
+ """
+ p[0] = p[1]
+
+ def p_TypeUnionType(self, p):
+ """
+ Type : UnionType TypeSuffix
+ """
+ p[0] = self.handleModifiers(p[1], p[2])
+
+ def p_SingleTypeNonAnyType(self, p):
+ """
+ SingleType : NonAnyType
+ """
+ p[0] = p[1]
+
+ def p_SingleTypeAnyType(self, p):
+ """
+ SingleType : ANY TypeSuffixStartingWithArray
+ """
+ p[0] = self.handleModifiers(BuiltinTypes[IDLBuiltinType.Types.any], p[2])
+
+ def p_UnionType(self, p):
+ """
+ UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN
+ """
+ types = [p[2], p[4]]
+ types.extend(p[5])
+ p[0] = IDLUnionType(self.getLocation(p, 1), types)
+
+ def p_UnionMemberTypeNonAnyType(self, p):
+ """
+ UnionMemberType : NonAnyType
+ """
+ p[0] = p[1]
+
+ def p_UnionMemberTypeArrayOfAny(self, p):
+ """
+ UnionMemberTypeArrayOfAny : ANY LBRACKET RBRACKET
+ """
+ p[0] = IDLArrayType(self.getLocation(p, 2),
+ BuiltinTypes[IDLBuiltinType.Types.any])
+
+ def p_UnionMemberType(self, p):
+ """
+ UnionMemberType : UnionType TypeSuffix
+ | UnionMemberTypeArrayOfAny TypeSuffix
+ """
+ p[0] = self.handleModifiers(p[1], p[2])
+
+ def p_UnionMemberTypes(self, p):
+ """
+ UnionMemberTypes : OR UnionMemberType UnionMemberTypes
+ """
+ p[0] = [p[2]]
+ p[0].extend(p[3])
+
+ def p_UnionMemberTypesEmpty(self, p):
+ """
+ UnionMemberTypes :
+ """
+ p[0] = []
+
+ def p_NonAnyType(self, p):
+ """
+ NonAnyType : PrimitiveOrStringType TypeSuffix
+ | ARRAYBUFFER TypeSuffix
+ | OBJECT TypeSuffix
+ """
+ if p[1] == "object":
+ type = BuiltinTypes[IDLBuiltinType.Types.object]
+ elif p[1] == "ArrayBuffer":
+ type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer]
+ else:
+ type = BuiltinTypes[p[1]]
+
+ p[0] = self.handleModifiers(type, p[2])
+
+ def p_NonAnyTypeSequenceType(self, p):
+ """
+ NonAnyType : SEQUENCE LT Type GT Null
+ """
+ innerType = p[3]
+ type = IDLSequenceType(self.getLocation(p, 1), innerType)
+ if p[5]:
+ type = IDLNullableType(self.getLocation(p, 5), type)
+ p[0] = type
+
+ # Note: Promise<void> is allowed, so we want to parametrize on
+ # ReturnType, not Type. Also, we want this to end up picking up
+ # the Promise interface for now, hence the games with IDLUnresolvedType.
+ def p_NonAnyTypePromiseType(self, p):
+ """
+ NonAnyType : PROMISE LT ReturnType GT Null
+ """
+ innerType = p[3]
+ promiseIdent = IDLUnresolvedIdentifier(self.getLocation(p, 1), "Promise")
+ type = IDLUnresolvedType(self.getLocation(p, 1), promiseIdent, p[3])
+ if p[5]:
+ type = IDLNullableType(self.getLocation(p, 5), type)
+ p[0] = type
+
+ def p_NonAnyTypeMozMapType(self, p):
+ """
+ NonAnyType : MOZMAP LT Type GT Null
+ """
+ innerType = p[3]
+ type = IDLMozMapType(self.getLocation(p, 1), innerType)
+ if p[5]:
+ type = IDLNullableType(self.getLocation(p, 5), type)
+ p[0] = type
+
+ def p_NonAnyTypeScopedName(self, p):
+ """
+ NonAnyType : ScopedName TypeSuffix
+ """
+ assert isinstance(p[1], IDLUnresolvedIdentifier)
+
+ if p[1].name == "Promise":
+ raise WebIDLError("Promise used without saying what it's "
+ "parametrized over",
+ [self.getLocation(p, 1)])
+
+ type = None
+
+ try:
+ if self.globalScope()._lookupIdentifier(p[1]):
+ obj = self.globalScope()._lookupIdentifier(p[1])
+ if obj.isType():
+ type = obj
+ else:
+ type = IDLWrapperType(self.getLocation(p, 1), p[1])
+ p[0] = self.handleModifiers(type, p[2])
+ return
+ except:
+ pass
+
+ type = IDLUnresolvedType(self.getLocation(p, 1), p[1])
+ p[0] = self.handleModifiers(type, p[2])
+
+ def p_NonAnyTypeDate(self, p):
+ """
+ NonAnyType : DATE TypeSuffix
+ """
+ p[0] = self.handleModifiers(BuiltinTypes[IDLBuiltinType.Types.date],
+ p[2])
+
+ def p_ConstType(self, p):
+ """
+ ConstType : PrimitiveOrStringType Null
+ """
+ type = BuiltinTypes[p[1]]
+ if p[2]:
+ type = IDLNullableType(self.getLocation(p, 1), type)
+ p[0] = type
+
+ def p_ConstTypeIdentifier(self, p):
+ """
+ ConstType : IDENTIFIER Null
+ """
+ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ type = IDLUnresolvedType(self.getLocation(p, 1), identifier)
+ if p[2]:
+ type = IDLNullableType(self.getLocation(p, 1), type)
+ p[0] = type
+
+ def p_PrimitiveOrStringTypeUint(self, p):
+ """
+ PrimitiveOrStringType : UnsignedIntegerType
+ """
+ p[0] = p[1]
+
+ def p_PrimitiveOrStringTypeBoolean(self, p):
+ """
+ PrimitiveOrStringType : BOOLEAN
+ """
+ p[0] = IDLBuiltinType.Types.boolean
+
+ def p_PrimitiveOrStringTypeByte(self, p):
+ """
+ PrimitiveOrStringType : BYTE
+ """
+ p[0] = IDLBuiltinType.Types.byte
+
+ def p_PrimitiveOrStringTypeOctet(self, p):
+ """
+ PrimitiveOrStringType : OCTET
+ """
+ p[0] = IDLBuiltinType.Types.octet
+
+ def p_PrimitiveOrStringTypeFloat(self, p):
+ """
+ PrimitiveOrStringType : FLOAT
+ """
+ p[0] = IDLBuiltinType.Types.float
+
+ def p_PrimitiveOrStringTypeUnrestictedFloat(self, p):
+ """
+ PrimitiveOrStringType : UNRESTRICTED FLOAT
+ """
+ p[0] = IDLBuiltinType.Types.unrestricted_float
+
+ def p_PrimitiveOrStringTypeDouble(self, p):
+ """
+ PrimitiveOrStringType : DOUBLE
+ """
+ p[0] = IDLBuiltinType.Types.double
+
+ def p_PrimitiveOrStringTypeUnrestictedDouble(self, p):
+ """
+ PrimitiveOrStringType : UNRESTRICTED DOUBLE
+ """
+ p[0] = IDLBuiltinType.Types.unrestricted_double
+
+ def p_PrimitiveOrStringTypeDOMString(self, p):
+ """
+ PrimitiveOrStringType : DOMSTRING
+ """
+ p[0] = IDLBuiltinType.Types.domstring
+
+ def p_PrimitiveOrStringTypeBytestring(self, p):
+ """
+ PrimitiveOrStringType : BYTESTRING
+ """
+ p[0] = IDLBuiltinType.Types.bytestring
+
+ def p_PrimitiveOrStringTypeScalarValueString(self, p):
+ """
+ PrimitiveOrStringType : SCALARVALUESTRING
+ """
+ p[0] = IDLBuiltinType.Types.scalarvaluestring
+
+ def p_UnsignedIntegerTypeUnsigned(self, p):
+ """
+ UnsignedIntegerType : UNSIGNED IntegerType
+ """
+ p[0] = p[2] + 1 # Adding one to a given signed integer type
+ # gets you the unsigned type.
+
+ def p_UnsignedIntegerType(self, p):
+ """
+ UnsignedIntegerType : IntegerType
+ """
+ p[0] = p[1]
+
+ def p_IntegerTypeShort(self, p):
+ """
+ IntegerType : SHORT
+ """
+ p[0] = IDLBuiltinType.Types.short
+
+ def p_IntegerTypeLong(self, p):
+ """
+ IntegerType : LONG OptionalLong
+ """
+ if p[2]:
+ p[0] = IDLBuiltinType.Types.long_long
+ else:
+ p[0] = IDLBuiltinType.Types.long
+
+ def p_OptionalLong(self, p):
+ """
+ OptionalLong : LONG
+ """
+ p[0] = True
+
+ def p_OptionalLongEmpty(self, p):
+ """
+ OptionalLong :
+ """
+ p[0] = False
+
+ def p_TypeSuffixBrackets(self, p):
+ """
+ TypeSuffix : LBRACKET RBRACKET TypeSuffix
+ """
+ p[0] = [(IDLMethod.TypeSuffixModifier.Brackets, self.getLocation(p, 1))]
+ p[0].extend(p[3])
+
+ def p_TypeSuffixQMark(self, p):
+ """
+ TypeSuffix : QUESTIONMARK TypeSuffixStartingWithArray
+ """
+ p[0] = [(IDLMethod.TypeSuffixModifier.QMark, self.getLocation(p, 1))]
+ p[0].extend(p[2])
+
+ def p_TypeSuffixEmpty(self, p):
+ """
+ TypeSuffix :
+ """
+ p[0] = []
+
+ def p_TypeSuffixStartingWithArray(self, p):
+ """
+ TypeSuffixStartingWithArray : LBRACKET RBRACKET TypeSuffix
+ """
+ p[0] = [(IDLMethod.TypeSuffixModifier.Brackets, self.getLocation(p, 1))]
+ p[0].extend(p[3])
+
+ def p_TypeSuffixStartingWithArrayEmpty(self, p):
+ """
+ TypeSuffixStartingWithArray :
+ """
+ p[0] = []
+
+ def p_Null(self, p):
+ """
+ Null : QUESTIONMARK
+ |
+ """
+ if len(p) > 1:
+ p[0] = True
+ else:
+ p[0] = False
+
+ def p_ReturnTypeType(self, p):
+ """
+ ReturnType : Type
+ """
+ p[0] = p[1]
+
+ def p_ReturnTypeVoid(self, p):
+ """
+ ReturnType : VOID
+ """
+ p[0] = BuiltinTypes[IDLBuiltinType.Types.void]
+
+ def p_ScopedName(self, p):
+ """
+ ScopedName : AbsoluteScopedName
+ | RelativeScopedName
+ """
+ p[0] = p[1]
+
+ def p_AbsoluteScopedName(self, p):
+ """
+ AbsoluteScopedName : SCOPE IDENTIFIER ScopedNameParts
+ """
+ assert False
+ pass
+
+ def p_RelativeScopedName(self, p):
+ """
+ RelativeScopedName : IDENTIFIER ScopedNameParts
+ """
+ assert not p[2] # Not implemented!
+
+ p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1])
+
+ def p_ScopedNameParts(self, p):
+ """
+ ScopedNameParts : SCOPE IDENTIFIER ScopedNameParts
+ """
+ assert False
+ pass
+
+ def p_ScopedNamePartsEmpty(self, p):
+ """
+ ScopedNameParts :
+ """
+ p[0] = None
+
+ def p_ExtendedAttributeNoArgs(self, p):
+ """
+ ExtendedAttributeNoArgs : IDENTIFIER
+ """
+ p[0] = (p[1],)
+
+ def p_ExtendedAttributeArgList(self, p):
+ """
+ ExtendedAttributeArgList : IDENTIFIER LPAREN ArgumentList RPAREN
+ """
+ p[0] = (p[1], p[3])
+
+ def p_ExtendedAttributeIdent(self, p):
+ """
+ ExtendedAttributeIdent : IDENTIFIER EQUALS STRING
+ | IDENTIFIER EQUALS IDENTIFIER
+ """
+ p[0] = (p[1], p[3])
+
+ def p_ExtendedAttributeNamedArgList(self, p):
+ """
+ ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN
+ """
+ p[0] = (p[1], p[3], p[5])
+
+ def p_ExtendedAttributeIdentList(self, p):
+ """
+ ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN
+ """
+ p[0] = (p[1], p[4])
+
+ def p_IdentifierList(self, p):
+ """
+ IdentifierList : IDENTIFIER Identifiers
+ """
+ idents = list(p[2])
+ idents.insert(0, p[1])
+ p[0] = idents
+
+ def p_IdentifiersList(self, p):
+ """
+ Identifiers : COMMA IDENTIFIER Identifiers
+ """
+ idents = list(p[3])
+ idents.insert(0, p[2])
+ p[0] = idents
+
+ def p_IdentifiersEmpty(self, p):
+ """
+ Identifiers :
+ """
+ p[0] = []
+
+ def p_error(self, p):
+ if not p:
+ raise WebIDLError("Syntax Error at end of file. Possibly due to missing semicolon(;), braces(}) or both",
+ [self._filename])
+ else:
+ raise WebIDLError("invalid syntax", [Location(self.lexer, p.lineno, p.lexpos, self._filename)])
+
+ def __init__(self, outputdir='', lexer=None):
+ Tokenizer.__init__(self, outputdir, lexer)
+
+ logger = SqueakyCleanLogger()
+ self.parser = yacc.yacc(module=self,
+ outputdir=outputdir,
+ tabmodule='webidlyacc',
+ errorlog=logger
+ # Pickling the grammar is a speedup in
+ # some cases (older Python?) but a
+ # significant slowdown in others.
+ # We're not pickling for now, until it
+ # becomes a speedup again.
+ # , picklefile='WebIDLGrammar.pkl'
+ )
+ logger.reportGrammarErrors()
+
+ self._globalScope = IDLScope(BuiltinLocation("<Global Scope>"), None, None)
+ # To make our test harness work, pretend like we have a primary global already. Note that we _don't_ set _globalScope.primaryGlobalAttr, so we'll still be able to detect multiple PrimaryGlobal extended attributes.
+ self._globalScope.primaryGlobalName = "FakeTestPrimaryGlobal"
+ self._globalScope.globalNames.add("FakeTestPrimaryGlobal")
+ self._globalScope.globalNameMapping["FakeTestPrimaryGlobal"].add("FakeTestPrimaryGlobal")
+ self._installBuiltins(self._globalScope)
+ self._productions = []
+
+ self._filename = "<builtin>"
+ self.lexer.input(Parser._builtins)
+ self._filename = None
+
+ self.parser.parse(lexer=self.lexer,tracking=True)
+
+ def _installBuiltins(self, scope):
+ assert isinstance(scope, IDLScope)
+
+ # xrange omits the last value.
+ for x in xrange(IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1):
+ builtin = BuiltinTypes[x]
+ name = builtin.name
+
+ typedef = IDLTypedefType(BuiltinLocation("<builtin type>"), builtin, name)
+ typedef.resolve(scope)
+
+ @ staticmethod
+ def handleModifiers(type, modifiers):
+ for (modifier, modifierLocation) in modifiers:
+ assert modifier == IDLMethod.TypeSuffixModifier.QMark or \
+ modifier == IDLMethod.TypeSuffixModifier.Brackets
+
+ if modifier == IDLMethod.TypeSuffixModifier.QMark:
+ type = IDLNullableType(modifierLocation, type)
+ elif modifier == IDLMethod.TypeSuffixModifier.Brackets:
+ type = IDLArrayType(modifierLocation, type)
+
+ return type
+
+ def parse(self, t, filename=None):
+ self.lexer.input(t)
+
+ #for tok in iter(self.lexer.token, None):
+ # print tok
+
+ self._filename = filename
+ self._productions.extend(self.parser.parse(lexer=self.lexer,tracking=True))
+ self._filename = None
+
+ def finish(self):
+ # First, finish all the IDLImplementsStatements. In particular, we
+ # have to make sure we do those before we do the IDLInterfaces.
+ # XXX khuey hates this bit and wants to nuke it from orbit.
+ implementsStatements = [ p for p in self._productions if
+ isinstance(p, IDLImplementsStatement)]
+ otherStatements = [ p for p in self._productions if
+ not isinstance(p, IDLImplementsStatement)]
+ for production in implementsStatements:
+ production.finish(self.globalScope())
+ for production in otherStatements:
+ production.finish(self.globalScope())
+
+ # Do any post-finish validation we need to do
+ for production in self._productions:
+ production.validate()
+
+ # De-duplicate self._productions, without modifying its order.
+ seen = set()
+ result = []
+ for p in self._productions:
+ if p not in seen:
+ seen.add(p)
+ result.append(p)
+ return result
+
+ def reset(self):
+ return Parser(lexer=self.lexer)
+
+ # Builtin IDL defined by WebIDL
+ _builtins = """
+ typedef unsigned long long DOMTimeStamp;
+ """
+
+def main():
+ # Parse arguments.
+ from optparse import OptionParser
+ usageString = "usage: %prog [options] files"
+ o = OptionParser(usage=usageString)
+ o.add_option("--cachedir", dest='cachedir', default=None,
+ help="Directory in which to cache lex/parse tables.")
+ o.add_option("--verbose-errors", action='store_true', default=False,
+ help="When an error happens, display the Python traceback.")
+ (options, args) = o.parse_args()
+
+ if len(args) < 1:
+ o.error(usageString)
+
+ fileList = args
+ baseDir = os.getcwd()
+
+ # Parse the WebIDL.
+ parser = Parser(options.cachedir)
+ try:
+ for filename in fileList:
+ fullPath = os.path.normpath(os.path.join(baseDir, filename))
+ f = open(fullPath, 'rb')
+ lines = f.readlines()
+ f.close()
+ print fullPath
+ parser.parse(''.join(lines), fullPath)
+ parser.finish()
+ except WebIDLError, e:
+ if options.verbose_errors:
+ traceback.print_exc()
+ else:
+ print e
+
+if __name__ == '__main__':
+ main()
diff --git a/components/script/dom/bindings/codegen/parser/external.patch b/components/script/dom/bindings/codegen/parser/external.patch
new file mode 100644
index 00000000000..9464511a9d0
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/external.patch
@@ -0,0 +1,49 @@
+--- WebIDL.py
++++ WebIDL.py
+@@ -450,44 +450,8 @@ class IDLIdentifierPlaceholder(IDLObjectWithIdentifier):
+
+ class IDLExternalInterface(IDLObjectWithIdentifier):
+ def __init__(self, location, parentScope, identifier):
+- assert isinstance(identifier, IDLUnresolvedIdentifier)
+- assert isinstance(parentScope, IDLScope)
+- self.parent = None
+- IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
+- IDLObjectWithIdentifier.resolve(self, parentScope)
+-
+- def finish(self, scope):
+- pass
+-
+- def validate(self):
+- pass
+-
+- def isExternal(self):
+- return True
+-
+- def isInterface(self):
+- return True
+-
+- def isConsequential(self):
+- return False
+-
+- def addExtendedAttributes(self, attrs):
+- assert len(attrs) == 0
+-
+- def resolve(self, parentScope):
+- pass
+-
+- def getJSImplementation(self):
+- return None
+-
+- def isJSImplemented(self):
+- return False
+-
+- def getNavigatorProperty(self):
+- return None
+-
+- def _getDependentObjects(self):
+- return set()
++ raise WebIDLError("Servo does not support external interfaces.",
++ [self.location])
+
+ class IDLPartialInterface(IDLObject):
+ def __init__(self, location, name, members, nonPartialInterface):
diff --git a/components/script/dom/bindings/codegen/parser/module.patch b/components/script/dom/bindings/codegen/parser/module.patch
new file mode 100644
index 00000000000..977947b4c63
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/module.patch
@@ -0,0 +1,12 @@
+--- WebIDL.py
++++ WebIDL.py
+@@ -3398,6 +3398,9 @@ class IDLCallbackType(IDLType, IDLObjectWithScope):
+ self._treatNonCallableAsNull = False
+ self._treatNonObjectAsNull = False
+
++ def module(self):
++ return self.location.filename().split('/')[-1].split('.webidl')[0] + 'Binding'
++
+ def isCallback(self):
+ return True
+
diff --git a/components/script/dom/bindings/codegen/parser/runtests.py b/components/script/dom/bindings/codegen/parser/runtests.py
new file mode 100644
index 00000000000..98a7d2b81d3
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/runtests.py
@@ -0,0 +1,79 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os, sys
+import glob
+import optparse
+import traceback
+import WebIDL
+
+class TestHarness(object):
+ def __init__(self, test, verbose):
+ self.test = test
+ self.verbose = verbose
+ self.printed_intro = False
+
+ def start(self):
+ if self.verbose:
+ self.maybe_print_intro()
+
+ def finish(self):
+ if self.verbose or self.printed_intro:
+ print "Finished test %s" % self.test
+
+ def maybe_print_intro(self):
+ if not self.printed_intro:
+ print "Starting test %s" % self.test
+ self.printed_intro = True
+
+ def test_pass(self, msg):
+ if self.verbose:
+ print "TEST-PASS | %s" % msg
+
+ def test_fail(self, msg):
+ self.maybe_print_intro()
+ print "TEST-UNEXPECTED-FAIL | %s" % msg
+
+ def ok(self, condition, msg):
+ if condition:
+ self.test_pass(msg)
+ else:
+ self.test_fail(msg)
+
+ def check(self, a, b, msg):
+ if a == b:
+ self.test_pass(msg)
+ else:
+ self.test_fail(msg)
+ print "\tGot %s expected %s" % (a, b)
+
+def run_tests(tests, verbose):
+ testdir = os.path.join(os.path.dirname(__file__), 'tests')
+ if not tests:
+ tests = glob.iglob(os.path.join(testdir, "*.py"))
+ sys.path.append(testdir)
+
+ for test in tests:
+ (testpath, ext) = os.path.splitext(os.path.basename(test))
+ _test = __import__(testpath, globals(), locals(), ['WebIDLTest'])
+
+ harness = TestHarness(test, verbose)
+ harness.start()
+ try:
+ _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
+ except Exception, ex:
+ print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s: %s" % (testpath, ex)
+ traceback.print_exc()
+ finally:
+ harness.finish()
+
+if __name__ == '__main__':
+ usage = """%prog [OPTIONS] [TESTS]
+ Where TESTS are relative to the tests directory."""
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+ help="Don't print passing tests.")
+ options, tests = parser.parse_args()
+
+ run_tests(tests, verbose=options.verbose)
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_any_null.py b/components/script/dom/bindings/codegen/parser/tests/test_any_null.py
new file mode 100644
index 00000000000..e3b690bf6f1
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_any_null.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface DoubleNull {
+ attribute any? foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py b/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py
new file mode 100644
index 00000000000..eb1f6d3c92e
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_argument_identifier_conflicts.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface ArgumentIdentifierConflict {
+ void foo(boolean arg1, boolean arg1);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py b/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py
new file mode 100644
index 00000000000..ef8c2229aed
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_argument_novoid.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface VoidArgument1 {
+ void foo(void arg2);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py
new file mode 100644
index 00000000000..26528984595
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_array_of_interface.py
@@ -0,0 +1,13 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface A {
+ attribute long a;
+ };
+
+ interface B {
+ attribute A[] b;
+ };
+ """);
+ parser.finish()
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py b/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py
new file mode 100644
index 00000000000..5b8e56f86ca
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_arraybuffer.py
@@ -0,0 +1,84 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestArrayBuffer {
+ attribute ArrayBuffer bufferAttr;
+ void bufferMethod(ArrayBuffer arg1, ArrayBuffer? arg2, ArrayBuffer[] arg3, sequence<ArrayBuffer> arg4);
+
+ attribute ArrayBufferView viewAttr;
+ void viewMethod(ArrayBufferView arg1, ArrayBufferView? arg2, ArrayBufferView[] arg3, sequence<ArrayBufferView> arg4);
+
+ attribute Int8Array int8ArrayAttr;
+ void int8ArrayMethod(Int8Array arg1, Int8Array? arg2, Int8Array[] arg3, sequence<Int8Array> arg4);
+
+ attribute Uint8Array uint8ArrayAttr;
+ void uint8ArrayMethod(Uint8Array arg1, Uint8Array? arg2, Uint8Array[] arg3, sequence<Uint8Array> arg4);
+
+ attribute Uint8ClampedArray uint8ClampedArrayAttr;
+ void uint8ClampedArrayMethod(Uint8ClampedArray arg1, Uint8ClampedArray? arg2, Uint8ClampedArray[] arg3, sequence<Uint8ClampedArray> arg4);
+
+ attribute Int16Array int16ArrayAttr;
+ void int16ArrayMethod(Int16Array arg1, Int16Array? arg2, Int16Array[] arg3, sequence<Int16Array> arg4);
+
+ attribute Uint16Array uint16ArrayAttr;
+ void uint16ArrayMethod(Uint16Array arg1, Uint16Array? arg2, Uint16Array[] arg3, sequence<Uint16Array> arg4);
+
+ attribute Int32Array int32ArrayAttr;
+ void int32ArrayMethod(Int32Array arg1, Int32Array? arg2, Int32Array[] arg3, sequence<Int32Array> arg4);
+
+ attribute Uint32Array uint32ArrayAttr;
+ void uint32ArrayMethod(Uint32Array arg1, Uint32Array? arg2, Uint32Array[] arg3, sequence<Uint32Array> arg4);
+
+ attribute Float32Array float32ArrayAttr;
+ void float32ArrayMethod(Float32Array arg1, Float32Array? arg2, Float32Array[] arg3, sequence<Float32Array> arg4);
+
+ attribute Float64Array float64ArrayAttr;
+ void float64ArrayMethod(Float64Array arg1, Float64Array? arg2, Float64Array[] arg3, sequence<Float64Array> arg4);
+ };
+ """)
+
+ results = parser.finish()
+
+ iface = results[0]
+
+ harness.ok(True, "TestArrayBuffer interface parsed without error")
+ harness.check(len(iface.members), 22, "Interface should have twenty two members")
+
+ members = iface.members
+
+ def checkStuff(attr, method, t):
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Expect an IDLAttribute")
+ harness.ok(isinstance(method, WebIDL.IDLMethod), "Expect an IDLMethod")
+
+ harness.check(str(attr.type), t, "Expect an ArrayBuffer type")
+ harness.ok(attr.type.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+ (retType, arguments) = method.signatures()[0]
+ harness.ok(retType.isVoid(), "Should have a void return type")
+ harness.check(len(arguments), 4, "Expect 4 arguments")
+
+ harness.check(str(arguments[0].type), t, "Expect an ArrayBuffer type")
+ harness.ok(arguments[0].type.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+ harness.check(str(arguments[1].type), t + "OrNull", "Expect an ArrayBuffer type")
+ harness.ok(arguments[1].type.inner.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+ harness.check(str(arguments[2].type), t + "Array", "Expect an ArrayBuffer type")
+ harness.ok(arguments[2].type.inner.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+ harness.check(str(arguments[3].type), t + "Sequence", "Expect an ArrayBuffer type")
+ harness.ok(arguments[3].type.inner.isSpiderMonkeyInterface(), "Should test as a js interface")
+
+
+ checkStuff(members[0], members[1], "ArrayBuffer")
+ checkStuff(members[2], members[3], "ArrayBufferView")
+ checkStuff(members[4], members[5], "Int8Array")
+ checkStuff(members[6], members[7], "Uint8Array")
+ checkStuff(members[8], members[9], "Uint8ClampedArray")
+ checkStuff(members[10], members[11], "Int16Array")
+ checkStuff(members[12], members[13], "Uint16Array")
+ checkStuff(members[14], members[15], "Int32Array")
+ checkStuff(members[16], members[17], "Uint32Array")
+ checkStuff(members[18], members[19], "Float32Array")
+ checkStuff(members[20], members[21], "Float64Array")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attr.py b/components/script/dom/bindings/codegen/parser/tests/test_attr.py
new file mode 100644
index 00000000000..6b6142b6243
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_attr.py
@@ -0,0 +1,302 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ testData = [("::TestAttr%s::b", "b", "Byte%s", False),
+ ("::TestAttr%s::rb", "rb", "Byte%s", True),
+ ("::TestAttr%s::o", "o", "Octet%s", False),
+ ("::TestAttr%s::ro", "ro", "Octet%s", True),
+ ("::TestAttr%s::s", "s", "Short%s", False),
+ ("::TestAttr%s::rs", "rs", "Short%s", True),
+ ("::TestAttr%s::us", "us", "UnsignedShort%s", False),
+ ("::TestAttr%s::rus", "rus", "UnsignedShort%s", True),
+ ("::TestAttr%s::l", "l", "Long%s", False),
+ ("::TestAttr%s::rl", "rl", "Long%s", True),
+ ("::TestAttr%s::ul", "ul", "UnsignedLong%s", False),
+ ("::TestAttr%s::rul", "rul", "UnsignedLong%s", True),
+ ("::TestAttr%s::ll", "ll", "LongLong%s", False),
+ ("::TestAttr%s::rll", "rll", "LongLong%s", True),
+ ("::TestAttr%s::ull", "ull", "UnsignedLongLong%s", False),
+ ("::TestAttr%s::rull", "rull", "UnsignedLongLong%s", True),
+ ("::TestAttr%s::str", "str", "String%s", False),
+ ("::TestAttr%s::rstr", "rstr", "String%s", True),
+ ("::TestAttr%s::obj", "obj", "Object%s", False),
+ ("::TestAttr%s::robj", "robj", "Object%s", True),
+ ("::TestAttr%s::object", "object", "Object%s", False),
+ ("::TestAttr%s::f", "f", "Float%s", False),
+ ("::TestAttr%s::rf", "rf", "Float%s", True)]
+
+ parser.parse("""
+ interface TestAttr {
+ attribute byte b;
+ readonly attribute byte rb;
+ attribute octet o;
+ readonly attribute octet ro;
+ attribute short s;
+ readonly attribute short rs;
+ attribute unsigned short us;
+ readonly attribute unsigned short rus;
+ attribute long l;
+ readonly attribute long rl;
+ attribute unsigned long ul;
+ readonly attribute unsigned long rul;
+ attribute long long ll;
+ readonly attribute long long rll;
+ attribute unsigned long long ull;
+ readonly attribute unsigned long long rull;
+ attribute DOMString str;
+ readonly attribute DOMString rstr;
+ attribute object obj;
+ readonly attribute object robj;
+ attribute object _object;
+ attribute float f;
+ readonly attribute float rf;
+ };
+
+ interface TestAttrNullable {
+ attribute byte? b;
+ readonly attribute byte? rb;
+ attribute octet? o;
+ readonly attribute octet? ro;
+ attribute short? s;
+ readonly attribute short? rs;
+ attribute unsigned short? us;
+ readonly attribute unsigned short? rus;
+ attribute long? l;
+ readonly attribute long? rl;
+ attribute unsigned long? ul;
+ readonly attribute unsigned long? rul;
+ attribute long long? ll;
+ readonly attribute long long? rll;
+ attribute unsigned long long? ull;
+ readonly attribute unsigned long long? rull;
+ attribute DOMString? str;
+ readonly attribute DOMString? rstr;
+ attribute object? obj;
+ readonly attribute object? robj;
+ attribute object? _object;
+ attribute float? f;
+ readonly attribute float? rf;
+ };
+
+ interface TestAttrArray {
+ attribute byte[] b;
+ readonly attribute byte[] rb;
+ attribute octet[] o;
+ readonly attribute octet[] ro;
+ attribute short[] s;
+ readonly attribute short[] rs;
+ attribute unsigned short[] us;
+ readonly attribute unsigned short[] rus;
+ attribute long[] l;
+ readonly attribute long[] rl;
+ attribute unsigned long[] ul;
+ readonly attribute unsigned long[] rul;
+ attribute long long[] ll;
+ readonly attribute long long[] rll;
+ attribute unsigned long long[] ull;
+ readonly attribute unsigned long long[] rull;
+ attribute DOMString[] str;
+ readonly attribute DOMString[] rstr;
+ attribute object[] obj;
+ readonly attribute object[] robj;
+ attribute object[] _object;
+ attribute float[] f;
+ readonly attribute float[] rf;
+ };
+
+ interface TestAttrNullableArray {
+ attribute byte[]? b;
+ readonly attribute byte[]? rb;
+ attribute octet[]? o;
+ readonly attribute octet[]? ro;
+ attribute short[]? s;
+ readonly attribute short[]? rs;
+ attribute unsigned short[]? us;
+ readonly attribute unsigned short[]? rus;
+ attribute long[]? l;
+ readonly attribute long[]? rl;
+ attribute unsigned long[]? ul;
+ readonly attribute unsigned long[]? rul;
+ attribute long long[]? ll;
+ readonly attribute long long[]? rll;
+ attribute unsigned long long[]? ull;
+ readonly attribute unsigned long long[]? rull;
+ attribute DOMString[]? str;
+ readonly attribute DOMString[]? rstr;
+ attribute object[]? obj;
+ readonly attribute object[]? robj;
+ attribute object[]? _object;
+ attribute float[]? f;
+ readonly attribute float[]? rf;
+ };
+
+ interface TestAttrArrayOfNullableTypes {
+ attribute byte?[] b;
+ readonly attribute byte?[] rb;
+ attribute octet?[] o;
+ readonly attribute octet?[] ro;
+ attribute short?[] s;
+ readonly attribute short?[] rs;
+ attribute unsigned short?[] us;
+ readonly attribute unsigned short?[] rus;
+ attribute long?[] l;
+ readonly attribute long?[] rl;
+ attribute unsigned long?[] ul;
+ readonly attribute unsigned long?[] rul;
+ attribute long long?[] ll;
+ readonly attribute long long?[] rll;
+ attribute unsigned long long?[] ull;
+ readonly attribute unsigned long long?[] rull;
+ attribute DOMString?[] str;
+ readonly attribute DOMString?[] rstr;
+ attribute object?[] obj;
+ readonly attribute object?[] robj;
+ attribute object?[] _object;
+ attribute float?[] f;
+ readonly attribute float?[] rf;
+ };
+
+ interface TestAttrNullableArrayOfNullableTypes {
+ attribute byte?[]? b;
+ readonly attribute byte?[]? rb;
+ attribute octet?[]? o;
+ readonly attribute octet?[]? ro;
+ attribute short?[]? s;
+ readonly attribute short?[]? rs;
+ attribute unsigned short?[]? us;
+ readonly attribute unsigned short?[]? rus;
+ attribute long?[]? l;
+ readonly attribute long?[]? rl;
+ attribute unsigned long?[]? ul;
+ readonly attribute unsigned long?[]? rul;
+ attribute long long?[]? ll;
+ readonly attribute long long?[]? rll;
+ attribute unsigned long long?[]? ull;
+ readonly attribute unsigned long long?[]? rull;
+ attribute DOMString?[]? str;
+ readonly attribute DOMString?[]? rstr;
+ attribute object?[]? obj;
+ readonly attribute object?[]? robj;
+ attribute object?[]? _object;
+ attribute float?[]? f;
+ readonly attribute float?[]? rf;
+ };
+ """)
+
+ results = parser.finish()
+
+ def checkAttr(attr, QName, name, type, readonly):
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute),
+ "Should be an IDLAttribute")
+ harness.ok(attr.isAttr(), "Attr is an Attr")
+ harness.ok(not attr.isMethod(), "Attr is not an method")
+ harness.ok(not attr.isConst(), "Attr is not a const")
+ harness.check(attr.identifier.QName(), QName, "Attr has the right QName")
+ harness.check(attr.identifier.name, name, "Attr has the right name")
+ harness.check(str(attr.type), type, "Attr has the right type")
+ harness.check(attr.readonly, readonly, "Attr's readonly state is correct")
+
+ harness.ok(True, "TestAttr interface parsed without error.")
+ harness.check(len(results), 6, "Should be six productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttr", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttr", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "", name, type % "", readonly)
+
+ iface = results[1]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttrNullable", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttrNullable", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "Nullable", name, type % "OrNull", readonly)
+
+ iface = results[2]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttrArray", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttrArray", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "Array", name, type % "Array", readonly)
+
+ iface = results[3]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttrNullableArray", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttrNullableArray", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "NullableArray", name, type % "ArrayOrNull", readonly)
+
+ iface = results[4]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttrArrayOfNullableTypes", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttrArrayOfNullableTypes", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "ArrayOfNullableTypes", name, type % "OrNullArray", readonly)
+
+ iface = results[5]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestAttrNullableArrayOfNullableTypes", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestAttrNullableArrayOfNullableTypes", "Interface has the right name")
+ harness.check(len(iface.members), len(testData), "Expect %s members" % len(testData))
+
+ attrs = iface.members
+
+ for i in range(len(attrs)):
+ data = testData[i]
+ attr = attrs[i]
+ (QName, name, type, readonly) = data
+ checkAttr(attr, QName % "NullableArrayOfNullableTypes", name, type % "OrNullArrayOrNull", readonly)
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A {
+ [SetterInfallible] readonly attribute boolean foo;
+ };
+ """)
+ results = parser.finish()
+ except Exception, x:
+ threw = True
+ harness.ok(threw, "Should not allow [SetterInfallible] on readonly attributes")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py b/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py
new file mode 100644
index 00000000000..fb1b97812bc
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_attr_sequence_type.py
@@ -0,0 +1,67 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface AttrSequenceType {
+ attribute sequence<object> foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Attribute type must not be a sequence type")
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse("""
+ interface AttrUnionWithSequenceType {
+ attribute (sequence<object> or DOMString) foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Attribute type must not be a union with a sequence member type")
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse("""
+ interface AttrNullableUnionWithSequenceType {
+ attribute (sequence<object>? or DOMString) foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Attribute type must not be a union with a nullable sequence "
+ "member type")
+
+ parser.reset()
+
+ threw = False
+ try:
+ parser.parse("""
+ interface AttrUnionWithUnionWithSequenceType {
+ attribute ((sequence<object> or DOMString) or AttrUnionWithUnionWithSequenceType) foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Attribute type must not be a union type with a union member "
+ "type that has a sequence member type")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py b/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py
new file mode 100644
index 00000000000..631e52eba0b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_builtin_filename.py
@@ -0,0 +1,11 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface Test {
+ attribute long b;
+ };
+ """);
+
+ attr = parser.finish()[0].members[0]
+ harness.check(attr.type.filename(), '<builtin>', 'Filename on builtin type')
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_builtins.py b/components/script/dom/bindings/codegen/parser/tests/test_builtins.py
new file mode 100644
index 00000000000..f8563fc2d9b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_builtins.py
@@ -0,0 +1,41 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestBuiltins {
+ attribute boolean b;
+ attribute byte s8;
+ attribute octet u8;
+ attribute short s16;
+ attribute unsigned short u16;
+ attribute long s32;
+ attribute unsigned long u32;
+ attribute long long s64;
+ attribute unsigned long long u64;
+ attribute DOMTimeStamp ts;
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestBuiltins interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(iface.identifier.QName(), "::TestBuiltins", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestBuiltins", "Interface has the right name")
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 10, "Should be one production")
+
+ names = ["b", "s8", "u8", "s16", "u16", "s32", "u32", "s64", "u64", "ts"]
+ types = ["Boolean", "Byte", "Octet", "Short", "UnsignedShort", "Long", "UnsignedLong", "LongLong", "UnsignedLongLong", "UnsignedLongLong"]
+ for i in range(10):
+ attr = members[i]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute), "Should be an IDLAttribute")
+ harness.check(attr.identifier.QName(), "::TestBuiltins::" + names[i], "Attr has correct QName")
+ harness.check(attr.identifier.name, names[i], "Attr has correct name")
+ harness.check(str(attr.type), types[i], "Attr type is the correct name")
+ harness.ok(attr.type.isPrimitive(), "Should be a primitive type")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback.py b/components/script/dom/bindings/codegen/parser/tests/test_callback.py
new file mode 100644
index 00000000000..267d27dc087
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_callback.py
@@ -0,0 +1,34 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestCallback {
+ attribute CallbackType? listener;
+ };
+
+ callback CallbackType = boolean (unsigned long arg);
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestCallback interface parsed without error.")
+ harness.check(len(results), 2, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestCallback", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestCallback", "Interface has the right name")
+ harness.check(len(iface.members), 1, "Expect %s members" % 1)
+
+ attr = iface.members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute),
+ "Should be an IDLAttribute")
+ harness.ok(attr.isAttr(), "Should be an attribute")
+ harness.ok(not attr.isMethod(), "Attr is not an method")
+ harness.ok(not attr.isConst(), "Attr is not a const")
+ harness.check(attr.identifier.QName(), "::TestCallback::listener", "Attr has the right QName")
+ harness.check(attr.identifier.name, "listener", "Attr has the right name")
+ t = attr.type
+ harness.ok(not isinstance(t, WebIDL.IDLWrapperType), "Attr has the right type")
+ harness.ok(isinstance(t, WebIDL.IDLNullableType), "Attr has the right type")
+ harness.ok(t.isCallback(), "Attr has the right type")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py
new file mode 100644
index 00000000000..80896ca1edb
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_callback_interface.py
@@ -0,0 +1,47 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ callback interface TestCallbackInterface {
+ attribute boolean bool;
+ };
+ """)
+
+ results = parser.finish()
+
+ iface = results[0]
+
+ harness.ok(iface.isCallback(), "Interface should be a callback")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface {
+ };
+ callback interface TestCallbackInterface : TestInterface {
+ attribute boolean bool;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow non-callback parent of callback interface")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface : TestCallbackInterface {
+ };
+ callback interface TestCallbackInterface {
+ attribute boolean bool;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow callback parent of non-callback interface")
+
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_const.py b/components/script/dom/bindings/codegen/parser/tests/test_const.py
new file mode 100644
index 00000000000..12f411363fb
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_const.py
@@ -0,0 +1,64 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestConsts {
+ const byte zero = 0;
+ const byte b = -1;
+ const octet o = 2;
+ const short s = -3;
+ const unsigned short us = 0x4;
+ const long l = -0X5;
+ const unsigned long ul = 6;
+ const unsigned long long ull = 7;
+ const long long ll = -010;
+ const boolean t = true;
+ const boolean f = false;
+ const boolean? n = null;
+ const boolean? nt = true;
+ const boolean? nf = false;
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestConsts interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestConsts", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestConsts", "Interface has the right name")
+ harness.check(len(iface.members), 14, "Expect 14 members")
+
+ consts = iface.members
+
+ def checkConst(const, QName, name, type, value):
+ harness.ok(isinstance(const, WebIDL.IDLConst),
+ "Should be an IDLConst")
+ harness.ok(const.isConst(), "Const is a const")
+ harness.ok(not const.isAttr(), "Const is not an attr")
+ harness.ok(not const.isMethod(), "Const is not a method")
+ harness.check(const.identifier.QName(), QName, "Const has the right QName")
+ harness.check(const.identifier.name, name, "Const has the right name")
+ harness.check(str(const.type), type, "Const has the right type")
+ harness.ok(const.type.isPrimitive(), "All consts should be primitive")
+ harness.check(str(const.value.type), str(const.type),
+ "Const's value has the same type as the type")
+ harness.check(const.value.value, value, "Const value has the right value.")
+
+ checkConst(consts[0], "::TestConsts::zero", "zero", "Byte", 0)
+ checkConst(consts[1], "::TestConsts::b", "b", "Byte", -1)
+ checkConst(consts[2], "::TestConsts::o", "o", "Octet", 2)
+ checkConst(consts[3], "::TestConsts::s", "s", "Short", -3)
+ checkConst(consts[4], "::TestConsts::us", "us", "UnsignedShort", 4)
+ checkConst(consts[5], "::TestConsts::l", "l", "Long", -5)
+ checkConst(consts[6], "::TestConsts::ul", "ul", "UnsignedLong", 6)
+ checkConst(consts[7], "::TestConsts::ull", "ull", "UnsignedLongLong", 7)
+ checkConst(consts[8], "::TestConsts::ll", "ll", "LongLong", -8)
+ checkConst(consts[9], "::TestConsts::t", "t", "Boolean", True)
+ checkConst(consts[10], "::TestConsts::f", "f", "Boolean", False)
+ checkConst(consts[11], "::TestConsts::n", "n", "BooleanOrNull", None)
+ checkConst(consts[12], "::TestConsts::nt", "nt", "BooleanOrNull", True)
+ checkConst(consts[13], "::TestConsts::nf", "nf", "BooleanOrNull", False)
+
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py
new file mode 100644
index 00000000000..6ec1be1871b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor.py
@@ -0,0 +1,75 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ def checkArgument(argument, QName, name, type, optional, variadic):
+ harness.ok(isinstance(argument, WebIDL.IDLArgument),
+ "Should be an IDLArgument")
+ harness.check(argument.identifier.QName(), QName, "Argument has the right QName")
+ harness.check(argument.identifier.name, name, "Argument has the right name")
+ harness.check(str(argument.type), type, "Argument has the right return type")
+ harness.check(argument.optional, optional, "Argument has the right optional value")
+ harness.check(argument.variadic, variadic, "Argument has the right variadic value")
+
+ def checkMethod(method, QName, name, signatures,
+ static=False, getter=False, setter=False, creator=False,
+ deleter=False, legacycaller=False, stringifier=False):
+ harness.ok(isinstance(method, WebIDL.IDLMethod),
+ "Should be an IDLMethod")
+ harness.ok(method.isMethod(), "Method is a method")
+ harness.ok(not method.isAttr(), "Method is not an attr")
+ harness.ok(not method.isConst(), "Method is not a const")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(method.isCreator(), creator, "Method has the correct creator value")
+ harness.check(method.isDeleter(), deleter, "Method has the correct deleter value")
+ harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value")
+ harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value")
+ harness.check(len(method.signatures()), len(signatures), "Method has the correct number of signatures")
+
+ sigpairs = zip(method.signatures(), signatures)
+ for (gotSignature, expectedSignature) in sigpairs:
+ (gotRetType, gotArgs) = gotSignature
+ (expectedRetType, expectedArgs) = expectedSignature
+
+ harness.check(str(gotRetType), expectedRetType,
+ "Method has the expected return type.")
+
+ for i in range(0, len(gotArgs)):
+ (QName, name, type, optional, variadic) = expectedArgs[i]
+ checkArgument(gotArgs[i], QName, name, type, optional, variadic)
+
+ parser.parse("""
+ [Constructor]
+ interface TestConstructorNoArgs {
+ };
+
+ [Constructor(DOMString name)]
+ interface TestConstructorWithArgs {
+ };
+
+ [Constructor(object foo), Constructor(boolean bar)]
+ interface TestConstructorOverloads {
+ };
+ """)
+ results = parser.finish()
+ harness.check(len(results), 3, "Should be two productions")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+
+ checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor",
+ "constructor", [("TestConstructorNoArgs (Wrapper)", [])])
+ checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor",
+ "constructor",
+ [("TestConstructorWithArgs (Wrapper)",
+ [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])])
+ checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor",
+ "constructor",
+ [("TestConstructorOverloads (Wrapper)",
+ [("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]),
+ ("TestConstructorOverloads (Wrapper)",
+ [("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])])
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py
new file mode 100644
index 00000000000..192c5f6f97b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_constructor_no_interface_object.py
@@ -0,0 +1,28 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ [Constructor, NoInterfaceObject]
+ interface TestConstructorNoInterfaceObject {
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ [NoInterfaceObject, Constructor]
+ interface TestConstructorNoInterfaceObject {
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py b/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py
new file mode 100644
index 00000000000..6249d36fb8f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_deduplicate.py
@@ -0,0 +1,15 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface Foo;
+ interface Bar;
+ interface Foo;
+ """);
+
+ results = parser.finish()
+
+ # There should be no duplicate interfaces in the result.
+ expectedNames = sorted(['Foo', 'Bar'])
+ actualNames = sorted(map(lambda iface: iface.identifier.name, results))
+ harness.check(actualNames, expectedNames, "Parser shouldn't output duplicate names.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py
new file mode 100644
index 00000000000..9ae9eb2b66f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_dictionary.py
@@ -0,0 +1,198 @@
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ dictionary Dict2 : Dict1 {
+ long child = 5;
+ Dict1 aaandAnother;
+ };
+ dictionary Dict1 {
+ long parent;
+ double otherParent;
+ };
+ """)
+ results = parser.finish()
+
+ dict1 = results[1];
+ dict2 = results[0];
+
+ harness.check(len(dict1.members), 2, "Dict1 has two members")
+ harness.check(len(dict2.members), 2, "Dict2 has four members")
+
+ harness.check(dict1.members[0].identifier.name, "otherParent",
+ "'o' comes before 'p'")
+ harness.check(dict1.members[1].identifier.name, "parent",
+ "'o' really comes before 'p'")
+ harness.check(dict2.members[0].identifier.name, "aaandAnother",
+ "'a' comes before 'c'")
+ harness.check(dict2.members[1].identifier.name, "child",
+ "'a' really comes before 'c'")
+
+ # Now reset our parser
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary Dict {
+ long prop = 5;
+ long prop;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow name duplication in a dictionary")
+
+ # Now reset our parser again
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary Dict1 : Dict2 {
+ long prop = 5;
+ };
+ dictionary Dict2 : Dict3 {
+ long prop2;
+ };
+ dictionary Dict3 {
+ double prop;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow name duplication in a dictionary and "
+ "its ancestor")
+
+ # More reset
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface Iface {};
+ dictionary Dict : Iface {
+ long prop;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow non-dictionary parents for dictionaries")
+
+ # Even more reset
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A : B {};
+ dictionary B : A {};
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow cycles in dictionary inheritance chains")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ [TreatNullAs=EmptyString] DOMString foo;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow [TreatNullAs] on dictionary members");
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ [TreatUndefinedAs=EmptyString] DOMString foo;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow [TreatUndefinedAs] on dictionary members");
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ };
+ interface X {
+ void doFoo(A arg);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Trailing dictionary arg must be optional")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ };
+ interface X {
+ void doFoo(A arg1, optional long arg2);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Dictionary arg followed by optional arg must be optional")
+
+ parser = parser.reset()
+ parser.parse("""
+ dictionary A {
+ };
+ interface X {
+ void doFoo(A arg1, long arg2);
+ };
+ """)
+ results = parser.finish()
+ harness.ok(True, "Dictionary arg followed by required arg can be required")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ };
+ interface X {
+ void doFoo(optional A? arg1);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Dictionary arg must not be nullable")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ dictionary A {
+ };
+ interface X {
+ void doFoo((A or long)? arg1);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Dictionary arg must not be in a nullable union")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py
new file mode 100644
index 00000000000..86847800631
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_distinguishability.py
@@ -0,0 +1,150 @@
+def firstArgType(method):
+ return method.signatures()[0][1][0].type
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ dictionary Dict {
+ };
+ callback interface Foo {
+ };
+ interface Bar {
+ // Bit of a pain to get things that have dictionary types
+ void passDict(optional Dict arg);
+ void passFoo(Foo arg);
+ void passNullableUnion((object? or DOMString) arg);
+ void passNullable(Foo? arg);
+ };
+ """)
+ results = parser.finish()
+
+ iface = results[2]
+ harness.ok(iface.isInterface(), "Should have interface")
+ dictMethod = iface.members[0]
+ ifaceMethod = iface.members[1]
+ nullableUnionMethod = iface.members[2]
+ nullableIfaceMethod = iface.members[3]
+
+ dictType = firstArgType(dictMethod)
+ ifaceType = firstArgType(ifaceMethod)
+
+ harness.ok(dictType.isDictionary(), "Should have dictionary type");
+ harness.ok(ifaceType.isInterface(), "Should have interface type");
+ harness.ok(ifaceType.isCallbackInterface(), "Should have callback interface type");
+
+ harness.ok(not dictType.isDistinguishableFrom(ifaceType),
+ "Dictionary not distinguishable from callback interface")
+ harness.ok(not ifaceType.isDistinguishableFrom(dictType),
+ "Callback interface not distinguishable from dictionary")
+
+ nullableUnionType = firstArgType(nullableUnionMethod)
+ nullableIfaceType = firstArgType(nullableIfaceMethod)
+
+ harness.ok(nullableUnionType.isUnion(), "Should have union type");
+ harness.ok(nullableIfaceType.isInterface(), "Should have interface type");
+ harness.ok(nullableIfaceType.nullable(), "Should have nullable type");
+
+ harness.ok(not nullableUnionType.isDistinguishableFrom(nullableIfaceType),
+ "Nullable type not distinguishable from union with nullable "
+ "member type")
+ harness.ok(not nullableIfaceType.isDistinguishableFrom(nullableUnionType),
+ "Union with nullable member type not distinguishable from "
+ "nullable type")
+
+ parser = parser.reset()
+ parser.parse("""
+ interface TestIface {
+ void passKid(Kid arg);
+ void passParent(Parent arg);
+ void passGrandparent(Grandparent arg);
+ void passImplemented(Implemented arg);
+ void passImplementedParent(ImplementedParent arg);
+ void passUnrelated1(Unrelated1 arg);
+ void passUnrelated2(Unrelated2 arg);
+ void passArrayBuffer(ArrayBuffer arg);
+ void passArrayBuffer(ArrayBufferView arg);
+ };
+
+ interface Kid : Parent {};
+ interface Parent : Grandparent {};
+ interface Grandparent {};
+ interface Implemented : ImplementedParent {};
+ Parent implements Implemented;
+ interface ImplementedParent {};
+ interface Unrelated1 {};
+ interface Unrelated2 {};
+ """)
+ results = parser.finish()
+
+ iface = results[0]
+ harness.ok(iface.isInterface(), "Should have interface")
+ argTypes = [firstArgType(method) for method in iface.members]
+ unrelatedTypes = [firstArgType(method) for method in iface.members[-3:]]
+
+ for type1 in argTypes:
+ for type2 in argTypes:
+ distinguishable = (type1 is not type2 and
+ (type1 in unrelatedTypes or
+ type2 in unrelatedTypes))
+
+ harness.check(type1.isDistinguishableFrom(type2),
+ distinguishable,
+ "Type %s should %sbe distinguishable from type %s" %
+ (type1, "" if distinguishable else "not ", type2))
+ harness.check(type2.isDistinguishableFrom(type1),
+ distinguishable,
+ "Type %s should %sbe distinguishable from type %s" %
+ (type2, "" if distinguishable else "not ", type1))
+
+ parser = parser.reset()
+ parser.parse("""
+ interface Dummy {};
+ interface TestIface {
+ void method(long arg1, TestIface arg2);
+ void method(long arg1, long arg2);
+ void method(long arg1, Dummy arg2);
+ void method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """)
+ results = parser.finish()
+ harness.check(len(results[1].members), 1,
+ "Should look like we have one method")
+ harness.check(len(results[1].members[0].signatures()), 4,
+ "Should have four signatures")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface Dummy {};
+ interface TestIface {
+ void method(long arg1, TestIface arg2);
+ void method(long arg1, long arg2);
+ void method(any arg1, Dummy arg2);
+ void method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should throw when args before the distinguishing arg are not "
+ "all the same type")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface Dummy {};
+ interface TestIface {
+ void method(long arg1, TestIface arg2);
+ void method(long arg1, long arg2);
+ void method(any arg1, DOMString arg2);
+ void method(DOMString arg1, DOMString arg2, DOMString arg3);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should throw when there is no distinguishing index")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_double_null.py b/components/script/dom/bindings/codegen/parser/tests/test_double_null.py
new file mode 100644
index 00000000000..700c7eade00
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_double_null.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface DoubleNull {
+ attribute byte?? foo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py
new file mode 100644
index 00000000000..799f2e0e0ed
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_duplicate_qualifiers.py
@@ -0,0 +1,84 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface DuplicateQualifiers1 {
+ getter getter byte foo(unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface DuplicateQualifiers2 {
+ setter setter byte foo(unsigned long index, byte value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface DuplicateQualifiers3 {
+ creator creator byte foo(unsigned long index, byte value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface DuplicateQualifiers4 {
+ deleter deleter byte foo(unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface DuplicateQualifiers5 {
+ getter deleter getter byte foo(unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ results = parser.parse("""
+ interface DuplicateQualifiers6 {
+ creator setter creator byte foo(unsigned long index, byte value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py b/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py
new file mode 100644
index 00000000000..ee0079f06da
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_empty_enum.py
@@ -0,0 +1,14 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse("""
+ enum TestEmptyEnum {
+ };
+ """)
+
+ harness.ok(False, "Should have thrown!")
+ except:
+ harness.ok(True, "Parsing TestEmptyEnum enum should fail")
+
+ results = parser.finish()
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_enum.py b/components/script/dom/bindings/codegen/parser/tests/test_enum.py
new file mode 100644
index 00000000000..69a6932062d
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_enum.py
@@ -0,0 +1,81 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ enum TestEnum {
+ "",
+ "foo",
+ "bar"
+ };
+
+ interface TestEnumInterface {
+ TestEnum doFoo(boolean arg);
+ readonly attribute TestEnum foo;
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestEnumInterfaces interface parsed without error.")
+ harness.check(len(results), 2, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLEnum),
+ "Should be an IDLEnum")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+
+ enum = results[0]
+ harness.check(enum.identifier.QName(), "::TestEnum", "Enum has the right QName")
+ harness.check(enum.identifier.name, "TestEnum", "Enum has the right name")
+ harness.check(enum.values(), ["", "foo", "bar"], "Enum has the right values")
+
+ iface = results[1]
+
+ harness.check(iface.identifier.QName(), "::TestEnumInterface", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestEnumInterface", "Interface has the right name")
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ members = iface.members
+ harness.check(len(members), 2, "Should be one production")
+ harness.ok(isinstance(members[0], WebIDL.IDLMethod),
+ "Should be an IDLMethod")
+ method = members[0]
+ harness.check(method.identifier.QName(), "::TestEnumInterface::doFoo",
+ "Method has correct QName")
+ harness.check(method.identifier.name, "doFoo", "Method has correct name")
+
+ signatures = method.signatures()
+ harness.check(len(signatures), 1, "Expect one signature")
+
+ (returnType, arguments) = signatures[0]
+ harness.check(str(returnType), "TestEnum (Wrapper)", "Method type is the correct name")
+ harness.check(len(arguments), 1, "Method has the right number of arguments")
+ arg = arguments[0]
+ harness.ok(isinstance(arg, WebIDL.IDLArgument), "Should be an IDLArgument")
+ harness.check(str(arg.type), "Boolean", "Argument has the right type")
+
+ attr = members[1]
+ harness.check(attr.identifier.QName(), "::TestEnumInterface::foo",
+ "Attr has correct QName")
+ harness.check(attr.identifier.name, "foo", "Attr has correct name")
+
+ harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name")
+
+ # Now reset our parser
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ enum Enum {
+ "a",
+ "b",
+ "c"
+ };
+ interface TestInterface {
+ void foo(optional Enum e = "d");
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow a bogus default value for an enum")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py b/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py
new file mode 100644
index 00000000000..51205d209e7
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_enum_duplicate_values.py
@@ -0,0 +1,13 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ try:
+ parser.parse("""
+ enum TestEnumDuplicateValue {
+ "",
+ ""
+ };
+ """)
+ harness.ok(False, "Should have thrown!")
+ except:
+ harness.ok(True, "Enum TestEnumDuplicateValue should throw")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py
new file mode 100644
index 00000000000..ca0674aec04
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_error_colno.py
@@ -0,0 +1,20 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ # Check that error messages put the '^' in the right place.
+
+ threw = False
+ input = 'interface ?'
+ try:
+ parser.parse(input)
+ results = parser.finish()
+ except WebIDL.WebIDLError, e:
+ threw = True
+ lines = str(e).split('\n')
+
+ harness.check(len(lines), 3, 'Expected number of lines in error message')
+ harness.check(lines[1], input, 'Second line shows error')
+ harness.check(lines[2], ' ' * (len(input) - 1) + '^',
+ 'Correct column pointer in error message')
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py
new file mode 100644
index 00000000000..f11222e7a4d
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_error_lineno.py
@@ -0,0 +1,28 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ # Check that error messages put the '^' in the right place.
+
+ threw = False
+ input = """\
+// This is a comment.
+interface Foo {
+};
+
+/* This is also a comment. */
+interface ?"""
+ try:
+ parser.parse(input)
+ results = parser.finish()
+ except WebIDL.WebIDLError, e:
+ threw = True
+ lines = str(e).split('\n')
+
+ harness.check(len(lines), 3, 'Expected number of lines in error message')
+ harness.ok(lines[0].endswith('line 6:10'), 'First line of error should end with "line 6:10", but was "%s".' % lines[0])
+ harness.check(lines[1], 'interface ?', 'Second line of error message is the line which caused the error.')
+ harness.check(lines[2], ' ' * (len('interface ?') - 1) + '^',
+ 'Correct column pointer in error message.')
+
+ harness.ok(threw, "Should have thrown.")
+
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py
new file mode 100644
index 00000000000..5c6887331e7
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_extended_attributes.py
@@ -0,0 +1,107 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ [Flippety]
+ interface TestExtendedAttr {
+ [Foopy] attribute byte b;
+ };
+ """)
+
+ results = parser.finish()
+
+ parser = parser.reset()
+ parser.parse("""
+ [Flippety="foo.bar",Floppety=flop]
+ interface TestExtendedAttr {
+ [Foopy="foo.bar"] attribute byte b;
+ };
+ """)
+
+ results = parser.finish()
+
+ parser = parser.reset()
+ parser.parse("""
+ interface TestLenientThis {
+ [LenientThis] attribute byte b;
+ };
+ """)
+
+ results = parser.finish()
+ harness.ok(results[0].members[0].hasLenientThis(),
+ "Should have a lenient this")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestLenientThis2 {
+ [LenientThis=something] attribute byte b;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "[LenientThis] must take no arguments")
+
+ parser = parser.reset()
+ parser.parse("""
+ interface TestClamp {
+ void testClamp([Clamp] long foo);
+ void testNotClamp(long foo);
+ };
+ """)
+
+ results = parser.finish()
+ # Pull out the first argument out of the arglist of the first (and
+ # only) signature.
+ harness.ok(results[0].members[0].signatures()[0][1][0].clamp,
+ "Should be clamped")
+ harness.ok(not results[0].members[1].signatures()[0][1][0].clamp,
+ "Should not be clamped")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestClamp2 {
+ void testClamp([Clamp=something] long foo);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "[Clamp] must take no arguments")
+
+ parser = parser.reset()
+ parser.parse("""
+ interface TestEnforceRange {
+ void testEnforceRange([EnforceRange] long foo);
+ void testNotEnforceRange(long foo);
+ };
+ """)
+
+ results = parser.finish()
+ # Pull out the first argument out of the arglist of the first (and
+ # only) signature.
+ harness.ok(results[0].members[0].signatures()[0][1][0].enforceRange,
+ "Should be enforceRange")
+ harness.ok(not results[0].members[1].signatures()[0][1][0].enforceRange,
+ "Should not be enforceRange")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestEnforceRange2 {
+ void testEnforceRange([EnforceRange=something] long foo);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "[EnforceRange] must take no arguments")
+
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py b/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py
new file mode 100644
index 00000000000..cac24c832cc
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_forward_decl.py
@@ -0,0 +1,15 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface ForwardDeclared;
+ interface ForwardDeclared;
+
+ interface TestForwardDecl {
+ attribute ForwardDeclared foo;
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestForwardDeclared interface parsed without error.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_implements.py b/components/script/dom/bindings/codegen/parser/tests/test_implements.py
new file mode 100644
index 00000000000..04c47d92abe
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_implements.py
@@ -0,0 +1,216 @@
+# Import the WebIDL module, so we can do isinstance checks and whatnot
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ # Basic functionality
+ threw = False
+ try:
+ parser.parse("""
+ A implements B;
+ interface B {
+ attribute long x;
+ };
+ interface A {
+ attribute long y;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(not threw, "Should not have thrown on implements statement "
+ "before interfaces")
+ harness.check(len(results), 3, "We have three statements")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface")
+ harness.check(len(results[1].members), 1, "B has one member")
+ A = results[2]
+ harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface")
+ harness.check(len(A.members), 2, "A has two members")
+ harness.check(A.members[0].identifier.name, "y", "First member is 'y'")
+ harness.check(A.members[1].identifier.name, "x", "Second member is 'x'")
+
+ # Duplicated member names not allowed
+ threw = False
+ try:
+ parser.parse("""
+ C implements D;
+ interface D {
+ attribute long x;
+ };
+ interface C {
+ attribute long x;
+ };
+ """)
+ parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on implemented interface duplicating "
+ "a name on base interface")
+
+ # Same, but duplicated across implemented interfaces
+ threw = False
+ try:
+ parser.parse("""
+ E implements F;
+ E implements G;
+ interface F {
+ attribute long x;
+ };
+ interface G {
+ attribute long x;
+ };
+ interface E {};
+ """)
+ parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on implemented interfaces "
+ "duplicating each other's member names")
+
+ # Same, but duplicated across indirectly implemented interfaces
+ threw = False
+ try:
+ parser.parse("""
+ H implements I;
+ H implements J;
+ I implements K;
+ interface K {
+ attribute long x;
+ };
+ interface L {
+ attribute long x;
+ };
+ interface I {};
+ interface J : L {};
+ interface H {};
+ """)
+ parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on indirectly implemented interfaces "
+ "duplicating each other's member names")
+
+ # Same, but duplicated across an implemented interface and its parent
+ threw = False
+ try:
+ parser.parse("""
+ M implements N;
+ interface O {
+ attribute long x;
+ };
+ interface N : O {
+ attribute long x;
+ };
+ interface M {};
+ """)
+ parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on implemented interface and its "
+ "ancestor duplicating member names")
+
+ # Reset the parser so we can actually find things where we expect
+ # them in the list
+ parser = parser.reset()
+
+ # Diamonds should be allowed
+ threw = False
+ try:
+ parser.parse("""
+ P implements Q;
+ P implements R;
+ Q implements S;
+ R implements S;
+ interface Q {};
+ interface R {};
+ interface S {
+ attribute long x;
+ };
+ interface P {};
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(not threw, "Diamond inheritance is fine")
+ harness.check(results[6].identifier.name, "S", "We should be looking at 'S'")
+ harness.check(len(results[6].members), 1, "S should have one member")
+ harness.check(results[6].members[0].identifier.name, "x",
+ "S's member should be 'x'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface {
+ };
+ callback interface TestCallbackInterface {
+ };
+ TestInterface implements TestCallbackInterface;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should not allow callback interfaces on the right-hand side "
+ "of 'implements'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface {
+ };
+ callback interface TestCallbackInterface {
+ };
+ TestCallbackInterface implements TestInterface;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should not allow callback interfaces on the left-hand side of "
+ "'implements'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface {
+ };
+ dictionary Dict {
+ };
+ Dict implements TestInterface;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should not allow non-interfaces on the left-hand side "
+ "of 'implements'")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface TestInterface {
+ };
+ dictionary Dict {
+ };
+ TestInterface implements Dict;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should not allow non-interfaces on the right-hand side "
+ "of 'implements'")
+
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py
new file mode 100644
index 00000000000..1f520a28e16
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_parent.py
@@ -0,0 +1,18 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestIncompleteParent : NotYetDefined {
+ void foo();
+ };
+
+ interface NotYetDefined : EvenHigherOnTheChain {
+ };
+
+ interface EvenHigherOnTheChain {
+ };
+ """)
+
+ parser.finish()
+
+ harness.ok(True, "TestIncompleteParent interface parsed without error.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py
new file mode 100644
index 00000000000..fdc39604070
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_incomplete_types.py
@@ -0,0 +1,44 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestIncompleteTypes {
+ attribute FooInterface attr1;
+
+ FooInterface method1(FooInterface arg);
+ };
+
+ interface FooInterface {
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestIncompleteTypes interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestIncompleteTypes", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestIncompleteTypes", "Interface has the right name")
+ harness.check(len(iface.members), 2, "Expect 2 members")
+
+ attr = iface.members[0]
+ harness.ok(isinstance(attr, WebIDL.IDLAttribute),
+ "Should be an IDLAttribute")
+ method = iface.members[1]
+ harness.ok(isinstance(method, WebIDL.IDLMethod),
+ "Should be an IDLMethod")
+
+ harness.check(attr.identifier.QName(), "::TestIncompleteTypes::attr1",
+ "Attribute has the right QName")
+ harness.check(attr.type.name, "FooInterface",
+ "Previously unresolved type has the right name")
+
+ harness.check(method.identifier.QName(), "::TestIncompleteTypes::method1",
+ "Attribute has the right QName")
+ (returnType, args) = method.signatures()[0]
+ harness.check(returnType.name, "FooInterface",
+ "Previously unresolved type has the right name")
+ harness.check(args[0].type.name, "FooInterface",
+ "Previously unresolved type has the right name")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface.py b/components/script/dom/bindings/codegen/parser/tests/test_interface.py
new file mode 100644
index 00000000000..5b07172c636
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_interface.py
@@ -0,0 +1,188 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("interface Foo { };")
+ results = parser.finish()
+ harness.ok(True, "Empty interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ iface = results[0]
+ harness.check(iface.identifier.QName(), "::Foo", "Interface has the right QName")
+ harness.check(iface.identifier.name, "Foo", "Interface has the right name")
+ harness.check(iface.parent, None, "Interface has no parent")
+
+ parser.parse("interface Bar : Foo { };")
+ results = parser.finish()
+ harness.ok(True, "Empty interface parsed without error.")
+ harness.check(len(results), 2, "Should be two productions")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ iface = results[1]
+ harness.check(iface.identifier.QName(), "::Bar", "Interface has the right QName")
+ harness.check(iface.identifier.name, "Bar", "Interface has the right name")
+ harness.ok(isinstance(iface.parent, WebIDL.IDLInterface),
+ "Interface has a parent")
+
+ parser = parser.reset()
+ parser.parse("""
+ interface QNameBase {
+ attribute long foo;
+ };
+
+ interface QNameDerived : QNameBase {
+ attribute long long foo;
+ attribute byte bar;
+ };
+ """)
+ results = parser.finish()
+ harness.check(len(results), 2, "Should be two productions")
+ harness.ok(isinstance(results[0], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.ok(isinstance(results[1], WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(results[1].parent, results[0], "Inheritance chain is right")
+ harness.check(len(results[0].members), 1, "Expect 1 productions")
+ harness.check(len(results[1].members), 2, "Expect 2 productions")
+ base = results[0]
+ derived = results[1]
+ harness.check(base.members[0].identifier.QName(), "::QNameBase::foo",
+ "Member has the right QName")
+ harness.check(derived.members[0].identifier.QName(), "::QNameDerived::foo",
+ "Member has the right QName")
+ harness.check(derived.members[1].identifier.QName(), "::QNameDerived::bar",
+ "Member has the right QName")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : B {};
+ interface B : A {};
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow cycles in interface inheritance chains")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : C {};
+ interface C : B {};
+ interface B : A {};
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow indirect cycles in interface inheritance chains")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A {};
+ interface B {};
+ A implements B;
+ B implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow cycles via implements")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A {};
+ interface C {};
+ interface B {};
+ A implements C;
+ C implements B;
+ B implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow indirect cycles via implements")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : B {};
+ interface B {};
+ B implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow inheriting from an interface that implements us")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : B {};
+ interface B {};
+ interface C {};
+ B implements C;
+ C implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow inheriting from an interface that indirectly implements us")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : B {};
+ interface B : C {};
+ interface C {};
+ C implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow indirectly inheriting from an interface that implements us")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A : B {};
+ interface B : C {};
+ interface C {};
+ interface D {};
+ C implements D;
+ D implements A;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow indirectly inheriting from an interface that indirectly implements us")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A;
+ interface B : A {};
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should not allow inheriting from an interface that is only forward declared")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py b/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py
new file mode 100644
index 00000000000..db944e7aaf7
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_interface_const_identifier_conflicts.py
@@ -0,0 +1,15 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface IdentifierConflict {
+ const byte thing1 = 1;
+ const unsigned long thing1 = 1;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py b/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py
new file mode 100644
index 00000000000..1a73fb917ed
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_interface_identifier_conflicts_across_members.py
@@ -0,0 +1,60 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface IdentifierConflictAcrossMembers1 {
+ const byte thing1 = 1;
+ readonly attribute long thing1;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface IdentifierConflictAcrossMembers2 {
+ readonly attribute long thing1;
+ const byte thing1 = 1;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface IdentifierConflictAcrossMembers3 {
+ getter boolean thing1(DOMString name);
+ readonly attribute long thing1;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface IdentifierConflictAcrossMembers1 {
+ const byte thing1 = 1;
+ long thing1();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_method.py b/components/script/dom/bindings/codegen/parser/tests/test_method.py
new file mode 100644
index 00000000000..40b2d2cf8b9
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_method.py
@@ -0,0 +1,145 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestMethods {
+ void basic();
+ static void basicStatic();
+ void basicWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3);
+ boolean basicBoolean();
+ static boolean basicStaticBoolean();
+ boolean basicBooleanWithSimpleArgs(boolean arg1, byte arg2, unsigned long arg3);
+ void optionalArg(optional byte? arg1, optional sequence<byte> arg2);
+ void variadicArg(byte?... arg1);
+ void crazyTypes(sequence<long?[]>? arg1, boolean?[][]? arg2);
+ object getObject();
+ void setObject(object arg1);
+ void setAny(any arg1);
+ float doFloats(float arg1);
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestMethods interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestMethods", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestMethods", "Interface has the right name")
+ harness.check(len(iface.members), 13, "Expect 13 members")
+
+ methods = iface.members
+
+ def checkArgument(argument, QName, name, type, optional, variadic):
+ harness.ok(isinstance(argument, WebIDL.IDLArgument),
+ "Should be an IDLArgument")
+ harness.check(argument.identifier.QName(), QName, "Argument has the right QName")
+ harness.check(argument.identifier.name, name, "Argument has the right name")
+ harness.check(str(argument.type), type, "Argument has the right return type")
+ harness.check(argument.optional, optional, "Argument has the right optional value")
+ harness.check(argument.variadic, variadic, "Argument has the right variadic value")
+
+ def checkMethod(method, QName, name, signatures,
+ static=False, getter=False, setter=False, creator=False,
+ deleter=False, legacycaller=False, stringifier=False):
+ harness.ok(isinstance(method, WebIDL.IDLMethod),
+ "Should be an IDLMethod")
+ harness.ok(method.isMethod(), "Method is a method")
+ harness.ok(not method.isAttr(), "Method is not an attr")
+ harness.ok(not method.isConst(), "Method is not a const")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(method.isCreator(), creator, "Method has the correct creator value")
+ harness.check(method.isDeleter(), deleter, "Method has the correct deleter value")
+ harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value")
+ harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value")
+ harness.check(len(method.signatures()), len(signatures), "Method has the correct number of signatures")
+
+ sigpairs = zip(method.signatures(), signatures)
+ for (gotSignature, expectedSignature) in sigpairs:
+ (gotRetType, gotArgs) = gotSignature
+ (expectedRetType, expectedArgs) = expectedSignature
+
+ harness.check(str(gotRetType), expectedRetType,
+ "Method has the expected return type.")
+
+ for i in range(0, len(gotArgs)):
+ (QName, name, type, optional, variadic) = expectedArgs[i]
+ checkArgument(gotArgs[i], QName, name, type, optional, variadic)
+
+ checkMethod(methods[0], "::TestMethods::basic", "basic", [("Void", [])])
+ checkMethod(methods[1], "::TestMethods::basicStatic", "basicStatic",
+ [("Void", [])], static=True)
+ checkMethod(methods[2], "::TestMethods::basicWithSimpleArgs",
+ "basicWithSimpleArgs",
+ [("Void",
+ [("::TestMethods::basicWithSimpleArgs::arg1", "arg1", "Boolean", False, False),
+ ("::TestMethods::basicWithSimpleArgs::arg2", "arg2", "Byte", False, False),
+ ("::TestMethods::basicWithSimpleArgs::arg3", "arg3", "UnsignedLong", False, False)])])
+ checkMethod(methods[3], "::TestMethods::basicBoolean", "basicBoolean", [("Boolean", [])])
+ checkMethod(methods[4], "::TestMethods::basicStaticBoolean", "basicStaticBoolean", [("Boolean", [])], static=True)
+ checkMethod(methods[5], "::TestMethods::basicBooleanWithSimpleArgs",
+ "basicBooleanWithSimpleArgs",
+ [("Boolean",
+ [("::TestMethods::basicBooleanWithSimpleArgs::arg1", "arg1", "Boolean", False, False),
+ ("::TestMethods::basicBooleanWithSimpleArgs::arg2", "arg2", "Byte", False, False),
+ ("::TestMethods::basicBooleanWithSimpleArgs::arg3", "arg3", "UnsignedLong", False, False)])])
+ checkMethod(methods[6], "::TestMethods::optionalArg",
+ "optionalArg",
+ [("Void",
+ [("::TestMethods::optionalArg::arg1", "arg1", "ByteOrNull", True, False),
+ ("::TestMethods::optionalArg::arg2", "arg2", "ByteSequence", True, False)])])
+ checkMethod(methods[7], "::TestMethods::variadicArg",
+ "variadicArg",
+ [("Void",
+ [("::TestMethods::variadicArg::arg1", "arg1", "ByteOrNull", True, True)])])
+ checkMethod(methods[8], "::TestMethods::crazyTypes",
+ "crazyTypes",
+ [("Void",
+ [("::TestMethods::crazyTypes::arg1", "arg1", "LongOrNullArraySequenceOrNull", False, False),
+ ("::TestMethods::crazyTypes::arg2", "arg2", "BooleanOrNullArrayArrayOrNull", False, False)])])
+ checkMethod(methods[9], "::TestMethods::getObject",
+ "getObject", [("Object", [])])
+ checkMethod(methods[10], "::TestMethods::setObject",
+ "setObject",
+ [("Void",
+ [("::TestMethods::setObject::arg1", "arg1", "Object", False, False)])])
+ checkMethod(methods[11], "::TestMethods::setAny",
+ "setAny",
+ [("Void",
+ [("::TestMethods::setAny::arg1", "arg1", "Any", False, False)])])
+ checkMethod(methods[12], "::TestMethods::doFloats",
+ "doFloats",
+ [("Float",
+ [("::TestMethods::doFloats::arg1", "arg1", "Float", False, False)])])
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A {
+ [GetterInfallible] void foo();
+ };
+ """)
+ results = parser.finish()
+ except Exception, x:
+ threw = True
+ harness.ok(threw, "Should not allow [GetterInfallible] on methods")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface A {
+ [SetterInfallible] void foo();
+ };
+ """)
+ results = parser.finish()
+ except Exception, x:
+ threw = True
+ harness.ok(threw, "Should not allow [SetterInfallible] on methods")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py
new file mode 100644
index 00000000000..3366b9fbbbd
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_nullable_equivalency.py
@@ -0,0 +1,126 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestNullableEquivalency1 {
+ attribute long a;
+ attribute long? b;
+ };
+
+ interface TestNullableEquivalency2 {
+ attribute ArrayBuffer a;
+ attribute ArrayBuffer? b;
+ };
+
+ /* Can't have dictionary-valued attributes, so can't test that here */
+
+ enum TestNullableEquivalency4Enum {
+ "Foo",
+ "Bar"
+ };
+
+ interface TestNullableEquivalency4 {
+ attribute TestNullableEquivalency4Enum a;
+ attribute TestNullableEquivalency4Enum? b;
+ };
+
+ interface TestNullableEquivalency5 {
+ attribute TestNullableEquivalency4 a;
+ attribute TestNullableEquivalency4? b;
+ };
+
+ interface TestNullableEquivalency6 {
+ attribute boolean a;
+ attribute boolean? b;
+ };
+
+ interface TestNullableEquivalency7 {
+ attribute DOMString a;
+ attribute DOMString? b;
+ };
+
+ /* Not implemented. */
+ /*interface TestNullableEquivalency8 {
+ attribute float a;
+ attribute float? b;
+ };*/
+
+ interface TestNullableEquivalency8 {
+ attribute double a;
+ attribute double? b;
+ };
+
+ interface TestNullableEquivalency9 {
+ attribute object a;
+ attribute object? b;
+ };
+
+ interface TestNullableEquivalency10 {
+ attribute double[] a;
+ attribute double[]? b;
+ };
+
+ interface TestNullableEquivalency11 {
+ attribute TestNullableEquivalency9[] a;
+ attribute TestNullableEquivalency9[]? b;
+ };
+ """)
+
+ for decl in parser.finish():
+ if decl.isInterface():
+ checkEquivalent(decl, harness)
+
+def checkEquivalent(iface, harness):
+ type1 = iface.members[0].type
+ type2 = iface.members[1].type
+
+ harness.check(type1.nullable(), False, 'attr1 should not be nullable')
+ harness.check(type2.nullable(), True, 'attr2 should be nullable')
+
+ # We don't know about type1, but type2, the nullable type, definitely
+ # shouldn't be builtin.
+ harness.check(type2.builtin, False, 'attr2 should not be builtin')
+
+ # Ensure that all attributes of type2 match those in type1, except for:
+ # - names on an ignore list,
+ # - names beginning with '_',
+ # - functions which throw when called with no args, and
+ # - class-level non-callables ("static variables").
+ #
+ # Yes, this is an ugly, fragile hack. But it finds bugs...
+ for attr in dir(type1):
+ if attr.startswith('_') or \
+ attr in ['nullable', 'builtin', 'filename', 'location',
+ 'inner', 'QName'] or \
+ (hasattr(type(type1), attr) and not callable(getattr(type1, attr))):
+ continue
+
+ a1 = getattr(type1, attr)
+
+ if callable(a1):
+ try:
+ v1 = a1()
+ except:
+ # Can't call a1 with no args, so skip this attriute.
+ continue
+
+ try:
+ a2 = getattr(type2, attr)
+ except:
+ harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
+ continue
+
+ if not callable(a2):
+ harness.ok(False, "%s attribute on type %s in %s wasn't callable" % (attr, type2, iface))
+ continue
+
+ v2 = a2()
+ harness.check(v2, v1, '%s method return value' % attr)
+ else:
+ try:
+ a2 = getattr(type2, attr)
+ except:
+ harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
+ continue
+
+ harness.check(a2, a1, '%s attribute should match' % attr)
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py b/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py
new file mode 100644
index 00000000000..961ff825e9f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_nullable_void.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface NullableVoid {
+ void? foo();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py b/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py
new file mode 100644
index 00000000000..1dcdc7fb8a5
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_optional_constraints.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface OptionalConstraints1 {
+ void foo(optional byte arg1, byte arg2);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_overload.py b/components/script/dom/bindings/codegen/parser/tests/test_overload.py
new file mode 100644
index 00000000000..59d9be54e53
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_overload.py
@@ -0,0 +1,47 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface TestOverloads {
+ void basic();
+ void basic(long arg1);
+ boolean abitharder(TestOverloads foo);
+ boolean abitharder(boolean foo);
+ void abitharder(ArrayBuffer? foo);
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestOverloads interface parsed without error.")
+ harness.check(len(results), 1, "Should be one production.")
+ iface = results[0]
+ harness.ok(isinstance(iface, WebIDL.IDLInterface),
+ "Should be an IDLInterface")
+ harness.check(iface.identifier.QName(), "::TestOverloads", "Interface has the right QName")
+ harness.check(iface.identifier.name, "TestOverloads", "Interface has the right name")
+ harness.check(len(iface.members), 2, "Expect %s members" % 2)
+
+ member = iface.members[0]
+ harness.check(member.identifier.QName(), "::TestOverloads::basic", "Method has the right QName")
+ harness.check(member.identifier.name, "basic", "Method has the right name")
+ harness.check(member.hasOverloads(), True, "Method has overloads")
+
+ signatures = member.signatures()
+ harness.check(len(signatures), 2, "Method should have 2 signatures")
+
+ (retval, argumentSet) = signatures[0]
+
+ harness.check(str(retval), "Void", "Expect a void retval")
+ harness.check(len(argumentSet), 0, "Expect an empty argument set")
+
+ (retval, argumentSet) = signatures[1]
+ harness.check(str(retval), "Void", "Expect a void retval")
+ harness.check(len(argumentSet), 1, "Expect an argument set with one argument")
+
+ argument = argumentSet[0]
+ harness.ok(isinstance(argument, WebIDL.IDLArgument),
+ "Should be an IDLArgument")
+ harness.check(argument.identifier.QName(), "::TestOverloads::basic::arg1", "Argument has the right QName")
+ harness.check(argument.identifier.name, "arg1", "Argument has the right name")
+ harness.check(str(argument.type), "Long", "Argument has the right type")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_sanity.py b/components/script/dom/bindings/codegen/parser/tests/test_sanity.py
new file mode 100644
index 00000000000..d3184c00731
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_sanity.py
@@ -0,0 +1,7 @@
+def WebIDLTest(parser, harness):
+ parser.parse("")
+ parser.finish()
+ harness.ok(True, "Parsing nothing doesn't throw.")
+ parser.parse("interface Foo {};")
+ parser.finish()
+ harness.ok(True, "Parsing a silly interface doesn't throw.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py
new file mode 100644
index 00000000000..5ea1743d36a
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_special_method_signature_mismatch.py
@@ -0,0 +1,294 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch1 {
+ getter long long foo(long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch2 {
+ getter void foo(unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch3 {
+ getter boolean foo(unsigned long index, boolean extraArg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch4 {
+ getter boolean foo(unsigned long... index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch5 {
+ getter boolean foo(optional unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch6 {
+ getter boolean foo();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch7 {
+ deleter long long foo(long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch9 {
+ deleter boolean foo(unsigned long index, boolean extraArg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch10 {
+ deleter boolean foo(unsigned long... index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch11 {
+ deleter boolean foo(optional unsigned long index);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch12 {
+ deleter boolean foo();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch13 {
+ setter long long foo(long index, long long value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch15 {
+ setter boolean foo(unsigned long index, boolean value, long long extraArg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch16 {
+ setter boolean foo(unsigned long index, boolean... value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch17 {
+ setter boolean foo(unsigned long index, optional boolean value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch18 {
+ setter boolean foo();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch20 {
+ creator long long foo(long index, long long value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch22 {
+ creator boolean foo(unsigned long index, boolean value, long long extraArg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch23 {
+ creator boolean foo(unsigned long index, boolean... value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch24 {
+ creator boolean foo(unsigned long index, optional boolean value);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodSignatureMismatch25 {
+ creator boolean foo();
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py
new file mode 100644
index 00000000000..695cfe4f250
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods.py
@@ -0,0 +1,73 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ interface SpecialMethods {
+ getter long long (unsigned long index);
+ setter long long (unsigned long index, long long value);
+ creator long long (unsigned long index, long long value);
+ deleter long long (unsigned long index);
+ getter boolean (DOMString name);
+ setter boolean (DOMString name, boolean value);
+ creator boolean (DOMString name, boolean value);
+ deleter boolean (DOMString name);
+ };
+
+ interface SpecialMethodsCombination {
+ getter deleter long long (unsigned long index);
+ setter creator long long (unsigned long index, long long value);
+ getter deleter boolean (DOMString name);
+ setter creator boolean (DOMString name, boolean value);
+ };
+ """)
+
+ results = parser.finish()
+
+ def checkMethod(method, QName, name,
+ static=False, getter=False, setter=False, creator=False,
+ deleter=False, legacycaller=False, stringifier=False):
+ harness.ok(isinstance(method, WebIDL.IDLMethod),
+ "Should be an IDLMethod")
+ harness.check(method.identifier.QName(), QName, "Method has the right QName")
+ harness.check(method.identifier.name, name, "Method has the right name")
+ harness.check(method.isStatic(), static, "Method has the correct static value")
+ harness.check(method.isGetter(), getter, "Method has the correct getter value")
+ harness.check(method.isSetter(), setter, "Method has the correct setter value")
+ harness.check(method.isCreator(), creator, "Method has the correct creator value")
+ harness.check(method.isDeleter(), deleter, "Method has the correct deleter value")
+ harness.check(method.isLegacycaller(), legacycaller, "Method has the correct legacycaller value")
+ harness.check(method.isStringifier(), stringifier, "Method has the correct stringifier value")
+
+ harness.check(len(results), 2, "Expect 2 interfaces")
+
+ iface = results[0]
+ harness.check(len(iface.members), 8, "Expect 8 members")
+
+ checkMethod(iface.members[0], "::SpecialMethods::__indexedgetter", "__indexedgetter",
+ getter=True)
+ checkMethod(iface.members[1], "::SpecialMethods::__indexedsetter", "__indexedsetter",
+ setter=True)
+ checkMethod(iface.members[2], "::SpecialMethods::__indexedcreator", "__indexedcreator",
+ creator=True)
+ checkMethod(iface.members[3], "::SpecialMethods::__indexeddeleter", "__indexeddeleter",
+ deleter=True)
+ checkMethod(iface.members[4], "::SpecialMethods::__namedgetter", "__namedgetter",
+ getter=True)
+ checkMethod(iface.members[5], "::SpecialMethods::__namedsetter", "__namedsetter",
+ setter=True)
+ checkMethod(iface.members[6], "::SpecialMethods::__namedcreator", "__namedcreator",
+ creator=True)
+ checkMethod(iface.members[7], "::SpecialMethods::__nameddeleter", "__nameddeleter",
+ deleter=True)
+
+ iface = results[1]
+ harness.check(len(iface.members), 4, "Expect 4 members")
+
+ checkMethod(iface.members[0], "::SpecialMethodsCombination::__indexedgetterdeleter",
+ "__indexedgetterdeleter", getter=True, deleter=True)
+ checkMethod(iface.members[1], "::SpecialMethodsCombination::__indexedsettercreator",
+ "__indexedsettercreator", setter=True, creator=True)
+ checkMethod(iface.members[2], "::SpecialMethodsCombination::__namedgetterdeleter",
+ "__namedgetterdeleter", getter=True, deleter=True)
+ checkMethod(iface.members[3], "::SpecialMethodsCombination::__namedsettercreator",
+ "__namedsettercreator", setter=True, creator=True)
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py
new file mode 100644
index 00000000000..42e2c5bb71b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_special_methods_uniqueness.py
@@ -0,0 +1,62 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodUniqueness1 {
+ getter deleter boolean (DOMString name);
+ getter boolean (DOMString name);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodUniqueness1 {
+ deleter boolean (DOMString name);
+ getter deleter boolean (DOMString name);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodUniqueness1 {
+ setter creator boolean (DOMString name);
+ creator boolean (DOMString name);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ parser.parse("""
+ interface SpecialMethodUniqueness1 {
+ setter boolean (DOMString name);
+ creator setter boolean (DOMString name);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py b/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py
new file mode 100644
index 00000000000..3d0e5ca479f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_treatNonCallableAsNull.py
@@ -0,0 +1,56 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ callback Function = any(any... arguments);
+
+ interface TestTreatNonCallableAsNull1 {
+ [TreatNonCallableAsNull] attribute Function? onfoo;
+ attribute Function? onbar;
+ };
+ """)
+
+ results = parser.finish()
+
+ iface = results[1]
+ attr = iface.members[0]
+ harness.check(attr.type.treatNonCallableAsNull(), True, "Got the expected value")
+ attr = iface.members[1]
+ harness.check(attr.type.treatNonCallableAsNull(), False, "Got the expected value")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse("""
+ callback Function = any(any... arguments);
+
+ interface TestTreatNonCallableAsNull2 {
+ [TreatNonCallableAsNull] attribute Function onfoo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
+
+ threw = False
+ try:
+ parser.parse("""
+ callback Function = any(any... arguments);
+
+ [TreatNonCallableAsNull]
+ interface TestTreatNonCallableAsNull3 {
+ attribute Function onfoo;
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_typedef.py b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py
new file mode 100644
index 00000000000..9d2f3b3c2ce
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_typedef.py
@@ -0,0 +1,76 @@
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ typedef long mylong;
+ typedef long? mynullablelong;
+ interface Foo {
+ const mylong X = 5;
+ const mynullablelong Y = 7;
+ const mynullablelong Z = null;
+ void foo(mylong arg);
+ };
+ """)
+
+ results = parser.finish()
+
+ harness.check(results[2].members[1].type.name, "Long",
+ "Should expand typedefs")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ typedef long? mynullablelong;
+ interface Foo {
+ void foo(mynullablelong? Y);
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on nullable inside nullable arg.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ typedef long? mynullablelong;
+ interface Foo {
+ const mynullablelong? X = 5;
+ };
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown on nullable inside nullable const.")
+
+ parser = parser.reset()
+ threw = False
+ try:
+ parser.parse("""
+ interface Foo {
+ const mynullablelong? X = 5;
+ };
+ typedef long? mynullablelong;
+ """)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Should have thrown on nullable inside nullable const typedef "
+ "after interface.")
+
+ parser = parser.reset()
+ parser.parse("""
+ interface Foo {
+ const mylong X = 5;
+ };
+ typedef long mylong;
+ """)
+
+ results = parser.finish()
+
+ harness.check(results[0].members[0].type.name, "Long",
+ "Should expand typedefs that come before interface")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union.py b/components/script/dom/bindings/codegen/parser/tests/test_union.py
new file mode 100644
index 00000000000..68c2bcade8c
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_union.py
@@ -0,0 +1,169 @@
+import WebIDL
+import itertools
+import string
+
+# We'd like to use itertools.chain but it's 2.6 or higher.
+def chain(*iterables):
+ # chain('ABC', 'DEF') --> A B C D E F
+ for it in iterables:
+ for element in it:
+ yield element
+
+# We'd like to use itertools.combinations but it's 2.6 or higher.
+def combinations(iterable, r):
+ # combinations('ABCD', 2) --> AB AC AD BC BD CD
+ # combinations(range(4), 3) --> 012 013 023 123
+ pool = tuple(iterable)
+ n = len(pool)
+ if r > n:
+ return
+ indices = range(r)
+ yield tuple(pool[i] for i in indices)
+ while True:
+ for i in reversed(range(r)):
+ if indices[i] != i + n - r:
+ break
+ else:
+ return
+ indices[i] += 1
+ for j in range(i+1, r):
+ indices[j] = indices[j-1] + 1
+ yield tuple(pool[i] for i in indices)
+
+# We'd like to use itertools.combinations_with_replacement but it's 2.7 or
+# higher.
+def combinations_with_replacement(iterable, r):
+ # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
+ pool = tuple(iterable)
+ n = len(pool)
+ if not n and r:
+ return
+ indices = [0] * r
+ yield tuple(pool[i] for i in indices)
+ while True:
+ for i in reversed(range(r)):
+ if indices[i] != n - 1:
+ break
+ else:
+ return
+ indices[i:] = [indices[i] + 1] * (r - i)
+ yield tuple(pool[i] for i in indices)
+
+def WebIDLTest(parser, harness):
+ types = ["float",
+ "double",
+ "short",
+ "unsigned short",
+ "long",
+ "unsigned long",
+ "long long",
+ "unsigned long long",
+ "boolean",
+ "byte",
+ "octet",
+ "DOMString",
+ #"sequence<float>",
+ "object",
+ "ArrayBuffer",
+ #"Date",
+ "TestInterface1",
+ "TestInterface2"]
+
+ testPre = """
+ interface TestInterface1 {
+ };
+ interface TestInterface2 {
+ };
+ """
+
+ interface = testPre + """
+ interface PrepareForTest {
+ """
+ for (i, type) in enumerate(types):
+ interface += string.Template("""
+ readonly attribute ${type} attr${i};
+ """).substitute(i=i, type=type)
+ interface += """
+ };
+ """
+
+ parser.parse(interface)
+ results = parser.finish()
+
+ iface = results[2]
+
+ parser = parser.reset()
+
+ def typesAreDistinguishable(t):
+ return all(u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2))
+ def typesAreNotDistinguishable(t):
+ return any(not u[0].isDistinguishableFrom(u[1]) for u in combinations(t, 2))
+ def unionTypeName(t):
+ if len(t) > 2:
+ t[0:2] = [unionTypeName(t[0:2])]
+ return "(" + " or ".join(t) + ")"
+
+ # typeCombinations is an iterable of tuples containing the name of the type
+ # as a string and the parsed IDL type.
+ def unionTypes(typeCombinations, predicate):
+ for c in typeCombinations:
+ if predicate(t[1] for t in c):
+ yield unionTypeName([t[0] for t in c])
+
+ # We limit invalid union types with a union member type to the subset of 3
+ # types with one invalid combination.
+ # typeCombinations is an iterable of tuples containing the name of the type
+ # as a string and the parsed IDL type.
+ def invalidUnionWithUnion(typeCombinations):
+ for c in typeCombinations:
+ if (typesAreNotDistinguishable((c[0][1], c[1][1])) and
+ typesAreDistinguishable((c[1][1], c[2][1])) and
+ typesAreDistinguishable((c[0][1], c[2][1]))):
+ yield unionTypeName([t[0] for t in c])
+
+ # Create a list of tuples containing the name of the type as a string and
+ # the parsed IDL type.
+ types = zip(types, (a.type for a in iface.members))
+
+ validUnionTypes = chain(unionTypes(combinations(types, 2), typesAreDistinguishable),
+ unionTypes(combinations(types, 3), typesAreDistinguishable))
+ invalidUnionTypes = chain(unionTypes(combinations_with_replacement(types, 2), typesAreNotDistinguishable),
+ invalidUnionWithUnion(combinations(types, 3)))
+ interface = testPre + """
+ interface TestUnion {
+ """
+ for (i, type) in enumerate(validUnionTypes):
+ interface += string.Template("""
+ void method${i}(${type} arg);
+ ${type} returnMethod${i}();
+ attribute ${type} attr${i};
+ void arrayMethod${i}(${type}[] arg);
+ ${type}[] arrayReturnMethod${i}();
+ attribute ${type}[] arrayAttr${i};
+ void optionalMethod${i}(${type}? arg);
+ """).substitute(i=i, type=type)
+ interface += """
+ };
+ """
+ parser.parse(interface)
+ results = parser.finish()
+
+ parser = parser.reset()
+
+ for invalid in invalidUnionTypes:
+ interface = testPre + string.Template("""
+ interface TestUnion {
+ void method(${type} arg);
+ };
+ """).substitute(type=invalid)
+
+ threw = False
+ try:
+ parser.parse(interface)
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ parser = parser.reset()
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union_any.py b/components/script/dom/bindings/codegen/parser/tests/test_union_any.py
new file mode 100644
index 00000000000..e34cadab470
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_union_any.py
@@ -0,0 +1,14 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface AnyNotInUnion {
+ void foo((any or DOMString) arg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py b/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py
new file mode 100644
index 00000000000..08430a94a2e
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_union_nullable.py
@@ -0,0 +1,53 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ parser.parse("""
+ interface OneNullableInUnion {
+ void foo((object? or DOMString?) arg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "Two nullable member types of a union should have thrown.")
+
+ parser.reset()
+ threw = False
+
+ try:
+ parser.parse("""
+ interface NullableInNullableUnion {
+ void foo((object? or DOMString)? arg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "A nullable union type with a nullable member type should have "
+ "thrown.")
+
+ parser.reset()
+ threw = False
+
+ try:
+ parser.parse("""
+ interface NullableInUnionNullableUnionHelper {
+ };
+ interface NullableInUnionNullableUnion {
+ void foo(((object? or DOMString) or NullableInUnionNullableUnionHelper)? arg);
+ };
+ """)
+
+ results = parser.finish()
+ except:
+ threw = True
+
+ harness.ok(threw,
+ "A nullable union type with a nullable member type should have "
+ "thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py b/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py
new file mode 100644
index 00000000000..d9a78db2043
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_variadic_callback.py
@@ -0,0 +1,10 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+ parser.parse("""
+ callback TestVariadicCallback = any(any... arguments);
+ """)
+
+ results = parser.finish()
+
+ harness.ok(True, "TestVariadicCallback callback parsed without error.")
diff --git a/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py b/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py
new file mode 100644
index 00000000000..9cba22c5842
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/tests/test_variadic_constraints.py
@@ -0,0 +1,39 @@
+def WebIDLTest(parser, harness):
+ threw = False
+ try:
+ results = parser.parse("""
+ interface VariadicConstraints1 {
+ void foo(byte... arg1, byte arg2);
+ };
+ """)
+
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ results = parser.parse("""
+ interface VariadicConstraints2 {
+ void foo(byte... arg1, optional byte arg2);
+ };
+ """)
+
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
+
+ threw = False
+ try:
+ results = parser.parse("""
+ interface VariadicConstraints3 {
+ void foo(optional byte... arg1);
+ };
+ """)
+
+ except:
+ threw = True
+
+ harness.ok(threw, "Should have thrown.")
diff --git a/components/script/dom/bindings/codegen/parser/update.sh b/components/script/dom/bindings/codegen/parser/update.sh
new file mode 100755
index 00000000000..5dd513812e1
--- /dev/null
+++ b/components/script/dom/bindings/codegen/parser/update.sh
@@ -0,0 +1,3 @@
+wget https://mxr.mozilla.org/mozilla-central/source/dom/bindings/parser/WebIDL.py?raw=1 -O WebIDL.py
+patch < external.patch
+patch < module.patch
diff --git a/components/script/dom/bindings/codegen/ply/COPYING b/components/script/dom/bindings/codegen/ply/COPYING
new file mode 100644
index 00000000000..3b107de4508
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ply/COPYING
@@ -0,0 +1,28 @@
+Copyright (C) 2001-2009,
+David M. Beazley (Dabeaz LLC)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+* Neither the name of the David Beazley or Dabeaz LLC may be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/components/script/dom/bindings/codegen/ply/README b/components/script/dom/bindings/codegen/ply/README
new file mode 100644
index 00000000000..2459c490197
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ply/README
@@ -0,0 +1,9 @@
+David Beazley's PLY (Python Lex-Yacc)
+http://www.dabeaz.com/ply/
+
+Licensed under BSD.
+
+This directory contains just the code and license from PLY version 3.3;
+the full distribution (see the URL) also contains examples, tests,
+documentation, and a longer README.
+
diff --git a/components/script/dom/bindings/codegen/ply/ply/__init__.py b/components/script/dom/bindings/codegen/ply/ply/__init__.py
new file mode 100644
index 00000000000..853a985542b
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ply/ply/__init__.py
@@ -0,0 +1,4 @@
+# PLY package
+# Author: David Beazley (dave@dabeaz.com)
+
+__all__ = ['lex','yacc']
diff --git a/components/script/dom/bindings/codegen/ply/ply/lex.py b/components/script/dom/bindings/codegen/ply/ply/lex.py
new file mode 100644
index 00000000000..267ec100fc2
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ply/ply/lex.py
@@ -0,0 +1,1058 @@
+# -----------------------------------------------------------------------------
+# ply: lex.py
+#
+# Copyright (C) 2001-2009,
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
+
+__version__ = "3.3"
+__tabversion__ = "3.2" # Version of table file used
+
+import re, sys, types, copy, os
+
+# This tuple contains known string types
+try:
+ # Python 2.6
+ StringTypes = (types.StringType, types.UnicodeType)
+except AttributeError:
+ # Python 3.0
+ StringTypes = (str, bytes)
+
+# Extract the code attribute of a function. Different implementations
+# are for Python 2/3 compatibility.
+
+if sys.version_info[0] < 3:
+ def func_code(f):
+ return f.func_code
+else:
+ def func_code(f):
+ return f.__code__
+
+# This regular expression is used to match valid token names
+_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')
+
+# Exception thrown when invalid token encountered and no default error
+# handler is defined.
+
+class LexError(Exception):
+ def __init__(self,message,s):
+ self.args = (message,)
+ self.text = s
+
+# Token class. This class is used to represent the tokens produced.
+class LexToken(object):
+ def __str__(self):
+ return "LexToken(%s,%r,%d,%d)" % (self.type,self.value,self.lineno,self.lexpos)
+ def __repr__(self):
+ return str(self)
+
+# This object is a stand-in for a logging object created by the
+# logging module.
+
+class PlyLogger(object):
+ def __init__(self,f):
+ self.f = f
+ def critical(self,msg,*args,**kwargs):
+ self.f.write((msg % args) + "\n")
+
+ def warning(self,msg,*args,**kwargs):
+ self.f.write("WARNING: "+ (msg % args) + "\n")
+
+ def error(self,msg,*args,**kwargs):
+ self.f.write("ERROR: " + (msg % args) + "\n")
+
+ info = critical
+ debug = critical
+
+# Null logger is used when no output is generated. Does nothing.
+class NullLogger(object):
+ def __getattribute__(self,name):
+ return self
+ def __call__(self,*args,**kwargs):
+ return self
+
+# -----------------------------------------------------------------------------
+# === Lexing Engine ===
+#
+# The following Lexer class implements the lexer runtime. There are only
+# a few public methods and attributes:
+#
+# input() - Store a new string in the lexer
+# token() - Get the next token
+# clone() - Clone the lexer
+#
+# lineno - Current line number
+# lexpos - Current position in the input string
+# -----------------------------------------------------------------------------
+
+class Lexer:
+ def __init__(self):
+ self.lexre = None # Master regular expression. This is a list of
+ # tuples (re,findex) where re is a compiled
+ # regular expression and findex is a list
+ # mapping regex group numbers to rules
+ self.lexretext = None # Current regular expression strings
+ self.lexstatere = {} # Dictionary mapping lexer states to master regexs
+ self.lexstateretext = {} # Dictionary mapping lexer states to regex strings
+ self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names
+ self.lexstate = "INITIAL" # Current lexer state
+ self.lexstatestack = [] # Stack of lexer states
+ self.lexstateinfo = None # State information
+ self.lexstateignore = {} # Dictionary of ignored characters for each state
+ self.lexstateerrorf = {} # Dictionary of error functions for each state
+ self.lexreflags = 0 # Optional re compile flags
+ self.lexdata = None # Actual input data (as a string)
+ self.lexpos = 0 # Current position in input text
+ self.lexlen = 0 # Length of the input text
+ self.lexerrorf = None # Error rule (if any)
+ self.lextokens = None # List of valid tokens
+ self.lexignore = "" # Ignored characters
+ self.lexliterals = "" # Literal characters that can be passed through
+ self.lexmodule = None # Module
+ self.lineno = 1 # Current line number
+ self.lexoptimize = 0 # Optimized mode
+
+ def clone(self,object=None):
+ c = copy.copy(self)
+
+ # If the object parameter has been supplied, it means we are attaching the
+ # lexer to a new object. In this case, we have to rebind all methods in
+ # the lexstatere and lexstateerrorf tables.
+
+ if object:
+ newtab = { }
+ for key, ritem in self.lexstatere.items():
+ newre = []
+ for cre, findex in ritem:
+ newfindex = []
+ for f in findex:
+ if not f or not f[0]:
+ newfindex.append(f)
+ continue
+ newfindex.append((getattr(object,f[0].__name__),f[1]))
+ newre.append((cre,newfindex))
+ newtab[key] = newre
+ c.lexstatere = newtab
+ c.lexstateerrorf = { }
+ for key, ef in self.lexstateerrorf.items():
+ c.lexstateerrorf[key] = getattr(object,ef.__name__)
+ c.lexmodule = object
+ return c
+
+ # ------------------------------------------------------------
+ # writetab() - Write lexer information to a table file
+ # ------------------------------------------------------------
+ def writetab(self,tabfile,outputdir=""):
+ if isinstance(tabfile,types.ModuleType):
+ return
+ basetabfilename = tabfile.split(".")[-1]
+ filename = os.path.join(outputdir,basetabfilename)+".py"
+ tf = open(filename,"w")
+ tf.write("# %s.py. This file automatically created by PLY (version %s). Don't edit!\n" % (tabfile,__version__))
+ tf.write("_tabversion = %s\n" % repr(__version__))
+ tf.write("_lextokens = %s\n" % repr(self.lextokens))
+ tf.write("_lexreflags = %s\n" % repr(self.lexreflags))
+ tf.write("_lexliterals = %s\n" % repr(self.lexliterals))
+ tf.write("_lexstateinfo = %s\n" % repr(self.lexstateinfo))
+
+ tabre = { }
+ # Collect all functions in the initial state
+ initial = self.lexstatere["INITIAL"]
+ initialfuncs = []
+ for part in initial:
+ for f in part[1]:
+ if f and f[0]:
+ initialfuncs.append(f)
+
+ for key, lre in self.lexstatere.items():
+ titem = []
+ for i in range(len(lre)):
+ titem.append((self.lexstateretext[key][i],_funcs_to_names(lre[i][1],self.lexstaterenames[key][i])))
+ tabre[key] = titem
+
+ tf.write("_lexstatere = %s\n" % repr(tabre))
+ tf.write("_lexstateignore = %s\n" % repr(self.lexstateignore))
+
+ taberr = { }
+ for key, ef in self.lexstateerrorf.items():
+ if ef:
+ taberr[key] = ef.__name__
+ else:
+ taberr[key] = None
+ tf.write("_lexstateerrorf = %s\n" % repr(taberr))
+ tf.close()
+
+ # ------------------------------------------------------------
+ # readtab() - Read lexer information from a tab file
+ # ------------------------------------------------------------
+ def readtab(self,tabfile,fdict):
+ if isinstance(tabfile,types.ModuleType):
+ lextab = tabfile
+ else:
+ if sys.version_info[0] < 3:
+ exec("import %s as lextab" % tabfile)
+ else:
+ env = { }
+ exec("import %s as lextab" % tabfile, env,env)
+ lextab = env['lextab']
+
+ if getattr(lextab,"_tabversion","0.0") != __version__:
+ raise ImportError("Inconsistent PLY version")
+
+ self.lextokens = lextab._lextokens
+ self.lexreflags = lextab._lexreflags
+ self.lexliterals = lextab._lexliterals
+ self.lexstateinfo = lextab._lexstateinfo
+ self.lexstateignore = lextab._lexstateignore
+ self.lexstatere = { }
+ self.lexstateretext = { }
+ for key,lre in lextab._lexstatere.items():
+ titem = []
+ txtitem = []
+ for i in range(len(lre)):
+ titem.append((re.compile(lre[i][0],lextab._lexreflags | re.VERBOSE),_names_to_funcs(lre[i][1],fdict)))
+ txtitem.append(lre[i][0])
+ self.lexstatere[key] = titem
+ self.lexstateretext[key] = txtitem
+ self.lexstateerrorf = { }
+ for key,ef in lextab._lexstateerrorf.items():
+ self.lexstateerrorf[key] = fdict[ef]
+ self.begin('INITIAL')
+
+ # ------------------------------------------------------------
+ # input() - Push a new string into the lexer
+ # ------------------------------------------------------------
+ def input(self,s):
+ # Pull off the first character to see if s looks like a string
+ c = s[:1]
+ if not isinstance(c,StringTypes):
+ raise ValueError("Expected a string")
+ self.lexdata = s
+ self.lexpos = 0
+ self.lexlen = len(s)
+
+ # ------------------------------------------------------------
+ # begin() - Changes the lexing state
+ # ------------------------------------------------------------
+ def begin(self,state):
+ if not state in self.lexstatere:
+ raise ValueError("Undefined state")
+ self.lexre = self.lexstatere[state]
+ self.lexretext = self.lexstateretext[state]
+ self.lexignore = self.lexstateignore.get(state,"")
+ self.lexerrorf = self.lexstateerrorf.get(state,None)
+ self.lexstate = state
+
+ # ------------------------------------------------------------
+ # push_state() - Changes the lexing state and saves old on stack
+ # ------------------------------------------------------------
+ def push_state(self,state):
+ self.lexstatestack.append(self.lexstate)
+ self.begin(state)
+
+ # ------------------------------------------------------------
+ # pop_state() - Restores the previous state
+ # ------------------------------------------------------------
+ def pop_state(self):
+ self.begin(self.lexstatestack.pop())
+
+ # ------------------------------------------------------------
+ # current_state() - Returns the current lexing state
+ # ------------------------------------------------------------
+ def current_state(self):
+ return self.lexstate
+
+ # ------------------------------------------------------------
+ # skip() - Skip ahead n characters
+ # ------------------------------------------------------------
+ def skip(self,n):
+ self.lexpos += n
+
+ # ------------------------------------------------------------
+ # opttoken() - Return the next token from the Lexer
+ #
+ # Note: This function has been carefully implemented to be as fast
+ # as possible. Don't make changes unless you really know what
+ # you are doing
+ # ------------------------------------------------------------
+ def token(self):
+ # Make local copies of frequently referenced attributes
+ lexpos = self.lexpos
+ lexlen = self.lexlen
+ lexignore = self.lexignore
+ lexdata = self.lexdata
+
+ while lexpos < lexlen:
+ # This code provides some short-circuit code for whitespace, tabs, and other ignored characters
+ if lexdata[lexpos] in lexignore:
+ lexpos += 1
+ continue
+
+ # Look for a regular expression match
+ for lexre,lexindexfunc in self.lexre:
+ m = lexre.match(lexdata,lexpos)
+ if not m: continue
+
+ # Create a token for return
+ tok = LexToken()
+ tok.value = m.group()
+ tok.lineno = self.lineno
+ tok.lexpos = lexpos
+
+ i = m.lastindex
+ func,tok.type = lexindexfunc[i]
+
+ if not func:
+ # If no token type was set, it's an ignored token
+ if tok.type:
+ self.lexpos = m.end()
+ return tok
+ else:
+ lexpos = m.end()
+ break
+
+ lexpos = m.end()
+
+ # If token is processed by a function, call it
+
+ tok.lexer = self # Set additional attributes useful in token rules
+ self.lexmatch = m
+ self.lexpos = lexpos
+
+ newtok = func(tok)
+
+ # Every function must return a token, if nothing, we just move to next token
+ if not newtok:
+ lexpos = self.lexpos # This is here in case user has updated lexpos.
+ lexignore = self.lexignore # This is here in case there was a state change
+ break
+
+ # Verify type of the token. If not in the token map, raise an error
+ if not self.lexoptimize:
+ if not newtok.type in self.lextokens:
+ raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
+ func_code(func).co_filename, func_code(func).co_firstlineno,
+ func.__name__, newtok.type),lexdata[lexpos:])
+
+ return newtok
+ else:
+ # No match, see if in literals
+ if lexdata[lexpos] in self.lexliterals:
+ tok = LexToken()
+ tok.value = lexdata[lexpos]
+ tok.lineno = self.lineno
+ tok.type = tok.value
+ tok.lexpos = lexpos
+ self.lexpos = lexpos + 1
+ return tok
+
+ # No match. Call t_error() if defined.
+ if self.lexerrorf:
+ tok = LexToken()
+ tok.value = self.lexdata[lexpos:]
+ tok.lineno = self.lineno
+ tok.type = "error"
+ tok.lexer = self
+ tok.lexpos = lexpos
+ self.lexpos = lexpos
+ newtok = self.lexerrorf(tok)
+ if lexpos == self.lexpos:
+ # Error method didn't change text position at all. This is an error.
+ raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
+ lexpos = self.lexpos
+ if not newtok: continue
+ return newtok
+
+ self.lexpos = lexpos
+ raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos],lexpos), lexdata[lexpos:])
+
+ self.lexpos = lexpos + 1
+ if self.lexdata is None:
+ raise RuntimeError("No input string given with input()")
+ return None
+
+ # Iterator interface
+ def __iter__(self):
+ return self
+
+ def next(self):
+ t = self.token()
+ if t is None:
+ raise StopIteration
+ return t
+
+ __next__ = next
+
+# -----------------------------------------------------------------------------
+# ==== Lex Builder ===
+#
+# The functions and classes below are used to collect lexing information
+# and build a Lexer object from it.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# get_caller_module_dict()
+#
+# This function returns a dictionary containing all of the symbols defined within
+# a caller further down the call stack. This is used to get the environment
+# associated with the yacc() call if none was provided.
+# -----------------------------------------------------------------------------
+
+def get_caller_module_dict(levels):
+ try:
+ raise RuntimeError
+ except RuntimeError:
+ e,b,t = sys.exc_info()
+ f = t.tb_frame
+ while levels > 0:
+ f = f.f_back
+ levels -= 1
+ ldict = f.f_globals.copy()
+ if f.f_globals != f.f_locals:
+ ldict.update(f.f_locals)
+
+ return ldict
+
+# -----------------------------------------------------------------------------
+# _funcs_to_names()
+#
+# Given a list of regular expression functions, this converts it to a list
+# suitable for output to a table file
+# -----------------------------------------------------------------------------
+
+def _funcs_to_names(funclist,namelist):
+ result = []
+ for f,name in zip(funclist,namelist):
+ if f and f[0]:
+ result.append((name, f[1]))
+ else:
+ result.append(f)
+ return result
+
+# -----------------------------------------------------------------------------
+# _names_to_funcs()
+#
+# Given a list of regular expression function names, this converts it back to
+# functions.
+# -----------------------------------------------------------------------------
+
+def _names_to_funcs(namelist,fdict):
+ result = []
+ for n in namelist:
+ if n and n[0]:
+ result.append((fdict[n[0]],n[1]))
+ else:
+ result.append(n)
+ return result
+
+# -----------------------------------------------------------------------------
+# _form_master_re()
+#
+# This function takes a list of all of the regex components and attempts to
+# form the master regular expression. Given limitations in the Python re
+# module, it may be necessary to break the master regex into separate expressions.
+# -----------------------------------------------------------------------------
+
+def _form_master_re(relist,reflags,ldict,toknames):
+ if not relist: return []
+ regex = "|".join(relist)
+ try:
+ lexre = re.compile(regex,re.VERBOSE | reflags)
+
+ # Build the index to function map for the matching engine
+ lexindexfunc = [ None ] * (max(lexre.groupindex.values())+1)
+ lexindexnames = lexindexfunc[:]
+
+ for f,i in lexre.groupindex.items():
+ handle = ldict.get(f,None)
+ if type(handle) in (types.FunctionType, types.MethodType):
+ lexindexfunc[i] = (handle,toknames[f])
+ lexindexnames[i] = f
+ elif handle is not None:
+ lexindexnames[i] = f
+ if f.find("ignore_") > 0:
+ lexindexfunc[i] = (None,None)
+ else:
+ lexindexfunc[i] = (None, toknames[f])
+
+ return [(lexre,lexindexfunc)],[regex],[lexindexnames]
+ except Exception:
+ m = int(len(relist)/2)
+ if m == 0: m = 1
+ llist, lre, lnames = _form_master_re(relist[:m],reflags,ldict,toknames)
+ rlist, rre, rnames = _form_master_re(relist[m:],reflags,ldict,toknames)
+ return llist+rlist, lre+rre, lnames+rnames
+
+# -----------------------------------------------------------------------------
+# def _statetoken(s,names)
+#
+# Given a declaration name s of the form "t_" and a dictionary whose keys are
+# state names, this function returns a tuple (states,tokenname) where states
+# is a tuple of state names and tokenname is the name of the token. For example,
+# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM')
+# -----------------------------------------------------------------------------
+
+def _statetoken(s,names):
+ nonstate = 1
+ parts = s.split("_")
+ for i in range(1,len(parts)):
+ if not parts[i] in names and parts[i] != 'ANY': break
+ if i > 1:
+ states = tuple(parts[1:i])
+ else:
+ states = ('INITIAL',)
+
+ if 'ANY' in states:
+ states = tuple(names)
+
+ tokenname = "_".join(parts[i:])
+ return (states,tokenname)
+
+
+# -----------------------------------------------------------------------------
+# LexerReflect()
+#
+# This class represents information needed to build a lexer as extracted from a
+# user's input file.
+# -----------------------------------------------------------------------------
+class LexerReflect(object):
+ def __init__(self,ldict,log=None,reflags=0):
+ self.ldict = ldict
+ self.error_func = None
+ self.tokens = []
+ self.reflags = reflags
+ self.stateinfo = { 'INITIAL' : 'inclusive'}
+ self.files = {}
+ self.error = 0
+
+ if log is None:
+ self.log = PlyLogger(sys.stderr)
+ else:
+ self.log = log
+
+ # Get all of the basic information
+ def get_all(self):
+ self.get_tokens()
+ self.get_literals()
+ self.get_states()
+ self.get_rules()
+
+ # Validate all of the information
+ def validate_all(self):
+ self.validate_tokens()
+ self.validate_literals()
+ self.validate_rules()
+ return self.error
+
+ # Get the tokens map
+ def get_tokens(self):
+ tokens = self.ldict.get("tokens",None)
+ if not tokens:
+ self.log.error("No token list is defined")
+ self.error = 1
+ return
+
+ if not isinstance(tokens,(list, tuple)):
+ self.log.error("tokens must be a list or tuple")
+ self.error = 1
+ return
+
+ if not tokens:
+ self.log.error("tokens is empty")
+ self.error = 1
+ return
+
+ self.tokens = tokens
+
+ # Validate the tokens
+ def validate_tokens(self):
+ terminals = {}
+ for n in self.tokens:
+ if not _is_identifier.match(n):
+ self.log.error("Bad token name '%s'",n)
+ self.error = 1
+ if n in terminals:
+ self.log.warning("Token '%s' multiply defined", n)
+ terminals[n] = 1
+
+ # Get the literals specifier
+ def get_literals(self):
+ self.literals = self.ldict.get("literals","")
+
+ # Validate literals
+ def validate_literals(self):
+ try:
+ for c in self.literals:
+ if not isinstance(c,StringTypes) or len(c) > 1:
+ self.log.error("Invalid literal %s. Must be a single character", repr(c))
+ self.error = 1
+ continue
+
+ except TypeError:
+ self.log.error("Invalid literals specification. literals must be a sequence of characters")
+ self.error = 1
+
+ def get_states(self):
+ self.states = self.ldict.get("states",None)
+ # Build statemap
+ if self.states:
+ if not isinstance(self.states,(tuple,list)):
+ self.log.error("states must be defined as a tuple or list")
+ self.error = 1
+ else:
+ for s in self.states:
+ if not isinstance(s,tuple) or len(s) != 2:
+ self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')",repr(s))
+ self.error = 1
+ continue
+ name, statetype = s
+ if not isinstance(name,StringTypes):
+ self.log.error("State name %s must be a string", repr(name))
+ self.error = 1
+ continue
+ if not (statetype == 'inclusive' or statetype == 'exclusive'):
+ self.log.error("State type for state %s must be 'inclusive' or 'exclusive'",name)
+ self.error = 1
+ continue
+ if name in self.stateinfo:
+ self.log.error("State '%s' already defined",name)
+ self.error = 1
+ continue
+ self.stateinfo[name] = statetype
+
+ # Get all of the symbols with a t_ prefix and sort them into various
+ # categories (functions, strings, error functions, and ignore characters)
+
+ def get_rules(self):
+ tsymbols = [f for f in self.ldict if f[:2] == 't_' ]
+
+ # Now build up a list of functions and a list of strings
+
+ self.toknames = { } # Mapping of symbols to token names
+ self.funcsym = { } # Symbols defined as functions
+ self.strsym = { } # Symbols defined as strings
+ self.ignore = { } # Ignore strings by state
+ self.errorf = { } # Error functions by state
+
+ for s in self.stateinfo:
+ self.funcsym[s] = []
+ self.strsym[s] = []
+
+ if len(tsymbols) == 0:
+ self.log.error("No rules of the form t_rulename are defined")
+ self.error = 1
+ return
+
+ for f in tsymbols:
+ t = self.ldict[f]
+ states, tokname = _statetoken(f,self.stateinfo)
+ self.toknames[f] = tokname
+
+ if hasattr(t,"__call__"):
+ if tokname == 'error':
+ for s in states:
+ self.errorf[s] = t
+ elif tokname == 'ignore':
+ line = func_code(t).co_firstlineno
+ file = func_code(t).co_filename
+ self.log.error("%s:%d: Rule '%s' must be defined as a string",file,line,t.__name__)
+ self.error = 1
+ else:
+ for s in states:
+ self.funcsym[s].append((f,t))
+ elif isinstance(t, StringTypes):
+ if tokname == 'ignore':
+ for s in states:
+ self.ignore[s] = t
+ if "\\" in t:
+ self.log.warning("%s contains a literal backslash '\\'",f)
+
+ elif tokname == 'error':
+ self.log.error("Rule '%s' must be defined as a function", f)
+ self.error = 1
+ else:
+ for s in states:
+ self.strsym[s].append((f,t))
+ else:
+ self.log.error("%s not defined as a function or string", f)
+ self.error = 1
+
+ # Sort the functions by line number
+ for f in self.funcsym.values():
+ if sys.version_info[0] < 3:
+ f.sort(lambda x,y: cmp(func_code(x[1]).co_firstlineno,func_code(y[1]).co_firstlineno))
+ else:
+ # Python 3.0
+ f.sort(key=lambda x: func_code(x[1]).co_firstlineno)
+
+ # Sort the strings by regular expression length
+ for s in self.strsym.values():
+ if sys.version_info[0] < 3:
+ s.sort(lambda x,y: (len(x[1]) < len(y[1])) - (len(x[1]) > len(y[1])))
+ else:
+ # Python 3.0
+ s.sort(key=lambda x: len(x[1]),reverse=True)
+
+ # Validate all of the t_rules collected
+ def validate_rules(self):
+ for state in self.stateinfo:
+ # Validate all rules defined by functions
+
+
+
+ for fname, f in self.funcsym[state]:
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ self.files[file] = 1
+
+ tokname = self.toknames[fname]
+ if isinstance(f, types.MethodType):
+ reqargs = 2
+ else:
+ reqargs = 1
+ nargs = func_code(f).co_argcount
+ if nargs > reqargs:
+ self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+ self.error = 1
+ continue
+
+ if nargs < reqargs:
+ self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+ self.error = 1
+ continue
+
+ if not f.__doc__:
+ self.log.error("%s:%d: No regular expression defined for rule '%s'",file,line,f.__name__)
+ self.error = 1
+ continue
+
+ try:
+ c = re.compile("(?P<%s>%s)" % (fname,f.__doc__), re.VERBOSE | self.reflags)
+ if c.match(""):
+ self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file,line,f.__name__)
+ self.error = 1
+ except re.error:
+ _etype, e, _etrace = sys.exc_info()
+ self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file,line,f.__name__,e)
+ if '#' in f.__doc__:
+ self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'",file,line, f.__name__)
+ self.error = 1
+
+ # Validate all rules defined by strings
+ for name,r in self.strsym[state]:
+ tokname = self.toknames[name]
+ if tokname == 'error':
+ self.log.error("Rule '%s' must be defined as a function", name)
+ self.error = 1
+ continue
+
+ if not tokname in self.tokens and tokname.find("ignore_") < 0:
+ self.log.error("Rule '%s' defined for an unspecified token %s",name,tokname)
+ self.error = 1
+ continue
+
+ try:
+ c = re.compile("(?P<%s>%s)" % (name,r),re.VERBOSE | self.reflags)
+ if (c.match("")):
+ self.log.error("Regular expression for rule '%s' matches empty string",name)
+ self.error = 1
+ except re.error:
+ _etype, e, _etrace = sys.exc_info()
+ self.log.error("Invalid regular expression for rule '%s'. %s",name,e)
+ if '#' in r:
+ self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'",name)
+ self.error = 1
+
+ if not self.funcsym[state] and not self.strsym[state]:
+ self.log.error("No rules defined for state '%s'",state)
+ self.error = 1
+
+ # Validate the error function
+ efunc = self.errorf.get(state,None)
+ if efunc:
+ f = efunc
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ self.files[file] = 1
+
+ if isinstance(f, types.MethodType):
+ reqargs = 2
+ else:
+ reqargs = 1
+ nargs = func_code(f).co_argcount
+ if nargs > reqargs:
+ self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,f.__name__)
+ self.error = 1
+
+ if nargs < reqargs:
+ self.log.error("%s:%d: Rule '%s' requires an argument", file,line,f.__name__)
+ self.error = 1
+
+ for f in self.files:
+ self.validate_file(f)
+
+
+ # -----------------------------------------------------------------------------
+ # validate_file()
+ #
+ # This checks to see if there are duplicated t_rulename() functions or strings
+ # in the parser input file. This is done using a simple regular expression
+ # match on each line in the given file.
+ # -----------------------------------------------------------------------------
+
+ def validate_file(self,filename):
+ import os.path
+ base,ext = os.path.splitext(filename)
+ if ext != '.py': return # No idea what the file is. Return OK
+
+ try:
+ f = open(filename)
+ lines = f.readlines()
+ f.close()
+ except IOError:
+ return # Couldn't find the file. Don't worry about it
+
+ fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
+ sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
+
+ counthash = { }
+ linen = 1
+ for l in lines:
+ m = fre.match(l)
+ if not m:
+ m = sre.match(l)
+ if m:
+ name = m.group(1)
+ prev = counthash.get(name)
+ if not prev:
+ counthash[name] = linen
+ else:
+ self.log.error("%s:%d: Rule %s redefined. Previously defined on line %d",filename,linen,name,prev)
+ self.error = 1
+ linen += 1
+
+# -----------------------------------------------------------------------------
+# lex(module)
+#
+# Build all of the regular expression rules from definitions in the supplied module
+# -----------------------------------------------------------------------------
+def lex(module=None,object=None,debug=0,optimize=0,lextab="lextab",reflags=0,nowarn=0,outputdir="", debuglog=None, errorlog=None):
+ global lexer
+ ldict = None
+ stateinfo = { 'INITIAL' : 'inclusive'}
+ lexobj = Lexer()
+ lexobj.lexoptimize = optimize
+ global token,input
+
+ if errorlog is None:
+ errorlog = PlyLogger(sys.stderr)
+
+ if debug:
+ if debuglog is None:
+ debuglog = PlyLogger(sys.stderr)
+
+ # Get the module dictionary used for the lexer
+ if object: module = object
+
+ if module:
+ _items = [(k,getattr(module,k)) for k in dir(module)]
+ ldict = dict(_items)
+ else:
+ ldict = get_caller_module_dict(2)
+
+ # Collect parser information from the dictionary
+ linfo = LexerReflect(ldict,log=errorlog,reflags=reflags)
+ linfo.get_all()
+ if not optimize:
+ if linfo.validate_all():
+ raise SyntaxError("Can't build lexer")
+
+ if optimize and lextab:
+ try:
+ lexobj.readtab(lextab,ldict)
+ token = lexobj.token
+ input = lexobj.input
+ lexer = lexobj
+ return lexobj
+
+ except ImportError:
+ pass
+
+ # Dump some basic debugging information
+ if debug:
+ debuglog.info("lex: tokens = %r", linfo.tokens)
+ debuglog.info("lex: literals = %r", linfo.literals)
+ debuglog.info("lex: states = %r", linfo.stateinfo)
+
+ # Build a dictionary of valid token names
+ lexobj.lextokens = { }
+ for n in linfo.tokens:
+ lexobj.lextokens[n] = 1
+
+ # Get literals specification
+ if isinstance(linfo.literals,(list,tuple)):
+ lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals)
+ else:
+ lexobj.lexliterals = linfo.literals
+
+ # Get the stateinfo dictionary
+ stateinfo = linfo.stateinfo
+
+ regexs = { }
+ # Build the master regular expressions
+ for state in stateinfo:
+ regex_list = []
+
+ # Add rules defined by functions first
+ for fname, f in linfo.funcsym[state]:
+ line = func_code(f).co_firstlineno
+ file = func_code(f).co_filename
+ regex_list.append("(?P<%s>%s)" % (fname,f.__doc__))
+ if debug:
+ debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",fname,f.__doc__, state)
+
+ # Now add all of the simple rules
+ for name,r in linfo.strsym[state]:
+ regex_list.append("(?P<%s>%s)" % (name,r))
+ if debug:
+ debuglog.info("lex: Adding rule %s -> '%s' (state '%s')",name,r, state)
+
+ regexs[state] = regex_list
+
+ # Build the master regular expressions
+
+ if debug:
+ debuglog.info("lex: ==== MASTER REGEXS FOLLOW ====")
+
+ for state in regexs:
+ lexre, re_text, re_names = _form_master_re(regexs[state],reflags,ldict,linfo.toknames)
+ lexobj.lexstatere[state] = lexre
+ lexobj.lexstateretext[state] = re_text
+ lexobj.lexstaterenames[state] = re_names
+ if debug:
+ for i in range(len(re_text)):
+ debuglog.info("lex: state '%s' : regex[%d] = '%s'",state, i, re_text[i])
+
+ # For inclusive states, we need to add the regular expressions from the INITIAL state
+ for state,stype in stateinfo.items():
+ if state != "INITIAL" and stype == 'inclusive':
+ lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL'])
+ lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL'])
+ lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL'])
+
+ lexobj.lexstateinfo = stateinfo
+ lexobj.lexre = lexobj.lexstatere["INITIAL"]
+ lexobj.lexretext = lexobj.lexstateretext["INITIAL"]
+ lexobj.lexreflags = reflags
+
+ # Set up ignore variables
+ lexobj.lexstateignore = linfo.ignore
+ lexobj.lexignore = lexobj.lexstateignore.get("INITIAL","")
+
+ # Set up error functions
+ lexobj.lexstateerrorf = linfo.errorf
+ lexobj.lexerrorf = linfo.errorf.get("INITIAL",None)
+ if not lexobj.lexerrorf:
+ errorlog.warning("No t_error rule is defined")
+
+ # Check state information for ignore and error rules
+ for s,stype in stateinfo.items():
+ if stype == 'exclusive':
+ if not s in linfo.errorf:
+ errorlog.warning("No error rule is defined for exclusive state '%s'", s)
+ if not s in linfo.ignore and lexobj.lexignore:
+ errorlog.warning("No ignore rule is defined for exclusive state '%s'", s)
+ elif stype == 'inclusive':
+ if not s in linfo.errorf:
+ linfo.errorf[s] = linfo.errorf.get("INITIAL",None)
+ if not s in linfo.ignore:
+ linfo.ignore[s] = linfo.ignore.get("INITIAL","")
+
+ # Create global versions of the token() and input() functions
+ token = lexobj.token
+ input = lexobj.input
+ lexer = lexobj
+
+ # If in optimize mode, we write the lextab
+ if lextab and optimize:
+ lexobj.writetab(lextab,outputdir)
+
+ return lexobj
+
+# -----------------------------------------------------------------------------
+# runmain()
+#
+# This runs the lexer as a main program
+# -----------------------------------------------------------------------------
+
+def runmain(lexer=None,data=None):
+ if not data:
+ try:
+ filename = sys.argv[1]
+ f = open(filename)
+ data = f.read()
+ f.close()
+ except IndexError:
+ sys.stdout.write("Reading from standard input (type EOF to end):\n")
+ data = sys.stdin.read()
+
+ if lexer:
+ _input = lexer.input
+ else:
+ _input = input
+ _input(data)
+ if lexer:
+ _token = lexer.token
+ else:
+ _token = token
+
+ while 1:
+ tok = _token()
+ if not tok: break
+ sys.stdout.write("(%s,%r,%d,%d)\n" % (tok.type, tok.value, tok.lineno,tok.lexpos))
+
+# -----------------------------------------------------------------------------
+# @TOKEN(regex)
+#
+# This decorator function can be used to set the regex expression on a function
+# when its docstring might need to be set in an alternative way
+# -----------------------------------------------------------------------------
+
+def TOKEN(r):
+ def set_doc(f):
+ if hasattr(r,"__call__"):
+ f.__doc__ = r.__doc__
+ else:
+ f.__doc__ = r
+ return f
+ return set_doc
+
+# Alternative spelling of the TOKEN decorator
+Token = TOKEN
+
diff --git a/components/script/dom/bindings/codegen/ply/ply/yacc.py b/components/script/dom/bindings/codegen/ply/ply/yacc.py
new file mode 100644
index 00000000000..e9f5c657551
--- /dev/null
+++ b/components/script/dom/bindings/codegen/ply/ply/yacc.py
@@ -0,0 +1,3276 @@
+# -----------------------------------------------------------------------------
+# ply: yacc.py
+#
+# Copyright (C) 2001-2009,
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
+#
+# This implements an LR parser that is constructed from grammar rules defined
+# as Python functions. The grammer is specified by supplying the BNF inside
+# Python documentation strings. The inspiration for this technique was borrowed
+# from John Aycock's Spark parsing system. PLY might be viewed as cross between
+# Spark and the GNU bison utility.
+#
+# The current implementation is only somewhat object-oriented. The
+# LR parser itself is defined in terms of an object (which allows multiple
+# parsers to co-exist). However, most of the variables used during table
+# construction are defined in terms of global variables. Users shouldn't
+# notice unless they are trying to define multiple parsers at the same
+# time using threads (in which case they should have their head examined).
+#
+# This implementation supports both SLR and LALR(1) parsing. LALR(1)
+# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu),
+# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles,
+# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced
+# by the more efficient DeRemer and Pennello algorithm.
+#
+# :::::::: WARNING :::::::
+#
+# Construction of LR parsing tables is fairly complicated and expensive.
+# To make this module run fast, a *LOT* of work has been put into
+# optimization---often at the expensive of readability and what might
+# consider to be good Python "coding style." Modify the code at your
+# own risk!
+# ----------------------------------------------------------------------------
+
+__version__ = "3.3"
+__tabversion__ = "3.2" # Table version
+
+#-----------------------------------------------------------------------------
+# === User configurable parameters ===
+#
+# Change these to modify the default behavior of yacc (if you wish)
+#-----------------------------------------------------------------------------
+
+yaccdebug = 1 # Debugging mode. If set, yacc generates a
+ # a 'parser.out' file in the current directory
+
+debug_file = 'parser.out' # Default name of the debugging file
+tab_module = 'parsetab' # Default name of the table module
+default_lr = 'LALR' # Default LR table generation method
+
+error_count = 3 # Number of symbols that must be shifted to leave recovery mode
+
+yaccdevel = 0 # Set to True if developing yacc. This turns off optimized
+ # implementations of certain functions.
+
+resultlimit = 40 # Size limit of results when running in debug mode.
+
+pickle_protocol = 0 # Protocol to use when writing pickle files
+
+import re, types, sys, os.path
+
+# Compatibility function for python 2.6/3.0
+if sys.version_info[0] < 3:
+ def func_code(f):
+ return f.func_code
+else:
+ def func_code(f):
+ return f.__code__
+
+# Compatibility
+try:
+ MAXINT = sys.maxint
+except AttributeError:
+ MAXINT = sys.maxsize
+
+# Python 2.x/3.0 compatibility.
+def load_ply_lex():
+ if sys.version_info[0] < 3:
+ import lex
+ else:
+ import ply.lex as lex
+ return lex
+
+# This object is a stand-in for a logging object created by the
+# logging module. PLY will use this by default to create things
+# such as the parser.out file. If a user wants more detailed
+# information, they can create their own logging object and pass
+# it into PLY.
+
+class PlyLogger(object):
+ def __init__(self,f):
+ self.f = f
+ def debug(self,msg,*args,**kwargs):
+ self.f.write((msg % args) + "\n")
+ info = debug
+
+ def warning(self,msg,*args,**kwargs):
+ self.f.write("WARNING: "+ (msg % args) + "\n")
+
+ def error(self,msg,*args,**kwargs):
+ self.f.write("ERROR: " + (msg % args) + "\n")
+
+ critical = debug
+
+# Null logger is used when no output is generated. Does nothing.
+class NullLogger(object):
+ def __getattribute__(self,name):
+ return self
+ def __call__(self,*args,**kwargs):
+ return self
+
+# Exception raised for yacc-related errors
+class YaccError(Exception): pass
+
+# Format the result message that the parser produces when running in debug mode.
+def format_result(r):
+ repr_str = repr(r)
+ if '\n' in repr_str: repr_str = repr(repr_str)
+ if len(repr_str) > resultlimit:
+ repr_str = repr_str[:resultlimit]+" ..."
+ result = "<%s @ 0x%x> (%s)" % (type(r).__name__,id(r),repr_str)
+ return result
+
+
+# Format stack entries when the parser is running in debug mode
+def format_stack_entry(r):
+ repr_str = repr(r)
+ if '\n' in repr_str: repr_str = repr(repr_str)
+ if len(repr_str) < 16:
+ return repr_str
+ else:
+ return "<%s @ 0x%x>" % (type(r).__name__,id(r))
+
+#-----------------------------------------------------------------------------
+# === LR Parsing Engine ===
+#
+# The following classes are used for the LR parser itself. These are not
+# used during table construction and are independent of the actual LR
+# table generation algorithm
+#-----------------------------------------------------------------------------
+
+# This class is used to hold non-terminal grammar symbols during parsing.
+# It normally has the following attributes set:
+# .type = Grammar symbol type
+# .value = Symbol value
+# .lineno = Starting line number
+# .endlineno = Ending line number (optional, set automatically)
+# .lexpos = Starting lex position
+# .endlexpos = Ending lex position (optional, set automatically)
+
+class YaccSymbol:
+ def __str__(self): return self.type
+ def __repr__(self): return str(self)
+
+# This class is a wrapper around the objects actually passed to each
+# grammar rule. Index lookup and assignment actually assign the
+# .value attribute of the underlying YaccSymbol object.
+# The lineno() method returns the line number of a given
+# item (or 0 if not defined). The linespan() method returns
+# a tuple of (startline,endline) representing the range of lines
+# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos)
+# representing the range of positional information for a symbol.
+
+class YaccProduction:
+ def __init__(self,s,stack=None):
+ self.slice = s
+ self.stack = stack
+ self.lexer = None
+ self.parser= None
+ def __getitem__(self,n):
+ if n >= 0: return self.slice[n].value
+ else: return self.stack[n].value
+
+ def __setitem__(self,n,v):
+ self.slice[n].value = v
+
+ def __getslice__(self,i,j):
+ return [s.value for s in self.slice[i:j]]
+
+ def __len__(self):
+ return len(self.slice)
+
+ def lineno(self,n):
+ return getattr(self.slice[n],"lineno",0)
+
+ def set_lineno(self,n,lineno):
+ self.slice[n].lineno = lineno
+
+ def linespan(self,n):
+ startline = getattr(self.slice[n],"lineno",0)
+ endline = getattr(self.slice[n],"endlineno",startline)
+ return startline,endline
+
+ def lexpos(self,n):
+ return getattr(self.slice[n],"lexpos",0)
+
+ def lexspan(self,n):
+ startpos = getattr(self.slice[n],"lexpos",0)
+ endpos = getattr(self.slice[n],"endlexpos",startpos)
+ return startpos,endpos
+
+ def error(self):
+ raise SyntaxError
+
+
+# -----------------------------------------------------------------------------
+# == LRParser ==
+#
+# The LR Parsing engine.
+# -----------------------------------------------------------------------------
+
+class LRParser:
+ def __init__(self,lrtab,errorf):
+ self.productions = lrtab.lr_productions
+ self.action = lrtab.lr_action
+ self.goto = lrtab.lr_goto
+ self.errorfunc = errorf
+
+ def errok(self):
+ self.errorok = 1
+
+ def restart(self):
+ del self.statestack[:]
+ del self.symstack[:]
+ sym = YaccSymbol()
+ sym.type = '$end'
+ self.symstack.append(sym)
+ self.statestack.append(0)
+
+ def parse(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
+ if debug or yaccdevel:
+ if isinstance(debug,int):
+ debug = PlyLogger(sys.stderr)
+ return self.parsedebug(input,lexer,debug,tracking,tokenfunc)
+ elif tracking:
+ return self.parseopt(input,lexer,debug,tracking,tokenfunc)
+ else:
+ return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc)
+
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # parsedebug().
+ #
+ # This is the debugging enabled version of parse(). All changes made to the
+ # parsing engine should be made here. For the non-debugging version,
+ # copy this code to a method parseopt() and delete all of the sections
+ # enclosed in:
+ #
+ # #--! DEBUG
+ # statements
+ # #--! DEBUG
+ #
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ def parsedebug(self,input=None,lexer=None,debug=None,tracking=0,tokenfunc=None):
+ lookahead = None # Current lookahead symbol
+ lookaheadstack = [ ] # Stack of lookahead symbols
+ actions = self.action # Local reference to action table (to avoid lookup on self.)
+ goto = self.goto # Local reference to goto table (to avoid lookup on self.)
+ prod = self.productions # Local reference to production list (to avoid lookup on self.)
+ pslice = YaccProduction(None) # Production object passed to grammar rules
+ errorcount = 0 # Used during error recovery
+
+ # --! DEBUG
+ debug.info("PLY: PARSE DEBUG START")
+ # --! DEBUG
+
+ # If no lexer was given, we will try to use the lex module
+ if not lexer:
+ lex = load_ply_lex()
+ lexer = lex.lexer
+
+ # Set up the lexer and parser objects on pslice
+ pslice.lexer = lexer
+ pslice.parser = self
+
+ # If input was supplied, pass to lexer
+ if input is not None:
+ lexer.input(input)
+
+ if tokenfunc is None:
+ # Tokenize function
+ get_token = lexer.token
+ else:
+ get_token = tokenfunc
+
+ # Set up the state and symbol stacks
+
+ statestack = [ ] # Stack of parsing states
+ self.statestack = statestack
+ symstack = [ ] # Stack of grammar symbols
+ self.symstack = symstack
+
+ pslice.stack = symstack # Put in the production
+ errtoken = None # Err token
+
+ # The start state is assumed to be (0,$end)
+
+ statestack.append(0)
+ sym = YaccSymbol()
+ sym.type = "$end"
+ symstack.append(sym)
+ state = 0
+ while 1:
+ # Get the next symbol on the input. If a lookahead symbol
+ # is already set, we just use that. Otherwise, we'll pull
+ # the next token off of the lookaheadstack or from the lexer
+
+ # --! DEBUG
+ debug.debug('')
+ debug.debug('State : %s', state)
+ # --! DEBUG
+
+ if not lookahead:
+ if not lookaheadstack:
+ lookahead = get_token() # Get the next token
+ else:
+ lookahead = lookaheadstack.pop()
+ if not lookahead:
+ lookahead = YaccSymbol()
+ lookahead.type = "$end"
+
+ # --! DEBUG
+ debug.debug('Stack : %s',
+ ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip())
+ # --! DEBUG
+
+ # Check the action table
+ ltype = lookahead.type
+ t = actions[state].get(ltype)
+
+ if t is not None:
+ if t > 0:
+ # shift a symbol on the stack
+ statestack.append(t)
+ state = t
+
+ # --! DEBUG
+ debug.debug("Action : Shift and goto state %s", t)
+ # --! DEBUG
+
+ symstack.append(lookahead)
+ lookahead = None
+
+ # Decrease error count on successful shift
+ if errorcount: errorcount -=1
+ continue
+
+ if t < 0:
+ # reduce a symbol on the stack, emit a production
+ p = prod[-t]
+ pname = p.name
+ plen = p.len
+
+ # Get production function
+ sym = YaccSymbol()
+ sym.type = pname # Production name
+ sym.value = None
+
+ # --! DEBUG
+ if plen:
+ debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, "["+",".join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+"]",-t)
+ else:
+ debug.info("Action : Reduce rule [%s] with %s and goto state %d", p.str, [],-t)
+
+ # --! DEBUG
+
+ if plen:
+ targ = symstack[-plen-1:]
+ targ[0] = sym
+
+ # --! TRACKING
+ if tracking:
+ t1 = targ[1]
+ sym.lineno = t1.lineno
+ sym.lexpos = t1.lexpos
+ t1 = targ[-1]
+ sym.endlineno = getattr(t1,"endlineno",t1.lineno)
+ sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos)
+
+ # --! TRACKING
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # below as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ del symstack[-plen:]
+ del statestack[-plen:]
+ p.callable(pslice)
+ # --! DEBUG
+ debug.info("Result : %s", format_result(pslice[0]))
+ # --! DEBUG
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ else:
+
+ # --! TRACKING
+ if tracking:
+ sym.lineno = lexer.lineno
+ sym.lexpos = lexer.lexpos
+ # --! TRACKING
+
+ targ = [ sym ]
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # above as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ p.callable(pslice)
+ # --! DEBUG
+ debug.info("Result : %s", format_result(pslice[0]))
+ # --! DEBUG
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ if t == 0:
+ n = symstack[-1]
+ result = getattr(n,"value",None)
+ # --! DEBUG
+ debug.info("Done : Returning %s", format_result(result))
+ debug.info("PLY: PARSE DEBUG END")
+ # --! DEBUG
+ return result
+
+ if t == None:
+
+ # --! DEBUG
+ debug.error('Error : %s',
+ ("%s . %s" % (" ".join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip())
+ # --! DEBUG
+
+ # We have some kind of parsing error here. To handle
+ # this, we are going to push the current token onto
+ # the tokenstack and replace it with an 'error' token.
+ # If there are any synchronization rules, they may
+ # catch it.
+ #
+ # In addition to pushing the error token, we call call
+ # the user defined p_error() function if this is the
+ # first syntax error. This function is only called if
+ # errorcount == 0.
+ if errorcount == 0 or self.errorok:
+ errorcount = error_count
+ self.errorok = 0
+ errtoken = lookahead
+ if errtoken.type == "$end":
+ errtoken = None # End of file!
+ if self.errorfunc:
+ global errok,token,restart
+ errok = self.errok # Set some special functions available in error recovery
+ token = get_token
+ restart = self.restart
+ if errtoken and not hasattr(errtoken,'lexer'):
+ errtoken.lexer = lexer
+ tok = self.errorfunc(errtoken)
+ del errok, token, restart # Delete special functions
+
+ if self.errorok:
+ # User must have done some kind of panic
+ # mode recovery on their own. The
+ # returned token is the next lookahead
+ lookahead = tok
+ errtoken = None
+ continue
+ else:
+ if errtoken:
+ if hasattr(errtoken,"lineno"): lineno = lookahead.lineno
+ else: lineno = 0
+ if lineno:
+ sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type))
+ else:
+ sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type)
+ else:
+ sys.stderr.write("yacc: Parse error in input. EOF\n")
+ return
+
+ else:
+ errorcount = error_count
+
+ # case 1: the statestack only has 1 entry on it. If we're in this state, the
+ # entire parse has been rolled back and we're completely hosed. The token is
+ # discarded and we just keep going.
+
+ if len(statestack) <= 1 and lookahead.type != "$end":
+ lookahead = None
+ errtoken = None
+ state = 0
+ # Nuke the pushback stack
+ del lookaheadstack[:]
+ continue
+
+ # case 2: the statestack has a couple of entries on it, but we're
+ # at the end of the file. nuke the top entry and generate an error token
+
+ # Start nuking entries on the stack
+ if lookahead.type == "$end":
+ # Whoa. We're really hosed here. Bail out
+ return
+
+ if lookahead.type != 'error':
+ sym = symstack[-1]
+ if sym.type == 'error':
+ # Hmmm. Error is on top of stack, we'll just nuke input
+ # symbol and continue
+ lookahead = None
+ continue
+ t = YaccSymbol()
+ t.type = 'error'
+ if hasattr(lookahead,"lineno"):
+ t.lineno = lookahead.lineno
+ t.value = lookahead
+ lookaheadstack.append(lookahead)
+ lookahead = t
+ else:
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1] # Potential bug fix
+
+ continue
+
+ # Call an error function here
+ raise RuntimeError("yacc: internal parser error!!!\n")
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # parseopt().
+ #
+ # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY.
+ # Edit the debug version above, then copy any modifications to the method
+ # below while removing #--! DEBUG sections.
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+ def parseopt(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
+ lookahead = None # Current lookahead symbol
+ lookaheadstack = [ ] # Stack of lookahead symbols
+ actions = self.action # Local reference to action table (to avoid lookup on self.)
+ goto = self.goto # Local reference to goto table (to avoid lookup on self.)
+ prod = self.productions # Local reference to production list (to avoid lookup on self.)
+ pslice = YaccProduction(None) # Production object passed to grammar rules
+ errorcount = 0 # Used during error recovery
+
+ # If no lexer was given, we will try to use the lex module
+ if not lexer:
+ lex = load_ply_lex()
+ lexer = lex.lexer
+
+ # Set up the lexer and parser objects on pslice
+ pslice.lexer = lexer
+ pslice.parser = self
+
+ # If input was supplied, pass to lexer
+ if input is not None:
+ lexer.input(input)
+
+ if tokenfunc is None:
+ # Tokenize function
+ get_token = lexer.token
+ else:
+ get_token = tokenfunc
+
+ # Set up the state and symbol stacks
+
+ statestack = [ ] # Stack of parsing states
+ self.statestack = statestack
+ symstack = [ ] # Stack of grammar symbols
+ self.symstack = symstack
+
+ pslice.stack = symstack # Put in the production
+ errtoken = None # Err token
+
+ # The start state is assumed to be (0,$end)
+
+ statestack.append(0)
+ sym = YaccSymbol()
+ sym.type = '$end'
+ symstack.append(sym)
+ state = 0
+ while 1:
+ # Get the next symbol on the input. If a lookahead symbol
+ # is already set, we just use that. Otherwise, we'll pull
+ # the next token off of the lookaheadstack or from the lexer
+
+ if not lookahead:
+ if not lookaheadstack:
+ lookahead = get_token() # Get the next token
+ else:
+ lookahead = lookaheadstack.pop()
+ if not lookahead:
+ lookahead = YaccSymbol()
+ lookahead.type = '$end'
+
+ # Check the action table
+ ltype = lookahead.type
+ t = actions[state].get(ltype)
+
+ if t is not None:
+ if t > 0:
+ # shift a symbol on the stack
+ statestack.append(t)
+ state = t
+
+ symstack.append(lookahead)
+ lookahead = None
+
+ # Decrease error count on successful shift
+ if errorcount: errorcount -=1
+ continue
+
+ if t < 0:
+ # reduce a symbol on the stack, emit a production
+ p = prod[-t]
+ pname = p.name
+ plen = p.len
+
+ # Get production function
+ sym = YaccSymbol()
+ sym.type = pname # Production name
+ sym.value = None
+
+ if plen:
+ targ = symstack[-plen-1:]
+ targ[0] = sym
+
+ # --! TRACKING
+ if tracking:
+ t1 = targ[1]
+ sym.lineno = t1.lineno
+ sym.lexpos = t1.lexpos
+ t1 = targ[-1]
+ sym.endlineno = getattr(t1,"endlineno",t1.lineno)
+ sym.endlexpos = getattr(t1,"endlexpos",t1.lexpos)
+
+ # --! TRACKING
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # below as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ del symstack[-plen:]
+ del statestack[-plen:]
+ p.callable(pslice)
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ else:
+
+ # --! TRACKING
+ if tracking:
+ sym.lineno = lexer.lineno
+ sym.lexpos = lexer.lexpos
+ # --! TRACKING
+
+ targ = [ sym ]
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # above as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ p.callable(pslice)
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ if t == 0:
+ n = symstack[-1]
+ return getattr(n,"value",None)
+
+ if t == None:
+
+ # We have some kind of parsing error here. To handle
+ # this, we are going to push the current token onto
+ # the tokenstack and replace it with an 'error' token.
+ # If there are any synchronization rules, they may
+ # catch it.
+ #
+ # In addition to pushing the error token, we call call
+ # the user defined p_error() function if this is the
+ # first syntax error. This function is only called if
+ # errorcount == 0.
+ if errorcount == 0 or self.errorok:
+ errorcount = error_count
+ self.errorok = 0
+ errtoken = lookahead
+ if errtoken.type == '$end':
+ errtoken = None # End of file!
+ if self.errorfunc:
+ global errok,token,restart
+ errok = self.errok # Set some special functions available in error recovery
+ token = get_token
+ restart = self.restart
+ if errtoken and not hasattr(errtoken,'lexer'):
+ errtoken.lexer = lexer
+ tok = self.errorfunc(errtoken)
+ del errok, token, restart # Delete special functions
+
+ if self.errorok:
+ # User must have done some kind of panic
+ # mode recovery on their own. The
+ # returned token is the next lookahead
+ lookahead = tok
+ errtoken = None
+ continue
+ else:
+ if errtoken:
+ if hasattr(errtoken,"lineno"): lineno = lookahead.lineno
+ else: lineno = 0
+ if lineno:
+ sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type))
+ else:
+ sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type)
+ else:
+ sys.stderr.write("yacc: Parse error in input. EOF\n")
+ return
+
+ else:
+ errorcount = error_count
+
+ # case 1: the statestack only has 1 entry on it. If we're in this state, the
+ # entire parse has been rolled back and we're completely hosed. The token is
+ # discarded and we just keep going.
+
+ if len(statestack) <= 1 and lookahead.type != '$end':
+ lookahead = None
+ errtoken = None
+ state = 0
+ # Nuke the pushback stack
+ del lookaheadstack[:]
+ continue
+
+ # case 2: the statestack has a couple of entries on it, but we're
+ # at the end of the file. nuke the top entry and generate an error token
+
+ # Start nuking entries on the stack
+ if lookahead.type == '$end':
+ # Whoa. We're really hosed here. Bail out
+ return
+
+ if lookahead.type != 'error':
+ sym = symstack[-1]
+ if sym.type == 'error':
+ # Hmmm. Error is on top of stack, we'll just nuke input
+ # symbol and continue
+ lookahead = None
+ continue
+ t = YaccSymbol()
+ t.type = 'error'
+ if hasattr(lookahead,"lineno"):
+ t.lineno = lookahead.lineno
+ t.value = lookahead
+ lookaheadstack.append(lookahead)
+ lookahead = t
+ else:
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1] # Potential bug fix
+
+ continue
+
+ # Call an error function here
+ raise RuntimeError("yacc: internal parser error!!!\n")
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # parseopt_notrack().
+ #
+ # Optimized version of parseopt() with line number tracking removed.
+ # DO NOT EDIT THIS CODE DIRECTLY. Copy the optimized version and remove
+ # code in the #--! TRACKING sections
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ def parseopt_notrack(self,input=None,lexer=None,debug=0,tracking=0,tokenfunc=None):
+ lookahead = None # Current lookahead symbol
+ lookaheadstack = [ ] # Stack of lookahead symbols
+ actions = self.action # Local reference to action table (to avoid lookup on self.)
+ goto = self.goto # Local reference to goto table (to avoid lookup on self.)
+ prod = self.productions # Local reference to production list (to avoid lookup on self.)
+ pslice = YaccProduction(None) # Production object passed to grammar rules
+ errorcount = 0 # Used during error recovery
+
+ # If no lexer was given, we will try to use the lex module
+ if not lexer:
+ lex = load_ply_lex()
+ lexer = lex.lexer
+
+ # Set up the lexer and parser objects on pslice
+ pslice.lexer = lexer
+ pslice.parser = self
+
+ # If input was supplied, pass to lexer
+ if input is not None:
+ lexer.input(input)
+
+ if tokenfunc is None:
+ # Tokenize function
+ get_token = lexer.token
+ else:
+ get_token = tokenfunc
+
+ # Set up the state and symbol stacks
+
+ statestack = [ ] # Stack of parsing states
+ self.statestack = statestack
+ symstack = [ ] # Stack of grammar symbols
+ self.symstack = symstack
+
+ pslice.stack = symstack # Put in the production
+ errtoken = None # Err token
+
+ # The start state is assumed to be (0,$end)
+
+ statestack.append(0)
+ sym = YaccSymbol()
+ sym.type = '$end'
+ symstack.append(sym)
+ state = 0
+ while 1:
+ # Get the next symbol on the input. If a lookahead symbol
+ # is already set, we just use that. Otherwise, we'll pull
+ # the next token off of the lookaheadstack or from the lexer
+
+ if not lookahead:
+ if not lookaheadstack:
+ lookahead = get_token() # Get the next token
+ else:
+ lookahead = lookaheadstack.pop()
+ if not lookahead:
+ lookahead = YaccSymbol()
+ lookahead.type = '$end'
+
+ # Check the action table
+ ltype = lookahead.type
+ t = actions[state].get(ltype)
+
+ if t is not None:
+ if t > 0:
+ # shift a symbol on the stack
+ statestack.append(t)
+ state = t
+
+ symstack.append(lookahead)
+ lookahead = None
+
+ # Decrease error count on successful shift
+ if errorcount: errorcount -=1
+ continue
+
+ if t < 0:
+ # reduce a symbol on the stack, emit a production
+ p = prod[-t]
+ pname = p.name
+ plen = p.len
+
+ # Get production function
+ sym = YaccSymbol()
+ sym.type = pname # Production name
+ sym.value = None
+
+ if plen:
+ targ = symstack[-plen-1:]
+ targ[0] = sym
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # below as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ del symstack[-plen:]
+ del statestack[-plen:]
+ p.callable(pslice)
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ else:
+
+ targ = [ sym ]
+
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ # The code enclosed in this section is duplicated
+ # above as a performance optimization. Make sure
+ # changes get made in both locations.
+
+ pslice.slice = targ
+
+ try:
+ # Call the grammar rule with our special slice object
+ p.callable(pslice)
+ symstack.append(sym)
+ state = goto[statestack[-1]][pname]
+ statestack.append(state)
+ except SyntaxError:
+ # If an error was set. Enter error recovery state
+ lookaheadstack.append(lookahead)
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1]
+ sym.type = 'error'
+ lookahead = sym
+ errorcount = error_count
+ self.errorok = 0
+ continue
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ if t == 0:
+ n = symstack[-1]
+ return getattr(n,"value",None)
+
+ if t == None:
+
+ # We have some kind of parsing error here. To handle
+ # this, we are going to push the current token onto
+ # the tokenstack and replace it with an 'error' token.
+ # If there are any synchronization rules, they may
+ # catch it.
+ #
+ # In addition to pushing the error token, we call call
+ # the user defined p_error() function if this is the
+ # first syntax error. This function is only called if
+ # errorcount == 0.
+ if errorcount == 0 or self.errorok:
+ errorcount = error_count
+ self.errorok = 0
+ errtoken = lookahead
+ if errtoken.type == '$end':
+ errtoken = None # End of file!
+ if self.errorfunc:
+ global errok,token,restart
+ errok = self.errok # Set some special functions available in error recovery
+ token = get_token
+ restart = self.restart
+ if errtoken and not hasattr(errtoken,'lexer'):
+ errtoken.lexer = lexer
+ tok = self.errorfunc(errtoken)
+ del errok, token, restart # Delete special functions
+
+ if self.errorok:
+ # User must have done some kind of panic
+ # mode recovery on their own. The
+ # returned token is the next lookahead
+ lookahead = tok
+ errtoken = None
+ continue
+ else:
+ if errtoken:
+ if hasattr(errtoken,"lineno"): lineno = lookahead.lineno
+ else: lineno = 0
+ if lineno:
+ sys.stderr.write("yacc: Syntax error at line %d, token=%s\n" % (lineno, errtoken.type))
+ else:
+ sys.stderr.write("yacc: Syntax error, token=%s" % errtoken.type)
+ else:
+ sys.stderr.write("yacc: Parse error in input. EOF\n")
+ return
+
+ else:
+ errorcount = error_count
+
+ # case 1: the statestack only has 1 entry on it. If we're in this state, the
+ # entire parse has been rolled back and we're completely hosed. The token is
+ # discarded and we just keep going.
+
+ if len(statestack) <= 1 and lookahead.type != '$end':
+ lookahead = None
+ errtoken = None
+ state = 0
+ # Nuke the pushback stack
+ del lookaheadstack[:]
+ continue
+
+ # case 2: the statestack has a couple of entries on it, but we're
+ # at the end of the file. nuke the top entry and generate an error token
+
+ # Start nuking entries on the stack
+ if lookahead.type == '$end':
+ # Whoa. We're really hosed here. Bail out
+ return
+
+ if lookahead.type != 'error':
+ sym = symstack[-1]
+ if sym.type == 'error':
+ # Hmmm. Error is on top of stack, we'll just nuke input
+ # symbol and continue
+ lookahead = None
+ continue
+ t = YaccSymbol()
+ t.type = 'error'
+ if hasattr(lookahead,"lineno"):
+ t.lineno = lookahead.lineno
+ t.value = lookahead
+ lookaheadstack.append(lookahead)
+ lookahead = t
+ else:
+ symstack.pop()
+ statestack.pop()
+ state = statestack[-1] # Potential bug fix
+
+ continue
+
+ # Call an error function here
+ raise RuntimeError("yacc: internal parser error!!!\n")
+
+# -----------------------------------------------------------------------------
+# === Grammar Representation ===
+#
+# The following functions, classes, and variables are used to represent and
+# manipulate the rules that make up a grammar.
+# -----------------------------------------------------------------------------
+
+import re
+
+# regex matching identifiers
+_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$')
+
+# -----------------------------------------------------------------------------
+# class Production:
+#
+# This class stores the raw information about a single production or grammar rule.
+# A grammar rule refers to a specification such as this:
+#
+# expr : expr PLUS term
+#
+# Here are the basic attributes defined on all productions
+#
+# name - Name of the production. For example 'expr'
+# prod - A list of symbols on the right side ['expr','PLUS','term']
+# prec - Production precedence level
+# number - Production number.
+# func - Function that executes on reduce
+# file - File where production function is defined
+# lineno - Line number where production function is defined
+#
+# The following attributes are defined or optional.
+#
+# len - Length of the production (number of symbols on right hand side)
+# usyms - Set of unique symbols found in the production
+# -----------------------------------------------------------------------------
+
+class Production(object):
+ reduced = 0
+ def __init__(self,number,name,prod,precedence=('right',0),func=None,file='',line=0):
+ self.name = name
+ self.prod = tuple(prod)
+ self.number = number
+ self.func = func
+ self.callable = None
+ self.file = file
+ self.line = line
+ self.prec = precedence
+
+ # Internal settings used during table construction
+
+ self.len = len(self.prod) # Length of the production
+
+ # Create a list of unique production symbols used in the production
+ self.usyms = [ ]
+ for s in self.prod:
+ if s not in self.usyms:
+ self.usyms.append(s)
+
+ # List of all LR items for the production
+ self.lr_items = []
+ self.lr_next = None
+
+ # Create a string representation
+ if self.prod:
+ self.str = "%s -> %s" % (self.name," ".join(self.prod))
+ else:
+ self.str = "%s -> <empty>" % self.name
+
+ def __str__(self):
+ return self.str
+
+ def __repr__(self):
+ return "Production("+str(self)+")"
+
+ def __len__(self):
+ return len(self.prod)
+
+ def __nonzero__(self):
+ return 1
+
+ def __getitem__(self,index):
+ return self.prod[index]
+
+ # Return the nth lr_item from the production (or None if at the end)
+ def lr_item(self,n):
+ if n > len(self.prod): return None
+ p = LRItem(self,n)
+
+ # Precompute the list of productions immediately following. Hack. Remove later
+ try:
+ p.lr_after = Prodnames[p.prod[n+1]]
+ except (IndexError,KeyError):
+ p.lr_after = []
+ try:
+ p.lr_before = p.prod[n-1]
+ except IndexError:
+ p.lr_before = None
+
+ return p
+
+ # Bind the production function name to a callable
+ def bind(self,pdict):
+ if self.func:
+ self.callable = pdict[self.func]
+
+# This class serves as a minimal standin for Production objects when
+# reading table data from files. It only contains information
+# actually used by the LR parsing engine, plus some additional
+# debugging information.
+class MiniProduction(object):
+ def __init__(self,str,name,len,func,file,line):
+ self.name = name
+ self.len = len
+ self.func = func
+ self.callable = None
+ self.file = file
+ self.line = line
+ self.str = str
+ def __str__(self):
+ return self.str
+ def __repr__(self):
+ return "MiniProduction(%s)" % self.str
+
+ # Bind the production function name to a callable
+ def bind(self,pdict):
+ if self.func:
+ self.callable = pdict[self.func]
+
+
+# -----------------------------------------------------------------------------
+# class LRItem
+#
+# This class represents a specific stage of parsing a production rule. For
+# example:
+#
+# expr : expr . PLUS term
+#
+# In the above, the "." represents the current location of the parse. Here
+# basic attributes:
+#
+# name - Name of the production. For example 'expr'
+# prod - A list of symbols on the right side ['expr','.', 'PLUS','term']
+# number - Production number.
+#
+# lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term'
+# then lr_next refers to 'expr -> expr PLUS . term'
+# lr_index - LR item index (location of the ".") in the prod list.
+# lookaheads - LALR lookahead symbols for this item
+# len - Length of the production (number of symbols on right hand side)
+# lr_after - List of all productions that immediately follow
+# lr_before - Grammar symbol immediately before
+# -----------------------------------------------------------------------------
+
+class LRItem(object):
+ def __init__(self,p,n):
+ self.name = p.name
+ self.prod = list(p.prod)
+ self.number = p.number
+ self.lr_index = n
+ self.lookaheads = { }
+ self.prod.insert(n,".")
+ self.prod = tuple(self.prod)
+ self.len = len(self.prod)
+ self.usyms = p.usyms
+
+ def __str__(self):
+ if self.prod:
+ s = "%s -> %s" % (self.name," ".join(self.prod))
+ else:
+ s = "%s -> <empty>" % self.name
+ return s
+
+ def __repr__(self):
+ return "LRItem("+str(self)+")"
+
+# -----------------------------------------------------------------------------
+# rightmost_terminal()
+#
+# Return the rightmost terminal from a list of symbols. Used in add_production()
+# -----------------------------------------------------------------------------
+def rightmost_terminal(symbols, terminals):
+ i = len(symbols) - 1
+ while i >= 0:
+ if symbols[i] in terminals:
+ return symbols[i]
+ i -= 1
+ return None
+
+# -----------------------------------------------------------------------------
+# === GRAMMAR CLASS ===
+#
+# The following class represents the contents of the specified grammar along
+# with various computed properties such as first sets, follow sets, LR items, etc.
+# This data is used for critical parts of the table generation process later.
+# -----------------------------------------------------------------------------
+
+class GrammarError(YaccError): pass
+
+class Grammar(object):
+ def __init__(self,terminals):
+ self.Productions = [None] # A list of all of the productions. The first
+ # entry is always reserved for the purpose of
+ # building an augmented grammar
+
+ self.Prodnames = { } # A dictionary mapping the names of nonterminals to a list of all
+ # productions of that nonterminal.
+
+ self.Prodmap = { } # A dictionary that is only used to detect duplicate
+ # productions.
+
+ self.Terminals = { } # A dictionary mapping the names of terminal symbols to a
+ # list of the rules where they are used.
+
+ for term in terminals:
+ self.Terminals[term] = []
+
+ self.Terminals['error'] = []
+
+ self.Nonterminals = { } # A dictionary mapping names of nonterminals to a list
+ # of rule numbers where they are used.
+
+ self.First = { } # A dictionary of precomputed FIRST(x) symbols
+
+ self.Follow = { } # A dictionary of precomputed FOLLOW(x) symbols
+
+ self.Precedence = { } # Precedence rules for each terminal. Contains tuples of the
+ # form ('right',level) or ('nonassoc', level) or ('left',level)
+
+ self.UsedPrecedence = { } # Precedence rules that were actually used by the grammer.
+ # This is only used to provide error checking and to generate
+ # a warning about unused precedence rules.
+
+ self.Start = None # Starting symbol for the grammar
+
+
+ def __len__(self):
+ return len(self.Productions)
+
+ def __getitem__(self,index):
+ return self.Productions[index]
+
+ # -----------------------------------------------------------------------------
+ # set_precedence()
+ #
+ # Sets the precedence for a given terminal. assoc is the associativity such as
+ # 'left','right', or 'nonassoc'. level is a numeric level.
+ #
+ # -----------------------------------------------------------------------------
+
+ def set_precedence(self,term,assoc,level):
+ assert self.Productions == [None],"Must call set_precedence() before add_production()"
+ if term in self.Precedence:
+ raise GrammarError("Precedence already specified for terminal '%s'" % term)
+ if assoc not in ['left','right','nonassoc']:
+ raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'")
+ self.Precedence[term] = (assoc,level)
+
+ # -----------------------------------------------------------------------------
+ # add_production()
+ #
+ # Given an action function, this function assembles a production rule and
+ # computes its precedence level.
+ #
+ # The production rule is supplied as a list of symbols. For example,
+ # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and
+ # symbols ['expr','PLUS','term'].
+ #
+ # Precedence is determined by the precedence of the right-most non-terminal
+ # or the precedence of a terminal specified by %prec.
+ #
+ # A variety of error checks are performed to make sure production symbols
+ # are valid and that %prec is used correctly.
+ # -----------------------------------------------------------------------------
+
+ def add_production(self,prodname,syms,func=None,file='',line=0):
+
+ if prodname in self.Terminals:
+ raise GrammarError("%s:%d: Illegal rule name '%s'. Already defined as a token" % (file,line,prodname))
+ if prodname == 'error':
+ raise GrammarError("%s:%d: Illegal rule name '%s'. error is a reserved word" % (file,line,prodname))
+ if not _is_identifier.match(prodname):
+ raise GrammarError("%s:%d: Illegal rule name '%s'" % (file,line,prodname))
+
+ # Look for literal tokens
+ for n,s in enumerate(syms):
+ if s[0] in "'\"":
+ try:
+ c = eval(s)
+ if (len(c) > 1):
+ raise GrammarError("%s:%d: Literal token %s in rule '%s' may only be a single character" % (file,line,s, prodname))
+ if not c in self.Terminals:
+ self.Terminals[c] = []
+ syms[n] = c
+ continue
+ except SyntaxError:
+ pass
+ if not _is_identifier.match(s) and s != '%prec':
+ raise GrammarError("%s:%d: Illegal name '%s' in rule '%s'" % (file,line,s, prodname))
+
+ # Determine the precedence level
+ if '%prec' in syms:
+ if syms[-1] == '%prec':
+ raise GrammarError("%s:%d: Syntax error. Nothing follows %%prec" % (file,line))
+ if syms[-2] != '%prec':
+ raise GrammarError("%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule" % (file,line))
+ precname = syms[-1]
+ prodprec = self.Precedence.get(precname,None)
+ if not prodprec:
+ raise GrammarError("%s:%d: Nothing known about the precedence of '%s'" % (file,line,precname))
+ else:
+ self.UsedPrecedence[precname] = 1
+ del syms[-2:] # Drop %prec from the rule
+ else:
+ # If no %prec, precedence is determined by the rightmost terminal symbol
+ precname = rightmost_terminal(syms,self.Terminals)
+ prodprec = self.Precedence.get(precname,('right',0))
+
+ # See if the rule is already in the rulemap
+ map = "%s -> %s" % (prodname,syms)
+ if map in self.Prodmap:
+ m = self.Prodmap[map]
+ raise GrammarError("%s:%d: Duplicate rule %s. " % (file,line, m) +
+ "Previous definition at %s:%d" % (m.file, m.line))
+
+ # From this point on, everything is valid. Create a new Production instance
+ pnumber = len(self.Productions)
+ if not prodname in self.Nonterminals:
+ self.Nonterminals[prodname] = [ ]
+
+ # Add the production number to Terminals and Nonterminals
+ for t in syms:
+ if t in self.Terminals:
+ self.Terminals[t].append(pnumber)
+ else:
+ if not t in self.Nonterminals:
+ self.Nonterminals[t] = [ ]
+ self.Nonterminals[t].append(pnumber)
+
+ # Create a production and add it to the list of productions
+ p = Production(pnumber,prodname,syms,prodprec,func,file,line)
+ self.Productions.append(p)
+ self.Prodmap[map] = p
+
+ # Add to the global productions list
+ try:
+ self.Prodnames[prodname].append(p)
+ except KeyError:
+ self.Prodnames[prodname] = [ p ]
+ return 0
+
+ # -----------------------------------------------------------------------------
+ # set_start()
+ #
+ # Sets the starting symbol and creates the augmented grammar. Production
+ # rule 0 is S' -> start where start is the start symbol.
+ # -----------------------------------------------------------------------------
+
+ def set_start(self,start=None):
+ if not start:
+ start = self.Productions[1].name
+ if start not in self.Nonterminals:
+ raise GrammarError("start symbol %s undefined" % start)
+ self.Productions[0] = Production(0,"S'",[start])
+ self.Nonterminals[start].append(0)
+ self.Start = start
+
+ # -----------------------------------------------------------------------------
+ # find_unreachable()
+ #
+ # Find all of the nonterminal symbols that can't be reached from the starting
+ # symbol. Returns a list of nonterminals that can't be reached.
+ # -----------------------------------------------------------------------------
+
+ def find_unreachable(self):
+
+ # Mark all symbols that are reachable from a symbol s
+ def mark_reachable_from(s):
+ if reachable[s]:
+ # We've already reached symbol s.
+ return
+ reachable[s] = 1
+ for p in self.Prodnames.get(s,[]):
+ for r in p.prod:
+ mark_reachable_from(r)
+
+ reachable = { }
+ for s in list(self.Terminals) + list(self.Nonterminals):
+ reachable[s] = 0
+
+ mark_reachable_from( self.Productions[0].prod[0] )
+
+ return [s for s in list(self.Nonterminals)
+ if not reachable[s]]
+
+ # -----------------------------------------------------------------------------
+ # infinite_cycles()
+ #
+ # This function looks at the various parsing rules and tries to detect
+ # infinite recursion cycles (grammar rules where there is no possible way
+ # to derive a string of only terminals).
+ # -----------------------------------------------------------------------------
+
+ def infinite_cycles(self):
+ terminates = {}
+
+ # Terminals:
+ for t in self.Terminals:
+ terminates[t] = 1
+
+ terminates['$end'] = 1
+
+ # Nonterminals:
+
+ # Initialize to false:
+ for n in self.Nonterminals:
+ terminates[n] = 0
+
+ # Then propagate termination until no change:
+ while 1:
+ some_change = 0
+ for (n,pl) in self.Prodnames.items():
+ # Nonterminal n terminates iff any of its productions terminates.
+ for p in pl:
+ # Production p terminates iff all of its rhs symbols terminate.
+ for s in p.prod:
+ if not terminates[s]:
+ # The symbol s does not terminate,
+ # so production p does not terminate.
+ p_terminates = 0
+ break
+ else:
+ # didn't break from the loop,
+ # so every symbol s terminates
+ # so production p terminates.
+ p_terminates = 1
+
+ if p_terminates:
+ # symbol n terminates!
+ if not terminates[n]:
+ terminates[n] = 1
+ some_change = 1
+ # Don't need to consider any more productions for this n.
+ break
+
+ if not some_change:
+ break
+
+ infinite = []
+ for (s,term) in terminates.items():
+ if not term:
+ if not s in self.Prodnames and not s in self.Terminals and s != 'error':
+ # s is used-but-not-defined, and we've already warned of that,
+ # so it would be overkill to say that it's also non-terminating.
+ pass
+ else:
+ infinite.append(s)
+
+ return infinite
+
+
+ # -----------------------------------------------------------------------------
+ # undefined_symbols()
+ #
+ # Find all symbols that were used the grammar, but not defined as tokens or
+ # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol
+ # and prod is the production where the symbol was used.
+ # -----------------------------------------------------------------------------
+ def undefined_symbols(self):
+ result = []
+ for p in self.Productions:
+ if not p: continue
+
+ for s in p.prod:
+ if not s in self.Prodnames and not s in self.Terminals and s != 'error':
+ result.append((s,p))
+ return result
+
+ # -----------------------------------------------------------------------------
+ # unused_terminals()
+ #
+ # Find all terminals that were defined, but not used by the grammar. Returns
+ # a list of all symbols.
+ # -----------------------------------------------------------------------------
+ def unused_terminals(self):
+ unused_tok = []
+ for s,v in self.Terminals.items():
+ if s != 'error' and not v:
+ unused_tok.append(s)
+
+ return unused_tok
+
+ # ------------------------------------------------------------------------------
+ # unused_rules()
+ #
+ # Find all grammar rules that were defined, but not used (maybe not reachable)
+ # Returns a list of productions.
+ # ------------------------------------------------------------------------------
+
+ def unused_rules(self):
+ unused_prod = []
+ for s,v in self.Nonterminals.items():
+ if not v:
+ p = self.Prodnames[s][0]
+ unused_prod.append(p)
+ return unused_prod
+
+ # -----------------------------------------------------------------------------
+ # unused_precedence()
+ #
+ # Returns a list of tuples (term,precedence) corresponding to precedence
+ # rules that were never used by the grammar. term is the name of the terminal
+ # on which precedence was applied and precedence is a string such as 'left' or
+ # 'right' corresponding to the type of precedence.
+ # -----------------------------------------------------------------------------
+
+ def unused_precedence(self):
+ unused = []
+ for termname in self.Precedence:
+ if not (termname in self.Terminals or termname in self.UsedPrecedence):
+ unused.append((termname,self.Precedence[termname][0]))
+
+ return unused
+
+ # -------------------------------------------------------------------------
+ # _first()
+ #
+ # Compute the value of FIRST1(beta) where beta is a tuple of symbols.
+ #
+ # During execution of compute_first1, the result may be incomplete.
+ # Afterward (e.g., when called from compute_follow()), it will be complete.
+ # -------------------------------------------------------------------------
+ def _first(self,beta):
+
+ # We are computing First(x1,x2,x3,...,xn)
+ result = [ ]
+ for x in beta:
+ x_produces_empty = 0
+
+ # Add all the non-<empty> symbols of First[x] to the result.
+ for f in self.First[x]:
+ if f == '<empty>':
+ x_produces_empty = 1
+ else:
+ if f not in result: result.append(f)
+
+ if x_produces_empty:
+ # We have to consider the next x in beta,
+ # i.e. stay in the loop.
+ pass
+ else:
+ # We don't have to consider any further symbols in beta.
+ break
+ else:
+ # There was no 'break' from the loop,
+ # so x_produces_empty was true for all x in beta,
+ # so beta produces empty as well.
+ result.append('<empty>')
+
+ return result
+
+ # -------------------------------------------------------------------------
+ # compute_first()
+ #
+ # Compute the value of FIRST1(X) for all symbols
+ # -------------------------------------------------------------------------
+ def compute_first(self):
+ if self.First:
+ return self.First
+
+ # Terminals:
+ for t in self.Terminals:
+ self.First[t] = [t]
+
+ self.First['$end'] = ['$end']
+
+ # Nonterminals:
+
+ # Initialize to the empty set:
+ for n in self.Nonterminals:
+ self.First[n] = []
+
+ # Then propagate symbols until no change:
+ while 1:
+ some_change = 0
+ for n in self.Nonterminals:
+ for p in self.Prodnames[n]:
+ for f in self._first(p.prod):
+ if f not in self.First[n]:
+ self.First[n].append( f )
+ some_change = 1
+ if not some_change:
+ break
+
+ return self.First
+
+ # ---------------------------------------------------------------------
+ # compute_follow()
+ #
+ # Computes all of the follow sets for every non-terminal symbol. The
+ # follow set is the set of all symbols that might follow a given
+ # non-terminal. See the Dragon book, 2nd Ed. p. 189.
+ # ---------------------------------------------------------------------
+ def compute_follow(self,start=None):
+ # If already computed, return the result
+ if self.Follow:
+ return self.Follow
+
+ # If first sets not computed yet, do that first.
+ if not self.First:
+ self.compute_first()
+
+ # Add '$end' to the follow list of the start symbol
+ for k in self.Nonterminals:
+ self.Follow[k] = [ ]
+
+ if not start:
+ start = self.Productions[1].name
+
+ self.Follow[start] = [ '$end' ]
+
+ while 1:
+ didadd = 0
+ for p in self.Productions[1:]:
+ # Here is the production set
+ for i in range(len(p.prod)):
+ B = p.prod[i]
+ if B in self.Nonterminals:
+ # Okay. We got a non-terminal in a production
+ fst = self._first(p.prod[i+1:])
+ hasempty = 0
+ for f in fst:
+ if f != '<empty>' and f not in self.Follow[B]:
+ self.Follow[B].append(f)
+ didadd = 1
+ if f == '<empty>':
+ hasempty = 1
+ if hasempty or i == (len(p.prod)-1):
+ # Add elements of follow(a) to follow(b)
+ for f in self.Follow[p.name]:
+ if f not in self.Follow[B]:
+ self.Follow[B].append(f)
+ didadd = 1
+ if not didadd: break
+ return self.Follow
+
+
+ # -----------------------------------------------------------------------------
+ # build_lritems()
+ #
+ # This function walks the list of productions and builds a complete set of the
+ # LR items. The LR items are stored in two ways: First, they are uniquely
+ # numbered and placed in the list _lritems. Second, a linked list of LR items
+ # is built for each production. For example:
+ #
+ # E -> E PLUS E
+ #
+ # Creates the list
+ #
+ # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ]
+ # -----------------------------------------------------------------------------
+
+ def build_lritems(self):
+ for p in self.Productions:
+ lastlri = p
+ i = 0
+ lr_items = []
+ while 1:
+ if i > len(p):
+ lri = None
+ else:
+ lri = LRItem(p,i)
+ # Precompute the list of productions immediately following
+ try:
+ lri.lr_after = self.Prodnames[lri.prod[i+1]]
+ except (IndexError,KeyError):
+ lri.lr_after = []
+ try:
+ lri.lr_before = lri.prod[i-1]
+ except IndexError:
+ lri.lr_before = None
+
+ lastlri.lr_next = lri
+ if not lri: break
+ lr_items.append(lri)
+ lastlri = lri
+ i += 1
+ p.lr_items = lr_items
+
+# -----------------------------------------------------------------------------
+# == Class LRTable ==
+#
+# This basic class represents a basic table of LR parsing information.
+# Methods for generating the tables are not defined here. They are defined
+# in the derived class LRGeneratedTable.
+# -----------------------------------------------------------------------------
+
+class VersionError(YaccError): pass
+
+class LRTable(object):
+ def __init__(self):
+ self.lr_action = None
+ self.lr_goto = None
+ self.lr_productions = None
+ self.lr_method = None
+
+ def read_table(self,module):
+ if isinstance(module,types.ModuleType):
+ parsetab = module
+ else:
+ if sys.version_info[0] < 3:
+ exec("import %s as parsetab" % module)
+ else:
+ env = { }
+ exec("import %s as parsetab" % module, env, env)
+ parsetab = env['parsetab']
+
+ if parsetab._tabversion != __tabversion__:
+ raise VersionError("yacc table file version is out of date")
+
+ self.lr_action = parsetab._lr_action
+ self.lr_goto = parsetab._lr_goto
+
+ self.lr_productions = []
+ for p in parsetab._lr_productions:
+ self.lr_productions.append(MiniProduction(*p))
+
+ self.lr_method = parsetab._lr_method
+ return parsetab._lr_signature
+
+ def read_pickle(self,filename):
+ try:
+ import cPickle as pickle
+ except ImportError:
+ import pickle
+
+ in_f = open(filename,"rb")
+
+ tabversion = pickle.load(in_f)
+ if tabversion != __tabversion__:
+ raise VersionError("yacc table file version is out of date")
+ self.lr_method = pickle.load(in_f)
+ signature = pickle.load(in_f)
+ self.lr_action = pickle.load(in_f)
+ self.lr_goto = pickle.load(in_f)
+ productions = pickle.load(in_f)
+
+ self.lr_productions = []
+ for p in productions:
+ self.lr_productions.append(MiniProduction(*p))
+
+ in_f.close()
+ return signature
+
+ # Bind all production function names to callable objects in pdict
+ def bind_callables(self,pdict):
+ for p in self.lr_productions:
+ p.bind(pdict)
+
+# -----------------------------------------------------------------------------
+# === LR Generator ===
+#
+# The following classes and functions are used to generate LR parsing tables on
+# a grammar.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# digraph()
+# traverse()
+#
+# The following two functions are used to compute set valued functions
+# of the form:
+#
+# F(x) = F'(x) U U{F(y) | x R y}
+#
+# This is used to compute the values of Read() sets as well as FOLLOW sets
+# in LALR(1) generation.
+#
+# Inputs: X - An input set
+# R - A relation
+# FP - Set-valued function
+# ------------------------------------------------------------------------------
+
+def digraph(X,R,FP):
+ N = { }
+ for x in X:
+ N[x] = 0
+ stack = []
+ F = { }
+ for x in X:
+ if N[x] == 0: traverse(x,N,stack,F,X,R,FP)
+ return F
+
+def traverse(x,N,stack,F,X,R,FP):
+ stack.append(x)
+ d = len(stack)
+ N[x] = d
+ F[x] = FP(x) # F(X) <- F'(x)
+
+ rel = R(x) # Get y's related to x
+ for y in rel:
+ if N[y] == 0:
+ traverse(y,N,stack,F,X,R,FP)
+ N[x] = min(N[x],N[y])
+ for a in F.get(y,[]):
+ if a not in F[x]: F[x].append(a)
+ if N[x] == d:
+ N[stack[-1]] = MAXINT
+ F[stack[-1]] = F[x]
+ element = stack.pop()
+ while element != x:
+ N[stack[-1]] = MAXINT
+ F[stack[-1]] = F[x]
+ element = stack.pop()
+
+class LALRError(YaccError): pass
+
+# -----------------------------------------------------------------------------
+# == LRGeneratedTable ==
+#
+# This class implements the LR table generation algorithm. There are no
+# public methods except for write()
+# -----------------------------------------------------------------------------
+
+class LRGeneratedTable(LRTable):
+ def __init__(self,grammar,method='LALR',log=None):
+ if method not in ['SLR','LALR']:
+ raise LALRError("Unsupported method %s" % method)
+
+ self.grammar = grammar
+ self.lr_method = method
+
+ # Set up the logger
+ if not log:
+ log = NullLogger()
+ self.log = log
+
+ # Internal attributes
+ self.lr_action = {} # Action table
+ self.lr_goto = {} # Goto table
+ self.lr_productions = grammar.Productions # Copy of grammar Production array
+ self.lr_goto_cache = {} # Cache of computed gotos
+ self.lr0_cidhash = {} # Cache of closures
+
+ self._add_count = 0 # Internal counter used to detect cycles
+
+ # Diagonistic information filled in by the table generator
+ self.sr_conflict = 0
+ self.rr_conflict = 0
+ self.conflicts = [] # List of conflicts
+
+ self.sr_conflicts = []
+ self.rr_conflicts = []
+
+ # Build the tables
+ self.grammar.build_lritems()
+ self.grammar.compute_first()
+ self.grammar.compute_follow()
+ self.lr_parse_table()
+
+ # Compute the LR(0) closure operation on I, where I is a set of LR(0) items.
+
+ def lr0_closure(self,I):
+ self._add_count += 1
+
+ # Add everything in I to J
+ J = I[:]
+ didadd = 1
+ while didadd:
+ didadd = 0
+ for j in J:
+ for x in j.lr_after:
+ if getattr(x,"lr0_added",0) == self._add_count: continue
+ # Add B --> .G to J
+ J.append(x.lr_next)
+ x.lr0_added = self._add_count
+ didadd = 1
+
+ return J
+
+ # Compute the LR(0) goto function goto(I,X) where I is a set
+ # of LR(0) items and X is a grammar symbol. This function is written
+ # in a way that guarantees uniqueness of the generated goto sets
+ # (i.e. the same goto set will never be returned as two different Python
+ # objects). With uniqueness, we can later do fast set comparisons using
+ # id(obj) instead of element-wise comparison.
+
+ def lr0_goto(self,I,x):
+ # First we look for a previously cached entry
+ g = self.lr_goto_cache.get((id(I),x),None)
+ if g: return g
+
+ # Now we generate the goto set in a way that guarantees uniqueness
+ # of the result
+
+ s = self.lr_goto_cache.get(x,None)
+ if not s:
+ s = { }
+ self.lr_goto_cache[x] = s
+
+ gs = [ ]
+ for p in I:
+ n = p.lr_next
+ if n and n.lr_before == x:
+ s1 = s.get(id(n),None)
+ if not s1:
+ s1 = { }
+ s[id(n)] = s1
+ gs.append(n)
+ s = s1
+ g = s.get('$end',None)
+ if not g:
+ if gs:
+ g = self.lr0_closure(gs)
+ s['$end'] = g
+ else:
+ s['$end'] = gs
+ self.lr_goto_cache[(id(I),x)] = g
+ return g
+
+ # Compute the LR(0) sets of item function
+ def lr0_items(self):
+
+ C = [ self.lr0_closure([self.grammar.Productions[0].lr_next]) ]
+ i = 0
+ for I in C:
+ self.lr0_cidhash[id(I)] = i
+ i += 1
+
+ # Loop over the items in C and each grammar symbols
+ i = 0
+ while i < len(C):
+ I = C[i]
+ i += 1
+
+ # Collect all of the symbols that could possibly be in the goto(I,X) sets
+ asyms = { }
+ for ii in I:
+ for s in ii.usyms:
+ asyms[s] = None
+
+ for x in asyms:
+ g = self.lr0_goto(I,x)
+ if not g: continue
+ if id(g) in self.lr0_cidhash: continue
+ self.lr0_cidhash[id(g)] = len(C)
+ C.append(g)
+
+ return C
+
+ # -----------------------------------------------------------------------------
+ # ==== LALR(1) Parsing ====
+ #
+ # LALR(1) parsing is almost exactly the same as SLR except that instead of
+ # relying upon Follow() sets when performing reductions, a more selective
+ # lookahead set that incorporates the state of the LR(0) machine is utilized.
+ # Thus, we mainly just have to focus on calculating the lookahead sets.
+ #
+ # The method used here is due to DeRemer and Pennelo (1982).
+ #
+ # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1)
+ # Lookahead Sets", ACM Transactions on Programming Languages and Systems,
+ # Vol. 4, No. 4, Oct. 1982, pp. 615-649
+ #
+ # Further details can also be found in:
+ #
+ # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing",
+ # McGraw-Hill Book Company, (1985).
+ #
+ # -----------------------------------------------------------------------------
+
+ # -----------------------------------------------------------------------------
+ # compute_nullable_nonterminals()
+ #
+ # Creates a dictionary containing all of the non-terminals that might produce
+ # an empty production.
+ # -----------------------------------------------------------------------------
+
+ def compute_nullable_nonterminals(self):
+ nullable = {}
+ num_nullable = 0
+ while 1:
+ for p in self.grammar.Productions[1:]:
+ if p.len == 0:
+ nullable[p.name] = 1
+ continue
+ for t in p.prod:
+ if not t in nullable: break
+ else:
+ nullable[p.name] = 1
+ if len(nullable) == num_nullable: break
+ num_nullable = len(nullable)
+ return nullable
+
+ # -----------------------------------------------------------------------------
+ # find_nonterminal_trans(C)
+ #
+ # Given a set of LR(0) items, this functions finds all of the non-terminal
+ # transitions. These are transitions in which a dot appears immediately before
+ # a non-terminal. Returns a list of tuples of the form (state,N) where state
+ # is the state number and N is the nonterminal symbol.
+ #
+ # The input C is the set of LR(0) items.
+ # -----------------------------------------------------------------------------
+
+ def find_nonterminal_transitions(self,C):
+ trans = []
+ for state in range(len(C)):
+ for p in C[state]:
+ if p.lr_index < p.len - 1:
+ t = (state,p.prod[p.lr_index+1])
+ if t[1] in self.grammar.Nonterminals:
+ if t not in trans: trans.append(t)
+ state = state + 1
+ return trans
+
+ # -----------------------------------------------------------------------------
+ # dr_relation()
+ #
+ # Computes the DR(p,A) relationships for non-terminal transitions. The input
+ # is a tuple (state,N) where state is a number and N is a nonterminal symbol.
+ #
+ # Returns a list of terminals.
+ # -----------------------------------------------------------------------------
+
+ def dr_relation(self,C,trans,nullable):
+ dr_set = { }
+ state,N = trans
+ terms = []
+
+ g = self.lr0_goto(C[state],N)
+ for p in g:
+ if p.lr_index < p.len - 1:
+ a = p.prod[p.lr_index+1]
+ if a in self.grammar.Terminals:
+ if a not in terms: terms.append(a)
+
+ # This extra bit is to handle the start state
+ if state == 0 and N == self.grammar.Productions[0].prod[0]:
+ terms.append('$end')
+
+ return terms
+
+ # -----------------------------------------------------------------------------
+ # reads_relation()
+ #
+ # Computes the READS() relation (p,A) READS (t,C).
+ # -----------------------------------------------------------------------------
+
+ def reads_relation(self,C, trans, empty):
+ # Look for empty transitions
+ rel = []
+ state, N = trans
+
+ g = self.lr0_goto(C[state],N)
+ j = self.lr0_cidhash.get(id(g),-1)
+ for p in g:
+ if p.lr_index < p.len - 1:
+ a = p.prod[p.lr_index + 1]
+ if a in empty:
+ rel.append((j,a))
+
+ return rel
+
+ # -----------------------------------------------------------------------------
+ # compute_lookback_includes()
+ #
+ # Determines the lookback and includes relations
+ #
+ # LOOKBACK:
+ #
+ # This relation is determined by running the LR(0) state machine forward.
+ # For example, starting with a production "N : . A B C", we run it forward
+ # to obtain "N : A B C ." We then build a relationship between this final
+ # state and the starting state. These relationships are stored in a dictionary
+ # lookdict.
+ #
+ # INCLUDES:
+ #
+ # Computes the INCLUDE() relation (p,A) INCLUDES (p',B).
+ #
+ # This relation is used to determine non-terminal transitions that occur
+ # inside of other non-terminal transition states. (p,A) INCLUDES (p', B)
+ # if the following holds:
+ #
+ # B -> LAT, where T -> epsilon and p' -L-> p
+ #
+ # L is essentially a prefix (which may be empty), T is a suffix that must be
+ # able to derive an empty string. State p' must lead to state p with the string L.
+ #
+ # -----------------------------------------------------------------------------
+
+ def compute_lookback_includes(self,C,trans,nullable):
+
+ lookdict = {} # Dictionary of lookback relations
+ includedict = {} # Dictionary of include relations
+
+ # Make a dictionary of non-terminal transitions
+ dtrans = {}
+ for t in trans:
+ dtrans[t] = 1
+
+ # Loop over all transitions and compute lookbacks and includes
+ for state,N in trans:
+ lookb = []
+ includes = []
+ for p in C[state]:
+ if p.name != N: continue
+
+ # Okay, we have a name match. We now follow the production all the way
+ # through the state machine until we get the . on the right hand side
+
+ lr_index = p.lr_index
+ j = state
+ while lr_index < p.len - 1:
+ lr_index = lr_index + 1
+ t = p.prod[lr_index]
+
+ # Check to see if this symbol and state are a non-terminal transition
+ if (j,t) in dtrans:
+ # Yes. Okay, there is some chance that this is an includes relation
+ # the only way to know for certain is whether the rest of the
+ # production derives empty
+
+ li = lr_index + 1
+ while li < p.len:
+ if p.prod[li] in self.grammar.Terminals: break # No forget it
+ if not p.prod[li] in nullable: break
+ li = li + 1
+ else:
+ # Appears to be a relation between (j,t) and (state,N)
+ includes.append((j,t))
+
+ g = self.lr0_goto(C[j],t) # Go to next set
+ j = self.lr0_cidhash.get(id(g),-1) # Go to next state
+
+ # When we get here, j is the final state, now we have to locate the production
+ for r in C[j]:
+ if r.name != p.name: continue
+ if r.len != p.len: continue
+ i = 0
+ # This look is comparing a production ". A B C" with "A B C ."
+ while i < r.lr_index:
+ if r.prod[i] != p.prod[i+1]: break
+ i = i + 1
+ else:
+ lookb.append((j,r))
+ for i in includes:
+ if not i in includedict: includedict[i] = []
+ includedict[i].append((state,N))
+ lookdict[(state,N)] = lookb
+
+ return lookdict,includedict
+
+ # -----------------------------------------------------------------------------
+ # compute_read_sets()
+ #
+ # Given a set of LR(0) items, this function computes the read sets.
+ #
+ # Inputs: C = Set of LR(0) items
+ # ntrans = Set of nonterminal transitions
+ # nullable = Set of empty transitions
+ #
+ # Returns a set containing the read sets
+ # -----------------------------------------------------------------------------
+
+ def compute_read_sets(self,C, ntrans, nullable):
+ FP = lambda x: self.dr_relation(C,x,nullable)
+ R = lambda x: self.reads_relation(C,x,nullable)
+ F = digraph(ntrans,R,FP)
+ return F
+
+ # -----------------------------------------------------------------------------
+ # compute_follow_sets()
+ #
+ # Given a set of LR(0) items, a set of non-terminal transitions, a readset,
+ # and an include set, this function computes the follow sets
+ #
+ # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)}
+ #
+ # Inputs:
+ # ntrans = Set of nonterminal transitions
+ # readsets = Readset (previously computed)
+ # inclsets = Include sets (previously computed)
+ #
+ # Returns a set containing the follow sets
+ # -----------------------------------------------------------------------------
+
+ def compute_follow_sets(self,ntrans,readsets,inclsets):
+ FP = lambda x: readsets[x]
+ R = lambda x: inclsets.get(x,[])
+ F = digraph(ntrans,R,FP)
+ return F
+
+ # -----------------------------------------------------------------------------
+ # add_lookaheads()
+ #
+ # Attaches the lookahead symbols to grammar rules.
+ #
+ # Inputs: lookbacks - Set of lookback relations
+ # followset - Computed follow set
+ #
+ # This function directly attaches the lookaheads to productions contained
+ # in the lookbacks set
+ # -----------------------------------------------------------------------------
+
+ def add_lookaheads(self,lookbacks,followset):
+ for trans,lb in lookbacks.items():
+ # Loop over productions in lookback
+ for state,p in lb:
+ if not state in p.lookaheads:
+ p.lookaheads[state] = []
+ f = followset.get(trans,[])
+ for a in f:
+ if a not in p.lookaheads[state]: p.lookaheads[state].append(a)
+
+ # -----------------------------------------------------------------------------
+ # add_lalr_lookaheads()
+ #
+ # This function does all of the work of adding lookahead information for use
+ # with LALR parsing
+ # -----------------------------------------------------------------------------
+
+ def add_lalr_lookaheads(self,C):
+ # Determine all of the nullable nonterminals
+ nullable = self.compute_nullable_nonterminals()
+
+ # Find all non-terminal transitions
+ trans = self.find_nonterminal_transitions(C)
+
+ # Compute read sets
+ readsets = self.compute_read_sets(C,trans,nullable)
+
+ # Compute lookback/includes relations
+ lookd, included = self.compute_lookback_includes(C,trans,nullable)
+
+ # Compute LALR FOLLOW sets
+ followsets = self.compute_follow_sets(trans,readsets,included)
+
+ # Add all of the lookaheads
+ self.add_lookaheads(lookd,followsets)
+
+ # -----------------------------------------------------------------------------
+ # lr_parse_table()
+ #
+ # This function constructs the parse tables for SLR or LALR
+ # -----------------------------------------------------------------------------
+ def lr_parse_table(self):
+ Productions = self.grammar.Productions
+ Precedence = self.grammar.Precedence
+ goto = self.lr_goto # Goto array
+ action = self.lr_action # Action array
+ log = self.log # Logger for output
+
+ actionp = { } # Action production array (temporary)
+
+ log.info("Parsing method: %s", self.lr_method)
+
+ # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items
+ # This determines the number of states
+
+ C = self.lr0_items()
+
+ if self.lr_method == 'LALR':
+ self.add_lalr_lookaheads(C)
+
+ # Build the parser table, state by state
+ st = 0
+ for I in C:
+ # Loop over each production in I
+ actlist = [ ] # List of actions
+ st_action = { }
+ st_actionp = { }
+ st_goto = { }
+ log.info("")
+ log.info("state %d", st)
+ log.info("")
+ for p in I:
+ log.info(" (%d) %s", p.number, str(p))
+ log.info("")
+
+ for p in I:
+ if p.len == p.lr_index + 1:
+ if p.name == "S'":
+ # Start symbol. Accept!
+ st_action["$end"] = 0
+ st_actionp["$end"] = p
+ else:
+ # We are at the end of a production. Reduce!
+ if self.lr_method == 'LALR':
+ laheads = p.lookaheads[st]
+ else:
+ laheads = self.grammar.Follow[p.name]
+ for a in laheads:
+ actlist.append((a,p,"reduce using rule %d (%s)" % (p.number,p)))
+ r = st_action.get(a,None)
+ if r is not None:
+ # Whoa. Have a shift/reduce or reduce/reduce conflict
+ if r > 0:
+ # Need to decide on shift or reduce here
+ # By default we favor shifting. Need to add
+ # some precedence rules here.
+ sprec,slevel = Productions[st_actionp[a].number].prec
+ rprec,rlevel = Precedence.get(a,('right',0))
+ if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')):
+ # We really need to reduce here.
+ st_action[a] = -p.number
+ st_actionp[a] = p
+ if not slevel and not rlevel:
+ log.info(" ! shift/reduce conflict for %s resolved as reduce",a)
+ self.sr_conflicts.append((st,a,'reduce'))
+ Productions[p.number].reduced += 1
+ elif (slevel == rlevel) and (rprec == 'nonassoc'):
+ st_action[a] = None
+ else:
+ # Hmmm. Guess we'll keep the shift
+ if not rlevel:
+ log.info(" ! shift/reduce conflict for %s resolved as shift",a)
+ self.sr_conflicts.append((st,a,'shift'))
+ elif r < 0:
+ # Reduce/reduce conflict. In this case, we favor the rule
+ # that was defined first in the grammar file
+ oldp = Productions[-r]
+ pp = Productions[p.number]
+ if oldp.line > pp.line:
+ st_action[a] = -p.number
+ st_actionp[a] = p
+ chosenp,rejectp = pp,oldp
+ Productions[p.number].reduced += 1
+ Productions[oldp.number].reduced -= 1
+ else:
+ chosenp,rejectp = oldp,pp
+ self.rr_conflicts.append((st,chosenp,rejectp))
+ log.info(" ! reduce/reduce conflict for %s resolved using rule %d (%s)", a,st_actionp[a].number, st_actionp[a])
+ else:
+ raise LALRError("Unknown conflict in state %d" % st)
+ else:
+ st_action[a] = -p.number
+ st_actionp[a] = p
+ Productions[p.number].reduced += 1
+ else:
+ i = p.lr_index
+ a = p.prod[i+1] # Get symbol right after the "."
+ if a in self.grammar.Terminals:
+ g = self.lr0_goto(I,a)
+ j = self.lr0_cidhash.get(id(g),-1)
+ if j >= 0:
+ # We are in a shift state
+ actlist.append((a,p,"shift and go to state %d" % j))
+ r = st_action.get(a,None)
+ if r is not None:
+ # Whoa have a shift/reduce or shift/shift conflict
+ if r > 0:
+ if r != j:
+ raise LALRError("Shift/shift conflict in state %d" % st)
+ elif r < 0:
+ # Do a precedence check.
+ # - if precedence of reduce rule is higher, we reduce.
+ # - if precedence of reduce is same and left assoc, we reduce.
+ # - otherwise we shift
+ rprec,rlevel = Productions[st_actionp[a].number].prec
+ sprec,slevel = Precedence.get(a,('right',0))
+ if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')):
+ # We decide to shift here... highest precedence to shift
+ Productions[st_actionp[a].number].reduced -= 1
+ st_action[a] = j
+ st_actionp[a] = p
+ if not rlevel:
+ log.info(" ! shift/reduce conflict for %s resolved as shift",a)
+ self.sr_conflicts.append((st,a,'shift'))
+ elif (slevel == rlevel) and (rprec == 'nonassoc'):
+ st_action[a] = None
+ else:
+ # Hmmm. Guess we'll keep the reduce
+ if not slevel and not rlevel:
+ log.info(" ! shift/reduce conflict for %s resolved as reduce",a)
+ self.sr_conflicts.append((st,a,'reduce'))
+
+ else:
+ raise LALRError("Unknown conflict in state %d" % st)
+ else:
+ st_action[a] = j
+ st_actionp[a] = p
+
+ # Print the actions associated with each terminal
+ _actprint = { }
+ for a,p,m in actlist:
+ if a in st_action:
+ if p is st_actionp[a]:
+ log.info(" %-15s %s",a,m)
+ _actprint[(a,m)] = 1
+ log.info("")
+ # Print the actions that were not used. (debugging)
+ not_used = 0
+ for a,p,m in actlist:
+ if a in st_action:
+ if p is not st_actionp[a]:
+ if not (a,m) in _actprint:
+ log.debug(" ! %-15s [ %s ]",a,m)
+ not_used = 1
+ _actprint[(a,m)] = 1
+ if not_used:
+ log.debug("")
+
+ # Construct the goto table for this state
+
+ nkeys = { }
+ for ii in I:
+ for s in ii.usyms:
+ if s in self.grammar.Nonterminals:
+ nkeys[s] = None
+ for n in nkeys:
+ g = self.lr0_goto(I,n)
+ j = self.lr0_cidhash.get(id(g),-1)
+ if j >= 0:
+ st_goto[n] = j
+ log.info(" %-30s shift and go to state %d",n,j)
+
+ action[st] = st_action
+ actionp[st] = st_actionp
+ goto[st] = st_goto
+ st += 1
+
+
+ # -----------------------------------------------------------------------------
+ # write()
+ #
+ # This function writes the LR parsing tables to a file
+ # -----------------------------------------------------------------------------
+
+ def write_table(self,modulename,outputdir='',signature=""):
+ basemodulename = modulename.split(".")[-1]
+ filename = os.path.join(outputdir,basemodulename) + ".py"
+ try:
+ f = open(filename,"w")
+
+ f.write("""
+# %s
+# This file is automatically generated. Do not edit.
+_tabversion = %r
+
+_lr_method = %r
+
+_lr_signature = %r
+ """ % (filename, __tabversion__, self.lr_method, signature))
+
+ # Change smaller to 0 to go back to original tables
+ smaller = 1
+
+ # Factor out names to try and make smaller
+ if smaller:
+ items = { }
+
+ for s,nd in self.lr_action.items():
+ for name,v in nd.items():
+ i = items.get(name)
+ if not i:
+ i = ([],[])
+ items[name] = i
+ i[0].append(s)
+ i[1].append(v)
+
+ f.write("\n_lr_action_items = {")
+ for k,v in items.items():
+ f.write("%r:([" % k)
+ for i in v[0]:
+ f.write("%r," % i)
+ f.write("],[")
+ for i in v[1]:
+ f.write("%r," % i)
+
+ f.write("]),")
+ f.write("}\n")
+
+ f.write("""
+_lr_action = { }
+for _k, _v in _lr_action_items.items():
+ for _x,_y in zip(_v[0],_v[1]):
+ if not _x in _lr_action: _lr_action[_x] = { }
+ _lr_action[_x][_k] = _y
+del _lr_action_items
+""")
+
+ else:
+ f.write("\n_lr_action = { ");
+ for k,v in self.lr_action.items():
+ f.write("(%r,%r):%r," % (k[0],k[1],v))
+ f.write("}\n");
+
+ if smaller:
+ # Factor out names to try and make smaller
+ items = { }
+
+ for s,nd in self.lr_goto.items():
+ for name,v in nd.items():
+ i = items.get(name)
+ if not i:
+ i = ([],[])
+ items[name] = i
+ i[0].append(s)
+ i[1].append(v)
+
+ f.write("\n_lr_goto_items = {")
+ for k,v in items.items():
+ f.write("%r:([" % k)
+ for i in v[0]:
+ f.write("%r," % i)
+ f.write("],[")
+ for i in v[1]:
+ f.write("%r," % i)
+
+ f.write("]),")
+ f.write("}\n")
+
+ f.write("""
+_lr_goto = { }
+for _k, _v in _lr_goto_items.items():
+ for _x,_y in zip(_v[0],_v[1]):
+ if not _x in _lr_goto: _lr_goto[_x] = { }
+ _lr_goto[_x][_k] = _y
+del _lr_goto_items
+""")
+ else:
+ f.write("\n_lr_goto = { ");
+ for k,v in self.lr_goto.items():
+ f.write("(%r,%r):%r," % (k[0],k[1],v))
+ f.write("}\n");
+
+ # Write production table
+ f.write("_lr_productions = [\n")
+ for p in self.lr_productions:
+ if p.func:
+ f.write(" (%r,%r,%d,%r,%r,%d),\n" % (p.str,p.name, p.len, p.func,p.file,p.line))
+ else:
+ f.write(" (%r,%r,%d,None,None,None),\n" % (str(p),p.name, p.len))
+ f.write("]\n")
+ f.close()
+
+ except IOError:
+ e = sys.exc_info()[1]
+ sys.stderr.write("Unable to create '%s'\n" % filename)
+ sys.stderr.write(str(e)+"\n")
+ return
+
+
+ # -----------------------------------------------------------------------------
+ # pickle_table()
+ #
+ # This function pickles the LR parsing tables to a supplied file object
+ # -----------------------------------------------------------------------------
+
+ def pickle_table(self,filename,signature=""):
+ try:
+ import cPickle as pickle
+ except ImportError:
+ import pickle
+ outf = open(filename,"wb")
+ pickle.dump(__tabversion__,outf,pickle_protocol)
+ pickle.dump(self.lr_method,outf,pickle_protocol)
+ pickle.dump(signature,outf,pickle_protocol)
+ pickle.dump(self.lr_action,outf,pickle_protocol)
+ pickle.dump(self.lr_goto,outf,pickle_protocol)
+
+ outp = []
+ for p in self.lr_productions:
+ if p.func:
+ outp.append((p.str,p.name, p.len, p.func,p.file,p.line))
+ else:
+ outp.append((str(p),p.name,p.len,None,None,None))
+ pickle.dump(outp,outf,pickle_protocol)
+ outf.close()
+
+# -----------------------------------------------------------------------------
+# === INTROSPECTION ===
+#
+# The following functions and classes are used to implement the PLY
+# introspection features followed by the yacc() function itself.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# get_caller_module_dict()
+#
+# This function returns a dictionary containing all of the symbols defined within
+# a caller further down the call stack. This is used to get the environment
+# associated with the yacc() call if none was provided.
+# -----------------------------------------------------------------------------
+
+def get_caller_module_dict(levels):
+ try:
+ raise RuntimeError
+ except RuntimeError:
+ e,b,t = sys.exc_info()
+ f = t.tb_frame
+ while levels > 0:
+ f = f.f_back
+ levels -= 1
+ ldict = f.f_globals.copy()
+ if f.f_globals != f.f_locals:
+ ldict.update(f.f_locals)
+
+ return ldict
+
+# -----------------------------------------------------------------------------
+# parse_grammar()
+#
+# This takes a raw grammar rule string and parses it into production data
+# -----------------------------------------------------------------------------
+def parse_grammar(doc,file,line):
+ grammar = []
+ # Split the doc string into lines
+ pstrings = doc.splitlines()
+ lastp = None
+ dline = line
+ for ps in pstrings:
+ dline += 1
+ p = ps.split()
+ if not p: continue
+ try:
+ if p[0] == '|':
+ # This is a continuation of a previous rule
+ if not lastp:
+ raise SyntaxError("%s:%d: Misplaced '|'" % (file,dline))
+ prodname = lastp
+ syms = p[1:]
+ else:
+ prodname = p[0]
+ lastp = prodname
+ syms = p[2:]
+ assign = p[1]
+ if assign != ':' and assign != '::=':
+ raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file,dline))
+
+ grammar.append((file,dline,prodname,syms))
+ except SyntaxError:
+ raise
+ except Exception:
+ raise SyntaxError("%s:%d: Syntax error in rule '%s'" % (file,dline,ps.strip()))
+
+ return grammar
+
+# -----------------------------------------------------------------------------
+# ParserReflect()
+#
+# This class represents information extracted for building a parser including
+# start symbol, error function, tokens, precedence list, action functions,
+# etc.
+# -----------------------------------------------------------------------------
+class ParserReflect(object):
+ def __init__(self,pdict,log=None):
+ self.pdict = pdict
+ self.start = None
+ self.error_func = None
+ self.tokens = None
+ self.files = {}
+ self.grammar = []
+ self.error = 0
+
+ if log is None:
+ self.log = PlyLogger(sys.stderr)
+ else:
+ self.log = log
+
+ # Get all of the basic information
+ def get_all(self):
+ self.get_start()
+ self.get_error_func()
+ self.get_tokens()
+ self.get_precedence()
+ self.get_pfunctions()
+
+ # Validate all of the information
+ def validate_all(self):
+ self.validate_start()
+ self.validate_error_func()
+ self.validate_tokens()
+ self.validate_precedence()
+ self.validate_pfunctions()
+ self.validate_files()
+ return self.error
+
+ # Compute a signature over the grammar
+ def signature(self):
+ try:
+ from hashlib import md5
+ except ImportError:
+ from md5 import md5
+ try:
+ sig = md5()
+ if self.start:
+ sig.update(self.start.encode('latin-1'))
+ if self.prec:
+ sig.update("".join(["".join(p) for p in self.prec]).encode('latin-1'))
+ if self.tokens:
+ sig.update(" ".join(self.tokens).encode('latin-1'))
+ for f in self.pfuncs:
+ if f[3]:
+ sig.update(f[3].encode('latin-1'))
+ except (TypeError,ValueError):
+ pass
+ return sig.digest()
+
+ # -----------------------------------------------------------------------------
+ # validate_file()
+ #
+ # This method checks to see if there are duplicated p_rulename() functions
+ # in the parser module file. Without this function, it is really easy for
+ # users to make mistakes by cutting and pasting code fragments (and it's a real
+ # bugger to try and figure out why the resulting parser doesn't work). Therefore,
+ # we just do a little regular expression pattern matching of def statements
+ # to try and detect duplicates.
+ # -----------------------------------------------------------------------------
+
+ def validate_files(self):
+ # Match def p_funcname(
+ fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(')
+
+ for filename in self.files.keys():
+ base,ext = os.path.splitext(filename)
+ if ext != '.py': return 1 # No idea. Assume it's okay.
+
+ try:
+ f = open(filename)
+ lines = f.readlines()
+ f.close()
+ except IOError:
+ continue
+
+ counthash = { }
+ for linen,l in enumerate(lines):
+ linen += 1
+ m = fre.match(l)
+ if m:
+ name = m.group(1)
+ prev = counthash.get(name)
+ if not prev:
+ counthash[name] = linen
+ else:
+ self.log.warning("%s:%d: Function %s redefined. Previously defined on line %d", filename,linen,name,prev)
+
+ # Get the start symbol
+ def get_start(self):
+ self.start = self.pdict.get('start')
+
+ # Validate the start symbol
+ def validate_start(self):
+ if self.start is not None:
+ if not isinstance(self.start,str):
+ self.log.error("'start' must be a string")
+
+ # Look for error handler
+ def get_error_func(self):
+ self.error_func = self.pdict.get('p_error')
+
+ # Validate the error function
+ def validate_error_func(self):
+ if self.error_func:
+ if isinstance(self.error_func,types.FunctionType):
+ ismethod = 0
+ elif isinstance(self.error_func, types.MethodType):
+ ismethod = 1
+ else:
+ self.log.error("'p_error' defined, but is not a function or method")
+ self.error = 1
+ return
+
+ eline = func_code(self.error_func).co_firstlineno
+ efile = func_code(self.error_func).co_filename
+ self.files[efile] = 1
+
+ if (func_code(self.error_func).co_argcount != 1+ismethod):
+ self.log.error("%s:%d: p_error() requires 1 argument",efile,eline)
+ self.error = 1
+
+ # Get the tokens map
+ def get_tokens(self):
+ tokens = self.pdict.get("tokens",None)
+ if not tokens:
+ self.log.error("No token list is defined")
+ self.error = 1
+ return
+
+ if not isinstance(tokens,(list, tuple)):
+ self.log.error("tokens must be a list or tuple")
+ self.error = 1
+ return
+
+ if not tokens:
+ self.log.error("tokens is empty")
+ self.error = 1
+ return
+
+ self.tokens = tokens
+
+ # Validate the tokens
+ def validate_tokens(self):
+ # Validate the tokens.
+ if 'error' in self.tokens:
+ self.log.error("Illegal token name 'error'. Is a reserved word")
+ self.error = 1
+ return
+
+ terminals = {}
+ for n in self.tokens:
+ if n in terminals:
+ self.log.warning("Token '%s' multiply defined", n)
+ terminals[n] = 1
+
+ # Get the precedence map (if any)
+ def get_precedence(self):
+ self.prec = self.pdict.get("precedence",None)
+
+ # Validate and parse the precedence map
+ def validate_precedence(self):
+ preclist = []
+ if self.prec:
+ if not isinstance(self.prec,(list,tuple)):
+ self.log.error("precedence must be a list or tuple")
+ self.error = 1
+ return
+ for level,p in enumerate(self.prec):
+ if not isinstance(p,(list,tuple)):
+ self.log.error("Bad precedence table")
+ self.error = 1
+ return
+
+ if len(p) < 2:
+ self.log.error("Malformed precedence entry %s. Must be (assoc, term, ..., term)",p)
+ self.error = 1
+ return
+ assoc = p[0]
+ if not isinstance(assoc,str):
+ self.log.error("precedence associativity must be a string")
+ self.error = 1
+ return
+ for term in p[1:]:
+ if not isinstance(term,str):
+ self.log.error("precedence items must be strings")
+ self.error = 1
+ return
+ preclist.append((term,assoc,level+1))
+ self.preclist = preclist
+
+ # Get all p_functions from the grammar
+ def get_pfunctions(self):
+ p_functions = []
+ for name, item in self.pdict.items():
+ if name[:2] != 'p_': continue
+ if name == 'p_error': continue
+ if isinstance(item,(types.FunctionType,types.MethodType)):
+ line = func_code(item).co_firstlineno
+ file = func_code(item).co_filename
+ p_functions.append((line,file,name,item.__doc__))
+
+ # Sort all of the actions by line number
+ p_functions.sort()
+ self.pfuncs = p_functions
+
+
+ # Validate all of the p_functions
+ def validate_pfunctions(self):
+ grammar = []
+ # Check for non-empty symbols
+ if len(self.pfuncs) == 0:
+ self.log.error("no rules of the form p_rulename are defined")
+ self.error = 1
+ return
+
+ for line, file, name, doc in self.pfuncs:
+ func = self.pdict[name]
+ if isinstance(func, types.MethodType):
+ reqargs = 2
+ else:
+ reqargs = 1
+ if func_code(func).co_argcount > reqargs:
+ self.log.error("%s:%d: Rule '%s' has too many arguments",file,line,func.__name__)
+ self.error = 1
+ elif func_code(func).co_argcount < reqargs:
+ self.log.error("%s:%d: Rule '%s' requires an argument",file,line,func.__name__)
+ self.error = 1
+ elif not func.__doc__:
+ self.log.warning("%s:%d: No documentation string specified in function '%s' (ignored)",file,line,func.__name__)
+ else:
+ try:
+ parsed_g = parse_grammar(doc,file,line)
+ for g in parsed_g:
+ grammar.append((name, g))
+ except SyntaxError:
+ e = sys.exc_info()[1]
+ self.log.error(str(e))
+ self.error = 1
+
+ # Looks like a valid grammar rule
+ # Mark the file in which defined.
+ self.files[file] = 1
+
+ # Secondary validation step that looks for p_ definitions that are not functions
+ # or functions that look like they might be grammar rules.
+
+ for n,v in self.pdict.items():
+ if n[0:2] == 'p_' and isinstance(v, (types.FunctionType, types.MethodType)): continue
+ if n[0:2] == 't_': continue
+ if n[0:2] == 'p_' and n != 'p_error':
+ self.log.warning("'%s' not defined as a function", n)
+ if ((isinstance(v,types.FunctionType) and func_code(v).co_argcount == 1) or
+ (isinstance(v,types.MethodType) and func_code(v).co_argcount == 2)):
+ try:
+ doc = v.__doc__.split(" ")
+ if doc[1] == ':':
+ self.log.warning("%s:%d: Possible grammar rule '%s' defined without p_ prefix",
+ func_code(v).co_filename, func_code(v).co_firstlineno,n)
+ except Exception:
+ pass
+
+ self.grammar = grammar
+
+# -----------------------------------------------------------------------------
+# yacc(module)
+#
+# Build a parser
+# -----------------------------------------------------------------------------
+
+def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None,
+ check_recursion=1, optimize=0, write_tables=1, debugfile=debug_file,outputdir='',
+ debuglog=None, errorlog = None, picklefile=None):
+
+ global parse # Reference to the parsing method of the last built parser
+
+ # If pickling is enabled, table files are not created
+
+ if picklefile:
+ write_tables = 0
+
+ if errorlog is None:
+ errorlog = PlyLogger(sys.stderr)
+
+ # Get the module dictionary used for the parser
+ if module:
+ _items = [(k,getattr(module,k)) for k in dir(module)]
+ pdict = dict(_items)
+ else:
+ pdict = get_caller_module_dict(2)
+
+ # Collect parser information from the dictionary
+ pinfo = ParserReflect(pdict,log=errorlog)
+ pinfo.get_all()
+
+ if pinfo.error:
+ raise YaccError("Unable to build parser")
+
+ # Check signature against table files (if any)
+ signature = pinfo.signature()
+
+ # Read the tables
+ try:
+ lr = LRTable()
+ if picklefile:
+ read_signature = lr.read_pickle(picklefile)
+ else:
+ read_signature = lr.read_table(tabmodule)
+ if optimize or (read_signature == signature):
+ try:
+ lr.bind_callables(pinfo.pdict)
+ parser = LRParser(lr,pinfo.error_func)
+ parse = parser.parse
+ return parser
+ except Exception:
+ e = sys.exc_info()[1]
+ errorlog.warning("There was a problem loading the table file: %s", repr(e))
+ except VersionError:
+ e = sys.exc_info()
+ errorlog.warning(str(e))
+ except Exception:
+ pass
+
+ if debuglog is None:
+ if debug:
+ debuglog = PlyLogger(open(debugfile,"w"))
+ else:
+ debuglog = NullLogger()
+
+ debuglog.info("Created by PLY version %s (http://www.dabeaz.com/ply)", __version__)
+
+
+ errors = 0
+
+ # Validate the parser information
+ if pinfo.validate_all():
+ raise YaccError("Unable to build parser")
+
+ if not pinfo.error_func:
+ errorlog.warning("no p_error() function is defined")
+
+ # Create a grammar object
+ grammar = Grammar(pinfo.tokens)
+
+ # Set precedence level for terminals
+ for term, assoc, level in pinfo.preclist:
+ try:
+ grammar.set_precedence(term,assoc,level)
+ except GrammarError:
+ e = sys.exc_info()[1]
+ errorlog.warning("%s",str(e))
+
+ # Add productions to the grammar
+ for funcname, gram in pinfo.grammar:
+ file, line, prodname, syms = gram
+ try:
+ grammar.add_production(prodname,syms,funcname,file,line)
+ except GrammarError:
+ e = sys.exc_info()[1]
+ errorlog.error("%s",str(e))
+ errors = 1
+
+ # Set the grammar start symbols
+ try:
+ if start is None:
+ grammar.set_start(pinfo.start)
+ else:
+ grammar.set_start(start)
+ except GrammarError:
+ e = sys.exc_info()[1]
+ errorlog.error(str(e))
+ errors = 1
+
+ if errors:
+ raise YaccError("Unable to build parser")
+
+ # Verify the grammar structure
+ undefined_symbols = grammar.undefined_symbols()
+ for sym, prod in undefined_symbols:
+ errorlog.error("%s:%d: Symbol '%s' used, but not defined as a token or a rule",prod.file,prod.line,sym)
+ errors = 1
+
+ unused_terminals = grammar.unused_terminals()
+ if unused_terminals:
+ debuglog.info("")
+ debuglog.info("Unused terminals:")
+ debuglog.info("")
+ for term in unused_terminals:
+ errorlog.warning("Token '%s' defined, but not used", term)
+ debuglog.info(" %s", term)
+
+ # Print out all productions to the debug log
+ if debug:
+ debuglog.info("")
+ debuglog.info("Grammar")
+ debuglog.info("")
+ for n,p in enumerate(grammar.Productions):
+ debuglog.info("Rule %-5d %s", n, p)
+
+ # Find unused non-terminals
+ unused_rules = grammar.unused_rules()
+ for prod in unused_rules:
+ errorlog.warning("%s:%d: Rule '%s' defined, but not used", prod.file, prod.line, prod.name)
+
+ if len(unused_terminals) == 1:
+ errorlog.warning("There is 1 unused token")
+ if len(unused_terminals) > 1:
+ errorlog.warning("There are %d unused tokens", len(unused_terminals))
+
+ if len(unused_rules) == 1:
+ errorlog.warning("There is 1 unused rule")
+ if len(unused_rules) > 1:
+ errorlog.warning("There are %d unused rules", len(unused_rules))
+
+ if debug:
+ debuglog.info("")
+ debuglog.info("Terminals, with rules where they appear")
+ debuglog.info("")
+ terms = list(grammar.Terminals)
+ terms.sort()
+ for term in terms:
+ debuglog.info("%-20s : %s", term, " ".join([str(s) for s in grammar.Terminals[term]]))
+
+ debuglog.info("")
+ debuglog.info("Nonterminals, with rules where they appear")
+ debuglog.info("")
+ nonterms = list(grammar.Nonterminals)
+ nonterms.sort()
+ for nonterm in nonterms:
+ debuglog.info("%-20s : %s", nonterm, " ".join([str(s) for s in grammar.Nonterminals[nonterm]]))
+ debuglog.info("")
+
+ if check_recursion:
+ unreachable = grammar.find_unreachable()
+ for u in unreachable:
+ errorlog.warning("Symbol '%s' is unreachable",u)
+
+ infinite = grammar.infinite_cycles()
+ for inf in infinite:
+ errorlog.error("Infinite recursion detected for symbol '%s'", inf)
+ errors = 1
+
+ unused_prec = grammar.unused_precedence()
+ for term, assoc in unused_prec:
+ errorlog.error("Precedence rule '%s' defined for unknown symbol '%s'", assoc, term)
+ errors = 1
+
+ if errors:
+ raise YaccError("Unable to build parser")
+
+ # Run the LRGeneratedTable on the grammar
+ if debug:
+ errorlog.debug("Generating %s tables", method)
+
+ lr = LRGeneratedTable(grammar,method,debuglog)
+
+ if debug:
+ num_sr = len(lr.sr_conflicts)
+
+ # Report shift/reduce and reduce/reduce conflicts
+ if num_sr == 1:
+ errorlog.warning("1 shift/reduce conflict")
+ elif num_sr > 1:
+ errorlog.warning("%d shift/reduce conflicts", num_sr)
+
+ num_rr = len(lr.rr_conflicts)
+ if num_rr == 1:
+ errorlog.warning("1 reduce/reduce conflict")
+ elif num_rr > 1:
+ errorlog.warning("%d reduce/reduce conflicts", num_rr)
+
+ # Write out conflicts to the output file
+ if debug and (lr.sr_conflicts or lr.rr_conflicts):
+ debuglog.warning("")
+ debuglog.warning("Conflicts:")
+ debuglog.warning("")
+
+ for state, tok, resolution in lr.sr_conflicts:
+ debuglog.warning("shift/reduce conflict for %s in state %d resolved as %s", tok, state, resolution)
+
+ already_reported = {}
+ for state, rule, rejected in lr.rr_conflicts:
+ if (state,id(rule),id(rejected)) in already_reported:
+ continue
+ debuglog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
+ debuglog.warning("rejected rule (%s) in state %d", rejected,state)
+ errorlog.warning("reduce/reduce conflict in state %d resolved using rule (%s)", state, rule)
+ errorlog.warning("rejected rule (%s) in state %d", rejected, state)
+ already_reported[state,id(rule),id(rejected)] = 1
+
+ warned_never = []
+ for state, rule, rejected in lr.rr_conflicts:
+ if not rejected.reduced and (rejected not in warned_never):
+ debuglog.warning("Rule (%s) is never reduced", rejected)
+ errorlog.warning("Rule (%s) is never reduced", rejected)
+ warned_never.append(rejected)
+
+ # Write the table file if requested
+ if write_tables:
+ lr.write_table(tabmodule,outputdir,signature)
+
+ # Write a pickled version of the tables
+ if picklefile:
+ lr.pickle_table(picklefile,signature)
+
+ # Build the parser
+ lr.bind_callables(pinfo.pdict)
+ parser = LRParser(lr,pinfo.error_func)
+
+ parse = parser.parse
+ return parser
diff --git a/components/script/dom/bindings/codegen/pythonpath.py b/components/script/dom/bindings/codegen/pythonpath.py
new file mode 100644
index 00000000000..49b2d2f740f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/pythonpath.py
@@ -0,0 +1,60 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+Run a python script, adding extra directories to the python path.
+"""
+
+
+def main(args):
+ def usage():
+ print >>sys.stderr, "pythonpath.py -I directory script.py [args...]"
+ sys.exit(150)
+
+ paths = []
+
+ while True:
+ try:
+ arg = args[0]
+ except IndexError:
+ usage()
+
+ if arg == '-I':
+ args.pop(0)
+ try:
+ path = args.pop(0)
+ except IndexError:
+ usage()
+
+ paths.append(os.path.abspath(path))
+ continue
+
+ if arg.startswith('-I'):
+ paths.append(os.path.abspath(args.pop(0)[2:]))
+ continue
+
+ if arg.startswith('-D'):
+ os.chdir(args.pop(0)[2:])
+ continue
+
+ break
+
+ script = args[0]
+
+ sys.path[0:0] = [os.path.abspath(os.path.dirname(script))] + paths
+ sys.argv = args
+ sys.argc = len(args)
+
+ frozenglobals['__name__'] = '__main__'
+ frozenglobals['__file__'] = script
+
+ execfile(script, frozenglobals)
+
+# Freeze scope here ... why this makes things work I have no idea ...
+frozenglobals = globals()
+
+import sys, os
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp
new file mode 100644
index 00000000000..dfa17d23400
--- /dev/null
+++ b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.cpp
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Skeleton.h"
+#include "mozilla/dom/SkeletonBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Skeleton)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Skeleton)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Skeleton)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Skeleton)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Skeleton::Skeleton()
+{
+ SetIsDOMBinding();
+}
+
+Skeleton::~Skeleton()
+{
+}
+
+JSObject*
+Skeleton::WrapObject(JSContext* aCx, JSObject* aScope,
+ bool* aTriedToWrap)
+{
+ return SkeletonBinding::Wrap(aCx, aScope, this, aTriedToWrap);
+}
+
+}
+}
+
diff --git a/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h
new file mode 100644
index 00000000000..286cff9af4a
--- /dev/null
+++ b/components/script/dom/bindings/codegen/stubgenerator/Skeleton.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+
+struct JSContext;
+
+namespace mozilla {
+namespace dom {
+
+class Skeleton MOZ_FINAL : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ Skeleton();
+ ~Skeleton();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Skeleton)
+
+ void* GetParentObject() const
+ {
+ // TODO: return something sensible here, and change the return type
+ return somethingSensible;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
+ bool* aTriedToWrap);
+};
+
+}
+}
+
diff --git a/components/script/dom/bindings/codegen/stubgenerator/generate.sh b/components/script/dom/bindings/codegen/stubgenerator/generate.sh
new file mode 100644
index 00000000000..52577f6f42f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/stubgenerator/generate.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# This script creates a skeleton implementation for a C++ class which
+# implements a Web IDL interface.
+
+# This script is released into the public domain.
+
+if [ -z "$1" ]; then
+ echo usage: ./generate.sh ClassName
+ exit 1
+fi
+
+expression="s/Skeleton/$1/g"
+
+sed "$expression" < Skeleton.h > "$1.h"
+sed "$expression" < Skeleton.cpp > "$1.cpp"
+
diff --git a/components/script/dom/bindings/codegen/test/Makefile.in b/components/script/dom/bindings/codegen/test/Makefile.in
new file mode 100644
index 00000000000..d8104db5ffd
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/Makefile.in
@@ -0,0 +1,87 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+MODULE = dom
+LIBRARY_NAME = dombindings_test_s
+LIBXUL_LIBRARY = 1
+FORCE_STATIC_LIB = 1
+# Do NOT export this library. We don't actually want our test code
+# being added to libxul or anything.
+
+include $(DEPTH)/config/autoconf.mk
+
+# Need this to find all our DOM source files.
+include $(topsrcdir)/dom/dom-config.mk
+
+# And need this for $(test_webidl_files)
+include $(topsrcdir)/dom/webidl/WebIDL.mk
+
+# But the webidl actually lives in our parent dir
+test_webidl_files := $(addprefix ../,$(test_webidl_files))
+
+CPPSRCS := $(subst .webidl,Binding.cpp,$(test_webidl_files))
+
+LOCAL_INCLUDES += \
+ -I$(topsrcdir)/js/xpconnect/src \
+ -I$(topsrcdir)/js/xpconnect/wrappers \
+ -I$(topsrcdir)/dom/bindings \
+ $(NULL)
+
+
+# If you change bindinggen_dependencies here, change it in
+# dom/bindings/Makefile.in too. But note that we include ../Makefile
+# here manually, since $(GLOBAL_DEPS) won't cover it.
+bindinggen_dependencies := \
+ ../BindingGen.py \
+ ../Bindings.conf \
+ ../Configuration.py \
+ ../Codegen.py \
+ ../parser/WebIDL.py \
+ ../ParserResults.pkl \
+ ../Makefile \
+ $(GLOBAL_DEPS) \
+ $(NULL)
+
+MOCHITEST_FILES := \
+ test_bug773326.html \
+ test_enums.html \
+ test_integers.html \
+ test_interfaceToString.html \
+ test_lookupGetter.html \
+ test_InstanceOf.html \
+ test_traceProtos.html \
+ test_forOf.html \
+ forOf_iframe.html \
+ test_sequence_wrapping.html \
+ file_bug775543.html \
+ test_bug788369.html \
+ $(NULL)
+
+MOCHITEST_CHROME_FILES = \
+ test_bug775543.html \
+ $(NULL)
+
+# Include rules.mk before any of our targets so our first target is coming from
+# rules.mk and running make with no target in this dir does the right thing.
+include $(topsrcdir)/config/rules.mk
+
+$(CPPSRCS): ../%Binding.cpp: $(bindinggen_dependencies) \
+ ../%.webidl \
+ $(NULL)
+ $(MAKE) -C .. $*Binding.h
+ $(MAKE) -C .. $*Binding.cpp
+
+check::
+ $(PYTHON) $(topsrcdir)/config/pythonpath.py \
+ $(PLY_INCLUDE) $(srcdir)/../parser/runtests.py
+
+check-interactive:
+ $(PYTHON) $(topsrcdir)/config/pythonpath.py \
+ $(PLY_INCLUDE) $(srcdir)/../parser/runtests.py -q
diff --git a/components/script/dom/bindings/codegen/test/TestBindingHeader.h b/components/script/dom/bindings/codegen/test/TestBindingHeader.h
new file mode 100644
index 00000000000..1fbab0a9fb8
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/TestBindingHeader.h
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef TestBindingHeader_h
+#define TestBindingHeader_h
+
+#include "nsWrapperCache.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsCOMPtr.h"
+// We don't export TestCodeGenBinding.h, but it's right in our parent dir.
+#include "../TestCodeGenBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+// IID for the TestNonCastableInterface
+#define NS_TEST_NONCASTABLE_INTERFACE_IID \
+{ 0x7c9f8ee2, 0xc9bf, 0x46ca, \
+ { 0xa0, 0xa9, 0x03, 0xa8, 0xd6, 0x30, 0x0e, 0xde } }
+
+class TestNonCastableInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_NONCASTABLE_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+};
+
+// IID for the IndirectlyImplementedInterface
+#define NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID \
+{ 0xfed55b69, 0x7012, 0x4849, \
+ { 0xaf, 0x56, 0x4b, 0xa9, 0xee, 0x41, 0x30, 0x89 } }
+
+class IndirectlyImplementedInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INDIRECTLY_IMPLEMENTED_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ bool IndirectlyImplementedProperty();
+ void IndirectlyImplementedProperty(bool);
+ void IndirectlyImplementedMethod();
+};
+
+// IID for the TestExternalInterface
+#define NS_TEST_EXTERNAL_INTERFACE_IID \
+{ 0xd5ba0c99, 0x9b1d, 0x4e71, \
+ { 0x8a, 0x94, 0x56, 0x38, 0x6c, 0xa3, 0xda, 0x3d } }
+class TestExternalInterface : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_EXTERNAL_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+};
+
+// IID for the TestCallbackInterface
+#define NS_TEST_CALLBACK_INTERFACE_IID \
+{ 0xbf711ba4, 0xc8f6, 0x46cf, \
+ { 0xba, 0x5b, 0xaa, 0xe2, 0x78, 0x18, 0xe6, 0x4a } }
+class TestCallbackInterface : public nsISupports
+{
+public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_TEST_CALLBACK_INTERFACE_IID)
+ NS_DECL_ISUPPORTS
+};
+
+class TestNonWrapperCacheInterface : public nsISupports
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ virtual JSObject* WrapObject(JSContext* cx, JSObject* scope);
+};
+
+class OnlyForUseInConstructor : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+};
+
+class TestInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ // And now our actual WebIDL API
+ // Constructors
+ static
+ already_AddRefed<TestInterface> Constructor(nsISupports*, ErrorResult&);
+ static
+ already_AddRefed<TestInterface> Constructor(nsISupports*, const nsAString&,
+ ErrorResult&);
+ static
+ already_AddRefed<TestInterface> Constructor(nsISupports*, uint32_t,
+ Nullable<bool>&, ErrorResult&);
+ static
+ already_AddRefed<TestInterface> Constructor(nsISupports*, TestInterface*,
+ ErrorResult&);
+ static
+ already_AddRefed<TestInterface> Constructor(nsISupports*,
+ TestNonCastableInterface&,
+ ErrorResult&);
+ /* static
+ already_AddRefed<TestInterface> Constructor(nsISupports*,
+ uint32_t, uint32_t,
+ const TestInterfaceOrOnlyForUseInConstructor&,
+ ErrorResult&);
+ */
+
+ // Integer types
+ int8_t ReadonlyByte();
+ int8_t WritableByte();
+ void SetWritableByte(int8_t);
+ void PassByte(int8_t);
+ int8_t ReceiveByte();
+ void PassOptionalByte(const Optional<int8_t>&);
+ void PassOptionalByteWithDefault(int8_t);
+ void PassNullableByte(Nullable<int8_t>&);
+ void PassOptionalNullableByte(const Optional< Nullable<int8_t> >&);
+
+ int16_t ReadonlyShort();
+ int16_t WritableShort();
+ void SetWritableShort(int16_t);
+ void PassShort(int16_t);
+ int16_t ReceiveShort();
+ void PassOptionalShort(const Optional<int16_t>&);
+ void PassOptionalShortWithDefault(int16_t);
+
+ int32_t ReadonlyLong();
+ int32_t WritableLong();
+ void SetWritableLong(int32_t);
+ void PassLong(int32_t);
+ int16_t ReceiveLong();
+ void PassOptionalLong(const Optional<int32_t>&);
+ void PassOptionalLongWithDefault(int32_t);
+
+ int64_t ReadonlyLongLong();
+ int64_t WritableLongLong();
+ void SetWritableLongLong(int64_t);
+ void PassLongLong(int64_t);
+ int64_t ReceiveLongLong();
+ void PassOptionalLongLong(const Optional<int64_t>&);
+ void PassOptionalLongLongWithDefault(int64_t);
+
+ uint8_t ReadonlyOctet();
+ uint8_t WritableOctet();
+ void SetWritableOctet(uint8_t);
+ void PassOctet(uint8_t);
+ uint8_t ReceiveOctet();
+ void PassOptionalOctet(const Optional<uint8_t>&);
+ void PassOptionalOctetWithDefault(uint8_t);
+
+ uint16_t ReadonlyUnsignedShort();
+ uint16_t WritableUnsignedShort();
+ void SetWritableUnsignedShort(uint16_t);
+ void PassUnsignedShort(uint16_t);
+ uint16_t ReceiveUnsignedShort();
+ void PassOptionalUnsignedShort(const Optional<uint16_t>&);
+ void PassOptionalUnsignedShortWithDefault(uint16_t);
+
+ uint32_t ReadonlyUnsignedLong();
+ uint32_t WritableUnsignedLong();
+ void SetWritableUnsignedLong(uint32_t);
+ void PassUnsignedLong(uint32_t);
+ uint32_t ReceiveUnsignedLong();
+ void PassOptionalUnsignedLong(const Optional<uint32_t>&);
+ void PassOptionalUnsignedLongWithDefault(uint32_t);
+
+ uint64_t ReadonlyUnsignedLongLong();
+ uint64_t WritableUnsignedLongLong();
+ void SetWritableUnsignedLongLong(uint64_t);
+ void PassUnsignedLongLong(uint64_t);
+ uint64_t ReceiveUnsignedLongLong();
+ void PassOptionalUnsignedLongLong(const Optional<uint64_t>&);
+ void PassOptionalUnsignedLongLongWithDefault(uint64_t);
+
+ // Interface types
+ already_AddRefed<TestInterface> ReceiveSelf();
+ already_AddRefed<TestInterface> ReceiveNullableSelf();
+ TestInterface* ReceiveWeakSelf();
+ TestInterface* ReceiveWeakNullableSelf();
+ void PassSelf(TestInterface&);
+ void PassSelf2(NonNull<TestInterface>&);
+ void PassNullableSelf(TestInterface*);
+ already_AddRefed<TestInterface> NonNullSelf();
+ void SetNonNullSelf(TestInterface&);
+ already_AddRefed<TestInterface> GetNullableSelf();
+ void SetNullableSelf(TestInterface*);
+ void PassOptionalSelf(const Optional<TestInterface*> &);
+ void PassOptionalNonNullSelf(const Optional<NonNull<TestInterface> >&);
+ void PassOptionalSelfWithDefault(TestInterface*);
+
+ already_AddRefed<TestNonWrapperCacheInterface> ReceiveNonWrapperCacheInterface();
+ already_AddRefed<TestNonWrapperCacheInterface> ReceiveNullableNonWrapperCacheInterface();
+ void ReceiveNonWrapperCacheInterfaceSequence(nsTArray<nsRefPtr<TestNonWrapperCacheInterface> >&);
+ void ReceiveNullableNonWrapperCacheInterfaceSequence(nsTArray<nsRefPtr<TestNonWrapperCacheInterface> >&);
+ void ReceiveNonWrapperCacheInterfaceNullableSequence(Nullable<nsTArray<nsRefPtr<TestNonWrapperCacheInterface> > >&);
+ void ReceiveNullableNonWrapperCacheInterfaceNullableSequence(Nullable<nsTArray<nsRefPtr<TestNonWrapperCacheInterface> > >&);
+
+ already_AddRefed<TestNonCastableInterface> ReceiveOther();
+ already_AddRefed<TestNonCastableInterface> ReceiveNullableOther();
+ TestNonCastableInterface* ReceiveWeakOther();
+ TestNonCastableInterface* ReceiveWeakNullableOther();
+ void PassOther(TestNonCastableInterface&);
+ void PassOther2(NonNull<TestNonCastableInterface>&);
+ void PassNullableOther(TestNonCastableInterface*);
+ already_AddRefed<TestNonCastableInterface> NonNullOther();
+ void SetNonNullOther(TestNonCastableInterface&);
+ already_AddRefed<TestNonCastableInterface> GetNullableOther();
+ void SetNullableOther(TestNonCastableInterface*);
+ void PassOptionalOther(const Optional<TestNonCastableInterface*>&);
+ void PassOptionalNonNullOther(const Optional<NonNull<TestNonCastableInterface> >&);
+ void PassOptionalOtherWithDefault(TestNonCastableInterface*);
+
+ already_AddRefed<TestExternalInterface> ReceiveExternal();
+ already_AddRefed<TestExternalInterface> ReceiveNullableExternal();
+ TestExternalInterface* ReceiveWeakExternal();
+ TestExternalInterface* ReceiveWeakNullableExternal();
+ void PassExternal(TestExternalInterface*);
+ void PassExternal2(TestExternalInterface*);
+ void PassNullableExternal(TestExternalInterface*);
+ already_AddRefed<TestExternalInterface> NonNullExternal();
+ void SetNonNullExternal(TestExternalInterface*);
+ already_AddRefed<TestExternalInterface> GetNullableExternal();
+ void SetNullableExternal(TestExternalInterface*);
+ void PassOptionalExternal(const Optional<TestExternalInterface*>&);
+ void PassOptionalNonNullExternal(const Optional<TestExternalInterface*>&);
+ void PassOptionalExternalWithDefault(TestExternalInterface*);
+
+ already_AddRefed<TestCallbackInterface> ReceiveCallbackInterface();
+ already_AddRefed<TestCallbackInterface> ReceiveNullableCallbackInterface();
+ TestCallbackInterface* ReceiveWeakCallbackInterface();
+ TestCallbackInterface* ReceiveWeakNullableCallbackInterface();
+ void PassCallbackInterface(TestCallbackInterface&);
+ void PassCallbackInterface2(OwningNonNull<TestCallbackInterface>);
+ void PassNullableCallbackInterface(TestCallbackInterface*);
+ already_AddRefed<TestCallbackInterface> NonNullCallbackInterface();
+ void SetNonNullCallbackInterface(TestCallbackInterface&);
+ already_AddRefed<TestCallbackInterface> GetNullableCallbackInterface();
+ void SetNullableCallbackInterface(TestCallbackInterface*);
+ void PassOptionalCallbackInterface(const Optional<nsRefPtr<TestCallbackInterface> >&);
+ void PassOptionalNonNullCallbackInterface(const Optional<OwningNonNull<TestCallbackInterface> >&);
+ void PassOptionalCallbackInterfaceWithDefault(TestCallbackInterface*);
+
+ already_AddRefed<IndirectlyImplementedInterface> ReceiveConsequentialInterface();
+ void PassConsequentialInterface(IndirectlyImplementedInterface&);
+
+ // Sequence types
+ void ReceiveSequence(nsTArray<int32_t>&);
+ void ReceiveNullableSequence(Nullable< nsTArray<int32_t> >&);
+ void ReceiveSequenceOfNullableInts(nsTArray< Nullable<int32_t> >&);
+ void ReceiveNullableSequenceOfNullableInts(Nullable< nsTArray< Nullable<int32_t> > >&);
+ void PassSequence(const Sequence<int32_t> &);
+ void PassNullableSequence(const Nullable< Sequence<int32_t> >&);
+ void PassSequenceOfNullableInts(const Sequence<Nullable<int32_t> >&);
+ void PassOptionalSequenceOfNullableInts(const Optional<Sequence<Nullable<int32_t> > > &);
+ void PassOptionalNullableSequenceOfNullableInts(const Optional<Nullable<Sequence<Nullable<int32_t> > > > &);
+ void ReceiveCastableObjectSequence(nsTArray< nsRefPtr<TestInterface> > &);
+ void ReceiveNullableCastableObjectSequence(nsTArray< nsRefPtr<TestInterface> > &);
+ void ReceiveCastableObjectNullableSequence(Nullable< nsTArray< nsRefPtr<TestInterface> > >&);
+ void ReceiveNullableCastableObjectNullableSequence(Nullable< nsTArray< nsRefPtr<TestInterface> > >&);
+ void ReceiveWeakCastableObjectSequence(nsTArray<TestInterface*> &);
+ void ReceiveWeakNullableCastableObjectSequence(nsTArray<TestInterface*> &);
+ void ReceiveWeakCastableObjectNullableSequence(Nullable< nsTArray<TestInterface*> >&);
+ void ReceiveWeakNullableCastableObjectNullableSequence(Nullable< nsTArray<TestInterface*> >&);
+ void PassCastableObjectSequence(const Sequence< OwningNonNull<TestInterface> >&);
+ void PassNullableCastableObjectSequence(const Sequence< nsRefPtr<TestInterface> > &);
+ void PassCastableObjectNullableSequence(const Nullable< Sequence< OwningNonNull<TestInterface> > >&);
+ void PassNullableCastableObjectNullableSequence(const Nullable< Sequence< nsRefPtr<TestInterface> > >&);
+ void PassOptionalSequence(const Optional<Sequence<int32_t> >&);
+ void PassOptionalNullableSequence(const Optional<Nullable<Sequence<int32_t> > >&);
+ void PassOptionalNullableSequenceWithDefaultValue(const Nullable< Sequence<int32_t> >&);
+ void PassOptionalObjectSequence(const Optional<Sequence<OwningNonNull<TestInterface> > >&);
+
+ void ReceiveStringSequence(nsTArray<nsString>&);
+ void PassStringSequence(const Sequence<nsString>&);
+
+ void ReceiveAnySequence(JSContext*, nsTArray<JS::Value>&);
+ void ReceiveNullableAnySequence(JSContext*, Nullable<nsTArray<JS::Value> >);
+
+ // Typed array types
+ void PassArrayBuffer(ArrayBuffer&);
+ void PassNullableArrayBuffer(ArrayBuffer*);
+ void PassOptionalArrayBuffer(const Optional<ArrayBuffer>&);
+ void PassOptionalNullableArrayBuffer(const Optional<ArrayBuffer*>&);
+ void PassOptionalNullableArrayBufferWithDefaultValue(ArrayBuffer*);
+ void PassArrayBufferView(ArrayBufferView&);
+ void PassInt8Array(Int8Array&);
+ void PassInt16Array(Int16Array&);
+ void PassInt32Array(Int32Array&);
+ void PassUint8Array(Uint8Array&);
+ void PassUint16Array(Uint16Array&);
+ void PassUint32Array(Uint32Array&);
+ void PassUint8ClampedArray(Uint8ClampedArray&);
+ void PassFloat32Array(Float32Array&);
+ void PassFloat64Array(Float64Array&);
+ JSObject* ReceiveUint8Array(JSContext*);
+
+ // String types
+ void PassString(const nsAString&);
+ void PassNullableString(const nsAString&);
+ void PassOptionalString(const Optional<nsAString>&);
+ void PassOptionalStringWithDefaultValue(const nsAString&);
+ void PassOptionalNullableString(const Optional<nsAString>&);
+ void PassOptionalNullableStringWithDefaultValue(const nsAString&);
+
+ // Enumarated types
+ void PassEnum(TestEnum);
+ void PassOptionalEnum(const Optional<TestEnum>&);
+ void PassEnumWithDefault(TestEnum);
+ TestEnum ReceiveEnum();
+ TestEnum EnumAttribute();
+ TestEnum ReadonlyEnumAttribute();
+ void SetEnumAttribute(TestEnum);
+
+ // Callback types
+ void PassCallback(JSContext*, JSObject*);
+ void PassNullableCallback(JSContext*, JSObject*);
+ void PassOptionalCallback(JSContext*, const Optional<JSObject*>&);
+ void PassOptionalNullableCallback(JSContext*, const Optional<JSObject*>&);
+ void PassOptionalNullableCallbackWithDefaultValue(JSContext*, JSObject*);
+ JSObject* ReceiveCallback(JSContext*);
+ JSObject* ReceiveNullableCallback(JSContext*);
+
+ // Any types
+ void PassAny(JSContext*, JS::Value);
+ void PassOptionalAny(JSContext*, const Optional<JS::Value>&);
+ void PassAnyDefaultNull(JSContext*, JS::Value);
+ JS::Value ReceiveAny(JSContext*);
+
+ // object types
+ void PassObject(JSContext*, JSObject&);
+ void PassNullableObject(JSContext*, JSObject*);
+ void PassOptionalObject(JSContext*, const Optional<NonNull<JSObject> >&);
+ void PassOptionalNullableObject(JSContext*, const Optional<JSObject*>&);
+ void PassOptionalNullableObjectWithDefaultValue(JSContext*, JSObject*);
+ JSObject* ReceiveObject(JSContext*);
+ JSObject* ReceiveNullableObject(JSContext*);
+
+ // Union types
+ void PassUnion(JSContext*, const ObjectOrLong& arg);
+ void PassUnionWithNullable(JSContext*, const ObjectOrNullOrLong& arg)
+ {
+ ObjectOrLong returnValue;
+ if (arg.IsNull()) {
+ } else if (arg.IsObject()) {
+ JSObject& obj = (JSObject&)arg.GetAsObject();
+ JS_GetClass(&obj);
+ //returnValue.SetAsObject(&obj);
+ } else {
+ int32_t i = arg.GetAsLong();
+ i += 1;
+ }
+ }
+ void PassNullableUnion(JSContext*, const Nullable<ObjectOrLong>&);
+ void PassOptionalUnion(JSContext*, const Optional<ObjectOrLong>&);
+ void PassOptionalNullableUnion(JSContext*, const Optional<Nullable<ObjectOrLong> >&);
+ void PassOptionalNullableUnionWithDefaultValue(JSContext*, const Nullable<ObjectOrLong>&);
+ //void PassUnionWithInterfaces(const TestInterfaceOrTestExternalInterface& arg);
+ //void PassUnionWithInterfacesAndNullable(const TestInterfaceOrNullOrTestExternalInterface& arg);
+ void PassUnionWithArrayBuffer(const ArrayBufferOrLong&);
+ void PassUnionWithString(JSContext*, const StringOrObject&);
+ //void PassUnionWithEnum(JSContext*, const TestEnumOrObject&);
+ void PassUnionWithCallback(JSContext*, const TestCallbackOrLong&);
+ void PassUnionWithObject(JSContext*, const ObjectOrLong&);
+
+ // binaryNames tests
+ void MethodRenamedTo();
+ void MethodRenamedTo(int8_t);
+ int8_t AttributeGetterRenamedTo();
+ int8_t AttributeRenamedTo();
+ void SetAttributeRenamedTo(int8_t);
+
+ // Dictionary tests
+ void PassDictionary(const Dict&);
+ void PassOtherDictionary(const GrandparentDict&);
+ void PassSequenceOfDictionaries(const Sequence<Dict>&);
+ void PassDictionaryOrLong(const Dict&);
+ void PassDictionaryOrLong(int32_t);
+ void PassDictContainingDict(const DictContainingDict&);
+ void PassDictContainingSequence(const DictContainingSequence&);
+
+ // Typedefs
+ void ExerciseTypedefInterfaces1(TestInterface&);
+ already_AddRefed<TestInterface> ExerciseTypedefInterfaces2(TestInterface*);
+ void ExerciseTypedefInterfaces3(TestInterface&);
+
+ // Miscellania
+ int32_t AttrWithLenientThis();
+ void SetAttrWithLenientThis(int32_t);
+
+ // Methods and properties imported via "implements"
+ bool ImplementedProperty();
+ void SetImplementedProperty(bool);
+ void ImplementedMethod();
+ bool ImplementedParentProperty();
+ void SetImplementedParentProperty(bool);
+ void ImplementedParentMethod();
+ bool IndirectlyImplementedProperty();
+ void SetIndirectlyImplementedProperty(bool);
+ void IndirectlyImplementedMethod();
+ uint32_t DiamondImplementedProperty();
+
+ // Test EnforceRange/Clamp
+ void DontEnforceRangeOrClamp(int8_t);
+ void DoEnforceRange(int8_t);
+ void DoClamp(int8_t);
+
+private:
+ // We add signatures here that _could_ start matching if the codegen
+ // got data types wrong. That way if it ever does we'll have a call
+ // to these private deleted methods and compilation will fail.
+ void SetReadonlyByte(int8_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableByte(T) MOZ_DELETE;
+ template<typename T>
+ void PassByte(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalByte(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalByteWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyShort(int16_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableShort(T) MOZ_DELETE;
+ template<typename T>
+ void PassShort(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalShort(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalShortWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyLong(int32_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalLong(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalLongWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyLongLong(int64_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableLongLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassLongLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalLongLong(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalLongLongWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyOctet(uint8_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableOctet(T) MOZ_DELETE;
+ template<typename T>
+ void PassOctet(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalOctet(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalOctetWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyUnsignedShort(uint16_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableUnsignedShort(T) MOZ_DELETE;
+ template<typename T>
+ void PassUnsignedShort(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedShort(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedShortWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyUnsignedLong(uint32_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableUnsignedLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassUnsignedLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedLong(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedLongWithDefault(T) MOZ_DELETE;
+
+ void SetReadonlyUnsignedLongLong(uint64_t) MOZ_DELETE;
+ template<typename T>
+ void SetWritableUnsignedLongLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassUnsignedLongLong(T) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedLongLong(const Optional<T>&) MOZ_DELETE;
+ template<typename T>
+ void PassOptionalUnsignedLongLongWithDefault(T) MOZ_DELETE;
+
+ // Enforce that only const things are passed for sequences
+ void PassSequence(Sequence<int32_t> &) MOZ_DELETE;
+ void PassNullableSequence(Nullable< Sequence<int32_t> >&) MOZ_DELETE;
+ void PassOptionalNullableSequenceWithDefaultValue(Nullable< Sequence<int32_t> >&) MOZ_DELETE;
+
+ // Enforce that only const things are passed for optional
+ void PassOptionalByte(Optional<int8_t>&) MOZ_DELETE;
+ void PassOptionalNullableByte(Optional<Nullable<int8_t> >&) MOZ_DELETE;
+ void PassOptionalShort(Optional<int16_t>&) MOZ_DELETE;
+ void PassOptionalLong(Optional<int32_t>&) MOZ_DELETE;
+ void PassOptionalLongLong(Optional<int64_t>&) MOZ_DELETE;
+ void PassOptionalOctet(Optional<uint8_t>&) MOZ_DELETE;
+ void PassOptionalUnsignedShort(Optional<uint16_t>&) MOZ_DELETE;
+ void PassOptionalUnsignedLong(Optional<uint32_t>&) MOZ_DELETE;
+ void PassOptionalUnsignedLongLong(Optional<uint64_t>&) MOZ_DELETE;
+ void PassOptionalSelf(Optional<TestInterface*> &) MOZ_DELETE;
+ void PassOptionalNonNullSelf(Optional<NonNull<TestInterface> >&) MOZ_DELETE;
+ void PassOptionalOther(Optional<TestNonCastableInterface*>&);
+ void PassOptionalNonNullOther(Optional<NonNull<TestNonCastableInterface> >&);
+ void PassOptionalExternal(Optional<TestExternalInterface*>&) MOZ_DELETE;
+ void PassOptionalNonNullExternal(Optional<TestExternalInterface*>&) MOZ_DELETE;
+ void PassOptionalSequence(Optional<Sequence<int32_t> >&) MOZ_DELETE;
+ void PassOptionalNullableSequence(Optional<Nullable<Sequence<int32_t> > >&) MOZ_DELETE;
+ void PassOptionalObjectSequence(Optional<Sequence<OwningNonNull<TestInterface> > >&) MOZ_DELETE;
+ void PassOptionalArrayBuffer(Optional<ArrayBuffer>&) MOZ_DELETE;
+ void PassOptionalNullableArrayBuffer(Optional<ArrayBuffer*>&) MOZ_DELETE;
+ void PassOptionalEnum(Optional<TestEnum>&) MOZ_DELETE;
+ void PassOptionalCallback(JSContext*, Optional<JSObject*>&) MOZ_DELETE;
+ void PassOptionalNullableCallback(JSContext*, Optional<JSObject*>&) MOZ_DELETE;
+ void PassOptionalAny(Optional<JS::Value>&) MOZ_DELETE;
+
+ // And test that string stuff is always const
+ void PassString(nsAString&) MOZ_DELETE;
+ void PassNullableString(nsAString&) MOZ_DELETE;
+ void PassOptionalString(Optional<nsAString>&) MOZ_DELETE;
+ void PassOptionalStringWithDefaultValue(nsAString&) MOZ_DELETE;
+ void PassOptionalNullableString(Optional<nsAString>&) MOZ_DELETE;
+ void PassOptionalNullableStringWithDefaultValue(nsAString&) MOZ_DELETE;
+
+};
+
+class TestIndexedGetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ uint32_t IndexedGetter(uint32_t, bool&);
+ uint32_t IndexedGetter(uint32_t&) MOZ_DELETE;
+ uint32_t Item(uint32_t&);
+ uint32_t Item(uint32_t, bool&) MOZ_DELETE;
+ uint32_t Length();
+};
+
+class TestNamedGetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+};
+
+class TestIndexedAndNamedGetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ uint32_t IndexedGetter(uint32_t, bool&);
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void NamedItem(const nsAString&, nsAString&);
+ uint32_t Length();
+};
+
+class TestIndexedSetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void IndexedSetter(uint32_t, const nsAString&);
+ void SetItem(uint32_t, const nsAString&);
+};
+
+class TestNamedSetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
+};
+
+class TestIndexedAndNamedSetterInterface : public nsISupports,
+ public nsWrapperCache
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ // We need a GetParentObject to make binding codegen happy
+ virtual nsISupports* GetParentObject();
+
+ void IndexedSetter(uint32_t, TestIndexedSetterInterface&);
+ void NamedSetter(const nsAString&, TestIndexedSetterInterface&);
+ void SetNamedItem(const nsAString&, TestIndexedSetterInterface&);
+};
+
+class TestIndexedAndNamedGetterAndSetterInterface : public TestIndexedSetterInterface
+{
+public:
+ uint32_t IndexedGetter(uint32_t, bool&);
+ uint32_t Item(uint32_t);
+ void NamedGetter(const nsAString&, bool&, nsAString&);
+ void NamedItem(const nsAString&, nsAString&);
+ void IndexedSetter(uint32_t, int32_t&);
+ void IndexedSetter(uint32_t, const nsAString&) MOZ_DELETE;
+ void NamedSetter(const nsAString&, const nsAString&);
+ void Stringify(nsAString&);
+ uint32_t Length();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* TestBindingHeader_h */
diff --git a/components/script/dom/bindings/codegen/test/TestCodeGen.webidl b/components/script/dom/bindings/codegen/test/TestCodeGen.webidl
new file mode 100644
index 00000000000..8c2b3c1b6b4
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/TestCodeGen.webidl
@@ -0,0 +1,442 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+typedef long myLong;
+typedef TestInterface AnotherNameForTestInterface;
+typedef TestInterface? NullableTestInterface;
+
+interface TestExternalInterface;
+
+interface TestNonCastableInterface {
+};
+
+callback interface TestCallbackInterface {
+ readonly attribute long foo;
+ void doSomething();
+};
+
+enum TestEnum {
+ "a",
+ "b"
+};
+
+callback TestCallback = void();
+
+TestInterface implements ImplementedInterface;
+
+// This interface is only for use in the constructor below
+interface OnlyForUseInConstructor {
+};
+
+[Constructor,
+ Constructor(DOMString str),
+ Constructor(unsigned long num, boolean? bool),
+ Constructor(TestInterface? iface),
+ Constructor(TestNonCastableInterface iface)
+ // , Constructor(long arg1, long arg2, (TestInterface or OnlyForUseInConstructor) arg3)
+ ]
+interface TestInterface {
+ // Integer types
+ // XXXbz add tests for throwing versions of all the integer stuff
+ readonly attribute byte readonlyByte;
+ attribute byte writableByte;
+ void passByte(byte arg);
+ byte receiveByte();
+ void passOptionalByte(optional byte arg);
+ void passOptionalByteWithDefault(optional byte arg = 0);
+ void passNullableByte(byte? arg);
+ void passOptionalNullableByte(optional byte? arg);
+
+ readonly attribute short readonlyShort;
+ attribute short writableShort;
+ void passShort(short arg);
+ short receiveShort();
+ void passOptionalShort(optional short arg);
+ void passOptionalShortWithDefault(optional short arg = 5);
+
+ readonly attribute long readonlyLong;
+ attribute long writableLong;
+ void passLong(long arg);
+ long receiveLong();
+ void passOptionalLong(optional long arg);
+ void passOptionalLongWithDefault(optional long arg = 7);
+
+ readonly attribute long long readonlyLongLong;
+ attribute long long writableLongLong;
+ void passLongLong(long long arg);
+ long long receiveLongLong();
+ void passOptionalLongLong(optional long long arg);
+ void passOptionalLongLongWithDefault(optional long long arg = -12);
+
+ readonly attribute octet readonlyOctet;
+ attribute octet writableOctet;
+ void passOctet(octet arg);
+ octet receiveOctet();
+ void passOptionalOctet(optional octet arg);
+ void passOptionalOctetWithDefault(optional octet arg = 19);
+
+ readonly attribute unsigned short readonlyUnsignedShort;
+ attribute unsigned short writableUnsignedShort;
+ void passUnsignedShort(unsigned short arg);
+ unsigned short receiveUnsignedShort();
+ void passOptionalUnsignedShort(optional unsigned short arg);
+ void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
+
+ readonly attribute unsigned long readonlyUnsignedLong;
+ attribute unsigned long writableUnsignedLong;
+ void passUnsignedLong(unsigned long arg);
+ unsigned long receiveUnsignedLong();
+ void passOptionalUnsignedLong(optional unsigned long arg);
+ void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
+
+ readonly attribute unsigned long long readonlyUnsignedLongLong;
+ attribute unsigned long long writableUnsignedLongLong;
+ void passUnsignedLongLong(unsigned long long arg);
+ unsigned long long receiveUnsignedLongLong();
+ void passOptionalUnsignedLongLong(optional unsigned long long arg);
+ void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
+
+ // Castable interface types
+ // XXXbz add tests for throwing versions of all the castable interface stuff
+ TestInterface receiveSelf();
+ TestInterface? receiveNullableSelf();
+ TestInterface receiveWeakSelf();
+ TestInterface? receiveWeakNullableSelf();
+ // A verstion to test for casting to TestInterface&
+ void passSelf(TestInterface arg);
+ // A version we can use to test for the exact type passed in
+ void passSelf2(TestInterface arg);
+ void passNullableSelf(TestInterface? arg);
+ attribute TestInterface nonNullSelf;
+ attribute TestInterface? nullableSelf;
+ // Optional arguments
+ void passOptionalSelf(optional TestInterface? arg);
+ void passOptionalNonNullSelf(optional TestInterface arg);
+ void passOptionalSelfWithDefault(optional TestInterface? arg = null);
+
+ // Non-wrapper-cache interface types
+ [Creator]
+ TestNonWrapperCacheInterface receiveNonWrapperCacheInterface();
+ [Creator]
+ TestNonWrapperCacheInterface? receiveNullableNonWrapperCacheInterface();
+ [Creator]
+ sequence<TestNonWrapperCacheInterface> receiveNonWrapperCacheInterfaceSequence();
+ [Creator]
+ sequence<TestNonWrapperCacheInterface?> receiveNullableNonWrapperCacheInterfaceSequence();
+ [Creator]
+ sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
+ [Creator]
+ sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
+
+ // Non-castable interface types
+ TestNonCastableInterface receiveOther();
+ TestNonCastableInterface? receiveNullableOther();
+ TestNonCastableInterface receiveWeakOther();
+ TestNonCastableInterface? receiveWeakNullableOther();
+ // A verstion to test for casting to TestNonCastableInterface&
+ void passOther(TestNonCastableInterface arg);
+ // A version we can use to test for the exact type passed in
+ void passOther2(TestNonCastableInterface arg);
+ void passNullableOther(TestNonCastableInterface? arg);
+ attribute TestNonCastableInterface nonNullOther;
+ attribute TestNonCastableInterface? nullableOther;
+ // Optional arguments
+ void passOptionalOther(optional TestNonCastableInterface? arg);
+ void passOptionalNonNullOther(optional TestNonCastableInterface arg);
+ void passOptionalOtherWithDefault(optional TestNonCastableInterface? arg = null);
+
+ // External interface types
+ TestExternalInterface receiveExternal();
+ TestExternalInterface? receiveNullableExternal();
+ TestExternalInterface receiveWeakExternal();
+ TestExternalInterface? receiveWeakNullableExternal();
+ // A verstion to test for casting to TestExternalInterface&
+ void passExternal(TestExternalInterface arg);
+ // A version we can use to test for the exact type passed in
+ void passExternal2(TestExternalInterface arg);
+ void passNullableExternal(TestExternalInterface? arg);
+ attribute TestExternalInterface nonNullExternal;
+ attribute TestExternalInterface? nullableExternal;
+ // Optional arguments
+ void passOptionalExternal(optional TestExternalInterface? arg);
+ void passOptionalNonNullExternal(optional TestExternalInterface arg);
+ void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null);
+
+ // Callback interface types
+ TestCallbackInterface receiveCallbackInterface();
+ TestCallbackInterface? receiveNullableCallbackInterface();
+ TestCallbackInterface receiveWeakCallbackInterface();
+ TestCallbackInterface? receiveWeakNullableCallbackInterface();
+ // A verstion to test for casting to TestCallbackInterface&
+ void passCallbackInterface(TestCallbackInterface arg);
+ // A version we can use to test for the exact type passed in
+ void passCallbackInterface2(TestCallbackInterface arg);
+ void passNullableCallbackInterface(TestCallbackInterface? arg);
+ attribute TestCallbackInterface nonNullCallbackInterface;
+ attribute TestCallbackInterface? nullableCallbackInterface;
+ // Optional arguments
+ void passOptionalCallbackInterface(optional TestCallbackInterface? arg);
+ void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg);
+ void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null);
+
+ // Miscellaneous interface tests
+ IndirectlyImplementedInterface receiveConsequentialInterface();
+ void passConsequentialInterface(IndirectlyImplementedInterface arg);
+
+ // Sequence types
+ sequence<long> receiveSequence();
+ sequence<long>? receiveNullableSequence();
+ sequence<long?> receiveSequenceOfNullableInts();
+ sequence<long?>? receiveNullableSequenceOfNullableInts();
+ void passSequence(sequence<long> arg);
+ void passNullableSequence(sequence<long>? arg);
+ void passSequenceOfNullableInts(sequence<long?> arg);
+ void passOptionalSequenceOfNullableInts(optional sequence<long?> arg);
+ void passOptionalNullableSequenceOfNullableInts(optional sequence<long?>? arg);
+ sequence<TestInterface> receiveCastableObjectSequence();
+ sequence<TestInterface?> receiveNullableCastableObjectSequence();
+ sequence<TestInterface>? receiveCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveNullableCastableObjectNullableSequence();
+ sequence<TestInterface> receiveWeakCastableObjectSequence();
+ sequence<TestInterface?> receiveWeakNullableCastableObjectSequence();
+ sequence<TestInterface>? receiveWeakCastableObjectNullableSequence();
+ sequence<TestInterface?>? receiveWeakNullableCastableObjectNullableSequence();
+ void passCastableObjectSequence(sequence<TestInterface> arg);
+ void passNullableCastableObjectSequence(sequence<TestInterface?> arg);
+ void passCastableObjectNullableSequence(sequence<TestInterface>? arg);
+ void passNullableCastableObjectNullableSequence(sequence<TestInterface?>? arg);
+ void passOptionalSequence(optional sequence<long> arg);
+ void passOptionalNullableSequence(optional sequence<long>? arg);
+ void passOptionalNullableSequenceWithDefaultValue(optional sequence<long>? arg = null);
+ void passOptionalObjectSequence(optional sequence<TestInterface> arg);
+
+ sequence<DOMString> receiveStringSequence();
+ void passStringSequence(sequence<DOMString> arg);
+
+ sequence<any> receiveAnySequence();
+ sequence<any>? receiveNullableAnySequence();
+
+ // Typed array types
+ void passArrayBuffer(ArrayBuffer arg);
+ void passNullableArrayBuffer(ArrayBuffer? arg);
+ void passOptionalArrayBuffer(optional ArrayBuffer arg);
+ void passOptionalNullableArrayBuffer(optional ArrayBuffer? arg);
+ void passOptionalNullableArrayBufferWithDefaultValue(optional ArrayBuffer? arg= null);
+ void passArrayBufferView(ArrayBufferView arg);
+ void passInt8Array(Int8Array arg);
+ void passInt16Array(Int16Array arg);
+ void passInt32Array(Int32Array arg);
+ void passUint8Array(Uint8Array arg);
+ void passUint16Array(Uint16Array arg);
+ void passUint32Array(Uint32Array arg);
+ void passUint8ClampedArray(Uint8ClampedArray arg);
+ void passFloat32Array(Float32Array arg);
+ void passFloat64Array(Float64Array arg);
+ Uint8Array receiveUint8Array();
+
+ // String types
+ void passString(DOMString arg);
+ void passNullableString(DOMString? arg);
+ void passOptionalString(optional DOMString arg);
+ void passOptionalStringWithDefaultValue(optional DOMString arg = "abc");
+ void passOptionalNullableString(optional DOMString? arg);
+ void passOptionalNullableStringWithDefaultValue(optional DOMString? arg = null);
+
+ // Enumerated types
+ void passEnum(TestEnum arg);
+ // No support for nullable enums yet
+ // void passNullableEnum(TestEnum? arg);
+ void passOptionalEnum(optional TestEnum arg);
+ void passEnumWithDefault(optional TestEnum arg = "a");
+ // void passOptionalNullableEnum(optional TestEnum? arg);
+ // void passOptionalNullableEnumWithDefaultValue(optional TestEnum? arg = null);
+ TestEnum receiveEnum();
+ attribute TestEnum enumAttribute;
+ readonly attribute TestEnum readonlyEnumAttribute;
+
+ // Callback types
+ void passCallback(TestCallback arg);
+ void passNullableCallback(TestCallback? arg);
+ void passOptionalCallback(optional TestCallback arg);
+ void passOptionalNullableCallback(optional TestCallback? arg);
+ void passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null);
+ TestCallback receiveCallback();
+ TestCallback? receiveNullableCallback();
+
+ // Any types
+ void passAny(any arg);
+ void passOptionalAny(optional any arg);
+ void passAnyDefaultNull(optional any arg = null);
+ any receiveAny();
+
+ // object types
+ void passObject(object arg);
+ void passNullableObject(object? arg);
+ void passOptionalObject(optional object arg);
+ void passOptionalNullableObject(optional object? arg);
+ void passOptionalNullableObjectWithDefaultValue(optional object? arg = null);
+ object receiveObject();
+ object? receiveNullableObject();
+
+ // Union types
+ void passUnion((object or long) arg);
+ void passUnionWithNullable((object? or long) arg);
+ void passNullableUnion((object or long)? arg);
+ void passOptionalUnion(optional (object or long) arg);
+ void passOptionalNullableUnion(optional (object or long)? arg);
+ void passOptionalNullableUnionWithDefaultValue(optional (object or long)? arg = null);
+ //void passUnionWithInterfaces((TestInterface or TestExternalInterface) arg);
+ //void passUnionWithInterfacesAndNullable((TestInterface? or TestExternalInterface) arg);
+ //void passUnionWithSequence((sequence<object> or long) arg);
+ void passUnionWithArrayBuffer((ArrayBuffer or long) arg);
+ void passUnionWithString((DOMString or object) arg);
+ //void passUnionWithEnum((TestEnum or object) arg);
+ void passUnionWithCallback((TestCallback or long) arg);
+ void passUnionWithObject((object or long) arg);
+ //void passUnionWithDict((Dict or long) arg);
+
+ // binaryNames tests
+ void methodRenamedFrom();
+ void methodRenamedFrom(byte argument);
+ readonly attribute byte attributeGetterRenamedFrom;
+ attribute byte attributeRenamedFrom;
+
+ void passDictionary(optional Dict x);
+ void passOtherDictionary(optional GrandparentDict x);
+ void passSequenceOfDictionaries(sequence<Dict> x);
+ void passDictionaryOrLong(optional Dict x);
+ void passDictionaryOrLong(long x);
+
+ void passDictContainingDict(optional DictContainingDict arg);
+ void passDictContainingSequence(optional DictContainingSequence arg);
+
+ // EnforceRange/Clamp tests
+ void dontEnforceRangeOrClamp(byte arg);
+ void doEnforceRange([EnforceRange] byte arg);
+ void doClamp([Clamp] byte arg);
+
+ // Typedefs
+ const myLong myLongConstant = 5;
+ void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg);
+ AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg);
+ void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg);
+
+ // Miscellania
+ [LenientThis] attribute long attrWithLenientThis;
+};
+
+interface TestNonWrapperCacheInterface {
+};
+
+interface ImplementedInterfaceParent {
+ void implementedParentMethod();
+ attribute boolean implementedParentProperty;
+
+ const long implementedParentConstant = 8;
+};
+
+ImplementedInterfaceParent implements IndirectlyImplementedInterface;
+
+[NoInterfaceObject]
+interface IndirectlyImplementedInterface {
+ void indirectlyImplementedMethod();
+ attribute boolean indirectlyImplementedProperty;
+
+ const long indirectlyImplementedConstant = 9;
+};
+
+interface ImplementedInterface : ImplementedInterfaceParent {
+ void implementedMethod();
+ attribute boolean implementedProperty;
+
+ const long implementedConstant = 5;
+};
+
+interface DiamondImplements {
+ readonly attribute long diamondImplementedProperty;
+};
+interface DiamondBranch1A {
+};
+interface DiamondBranch1B {
+};
+interface DiamondBranch2A : DiamondImplements {
+};
+interface DiamondBranch2B : DiamondImplements {
+};
+TestInterface implements DiamondBranch1A;
+TestInterface implements DiamondBranch1B;
+TestInterface implements DiamondBranch2A;
+TestInterface implements DiamondBranch2B;
+DiamondBranch1A implements DiamondImplements;
+DiamondBranch1B implements DiamondImplements;
+
+dictionary Dict : ParentDict {
+ TestEnum someEnum;
+ long x;
+ long a;
+ long b = 8;
+ long z = 9;
+ DOMString str;
+ DOMString empty = "";
+ TestEnum otherEnum = "b";
+ DOMString otherStr = "def";
+ DOMString? yetAnotherStr = null;
+};
+
+dictionary ParentDict : GrandparentDict {
+ long c = 5;
+ TestInterface someInterface;
+ TestExternalInterface someExternalInterface;
+};
+
+dictionary DictContainingDict {
+ Dict memberDict;
+};
+
+dictionary DictContainingSequence {
+ sequence<long> ourSequence;
+};
+
+interface TestIndexedGetterInterface {
+ getter long item(unsigned long index);
+ [Infallible]
+ readonly attribute unsigned long length;
+};
+
+interface TestNamedGetterInterface {
+ getter DOMString (DOMString name);
+};
+
+interface TestIndexedAndNamedGetterInterface {
+ getter long (unsigned long index);
+ getter DOMString namedItem(DOMString name);
+ [Infallible]
+ readonly attribute unsigned long length;
+};
+
+interface TestIndexedSetterInterface {
+ setter creator void setItem(unsigned long index, DOMString item);
+};
+
+interface TestNamedSetterInterface {
+ setter creator void (DOMString name, TestIndexedSetterInterface item);
+};
+
+interface TestIndexedAndNamedSetterInterface {
+ setter creator void (unsigned long index, TestIndexedSetterInterface item);
+ setter creator void setNamedItem(DOMString name, TestIndexedSetterInterface item);
+};
+
+interface TestIndexedAndNamedGetterAndSetterInterface : TestIndexedSetterInterface {
+ getter long item(unsigned long index);
+ getter DOMString namedItem(DOMString name);
+ setter creator void (unsigned long index, long item);
+ setter creator void (DOMString name, DOMString item);
+ [Infallible]
+ stringifier DOMString ();
+ [Infallible]
+ readonly attribute unsigned long length;
+};
diff --git a/components/script/dom/bindings/codegen/test/TestDictionary.webidl b/components/script/dom/bindings/codegen/test/TestDictionary.webidl
new file mode 100644
index 00000000000..3dd91bd6500
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/TestDictionary.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+dictionary GrandparentDict {
+ double someNum;
+}; \ No newline at end of file
diff --git a/components/script/dom/bindings/codegen/test/TestTypedef.webidl b/components/script/dom/bindings/codegen/test/TestTypedef.webidl
new file mode 100644
index 00000000000..7f758c79e8f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/TestTypedef.webidl
@@ -0,0 +1,7 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+typedef TestInterface YetAnotherNameForTestInterface;
diff --git a/components/script/dom/bindings/codegen/test/file_bug775543.html b/components/script/dom/bindings/codegen/test/file_bug775543.html
new file mode 100644
index 00000000000..ee8c14c4d9c
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/file_bug775543.html
@@ -0,0 +1,5 @@
+<body>
+<script>
+worker = new Worker("a");
+</script>
+</body>
diff --git a/components/script/dom/bindings/codegen/test/forOf_iframe.html b/components/script/dom/bindings/codegen/test/forOf_iframe.html
new file mode 100644
index 00000000000..91417aba0e8
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/forOf_iframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>iframe content for test_forOf_iframe.html</title>
+</head>
+<body>
+ <div id="basket">
+ <span id="egg0"></span>
+ <span id="egg1"><span id="duckling1"></span></span>
+ <span id="egg2"></span>
+ </div>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_InstanceOf.html b/components/script/dom/bindings/codegen/test/test_InstanceOf.html
new file mode 100644
index 00000000000..3a5a76b1b21
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_InstanceOf.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=748983
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 748983</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=748983">Mozilla Bug 748983</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 748983 **/
+ok(document instanceof EventTarget, "document is an event target")
+ok(new XMLHttpRequest() instanceof XMLHttpRequest, "instanceof should work on XHR");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_bug773326.html b/components/script/dom/bindings/codegen/test/test_bug773326.html
new file mode 100644
index 00000000000..2e3b1ea304d
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_bug773326.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test for Bug 773326</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ new Worker("data:text/javascript,new XMLHttpRequest(42)");
+}, "Should not crash")
+</script>
diff --git a/components/script/dom/bindings/codegen/test/test_bug775543.html b/components/script/dom/bindings/codegen/test/test_bug775543.html
new file mode 100644
index 00000000000..d8df05f630f
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_bug775543.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775543
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 775543</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775543">Mozilla Bug 775543</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/tests/dom/bindings/test/file_bug775543.html" onload="test();"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 775543 **/
+
+function test()
+{
+ var a = XPCNativeWrapper(document.getElementById("t").contentWindow.wrappedJSObject.worker);
+ isnot(XPCNativeWrapper.unwrap(a), a, "XPCNativeWrapper(Worker) should be an Xray wrapper");
+ a.toString();
+ ok(true, "Shouldn't crash when calling a method on an Xray wrapper around a worker");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_bug788369.html b/components/script/dom/bindings/codegen/test/test_bug788369.html
new file mode 100644
index 00000000000..787bd28fe34
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_bug788369.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=788369
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 788369</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=788369">Mozilla Bug 788369</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 788369 **/
+try {
+ var xhr = new(window.ActiveXObject || XMLHttpRequest)("Microsoft.XMLHTTP");
+ ok(xhr instanceof XMLHttpRequest, "Should have an XHR object");
+} catch (e) {
+ ok(false, "Should not throw exception when constructing: " + e);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_enums.html b/components/script/dom/bindings/codegen/test/test_enums.html
new file mode 100644
index 00000000000..e5dc519a0c9
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_enums.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Enums</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+test(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("get", "foo")
+ assert_equals(xhr.responseType, "");
+ xhr.responseType = "foo";
+ assert_equals(xhr.responseType, "");
+}, "Assigning an invalid value to an enum attribute should not throw.");
+</script>
diff --git a/components/script/dom/bindings/codegen/test/test_forOf.html b/components/script/dom/bindings/codegen/test/test_forOf.html
new file mode 100644
index 00000000000..b1a3032a385
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_forOf.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725907
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 725907</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=725907">Mozilla Bug 725907</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<div id="basket">
+ <span id="egg0"></span>
+ <span id="egg1"><span id="duckling1"></span></span>
+ <span id="egg2"></span>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 725907 **/
+
+function runTestsForDocument(document, msgSuffix) {
+ function is(a, b, msg) { SimpleTest.is(a, b, msg + msgSuffix); }
+ function isnot(a, b, msg) { SimpleTest.isnot(a, b, msg + msgSuffix); }
+
+ var basket = document.getElementById("basket");
+ var egg3 = document.createElement("span");
+ egg3.id = "egg3";
+
+ var log = '';
+ for (var x of basket.childNodes) {
+ if (x.nodeType != x.TEXT_NODE)
+ log += x.id + ";";
+ }
+ is(log, "egg0;egg1;egg2;", "'for (x of div.childNodes)' should iterate over child nodes");
+
+ log = '';
+ for (var x of basket.childNodes) {
+ if (x.nodeType != x.TEXT_NODE) {
+ log += x.id + ";";
+ if (x.id == "egg1")
+ basket.appendChild(egg3);
+ }
+ }
+ is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.childNodes)' should see elements added during iteration");
+
+ var iter1 = basket.childNodes.iterator();
+ var iter2 = basket.childNodes.iterator();
+ isnot(iter1, iter2, "nodelist.iterator() returns a new iterator each time");
+
+ log = '';
+ basket.appendChild(document.createTextNode("some text"));
+ for (var x of basket.children)
+ log += x.id + ";";
+ is(log, "egg0;egg1;egg2;egg3;", "'for (x of div.children)' should iterate over child elements");
+
+ var iter1 = basket.children.iterator();
+ var iter2 = basket.children.iterator();
+ isnot(iter1, iter2, ".iterator() returns a new iterator each time");
+
+ var count = 0;
+ for (var x of document.getElementsByClassName("hazardous-materials"))
+ count++;
+ is(count, 0, "'for (x of emptyNodeList)' loop should run zero times");
+
+ var log = '';
+ for (var x of document.querySelectorAll("span"))
+ log += x.id + ";";
+ is(log, "egg0;egg1;duckling1;egg2;egg3;", "for-of loop should work with a querySelectorAll() NodeList");
+}
+
+/* All the tests run twice. First, in this document, so without any wrappers. */
+runTestsForDocument(document, "");
+
+/* And once using the document of an iframe, so working with cross-compartment wrappers. */
+SimpleTest.waitForExplicitFinish();
+function iframeLoaded(iframe) {
+ runTestsForDocument(iframe.contentWindow.document, " (in iframe)");
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe src="forOf_iframe.html" onload="iframeLoaded(this)"></iframe>
+
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_integers.html b/components/script/dom/bindings/codegen/test/test_integers.html
new file mode 100644
index 00000000000..6799fd791a8
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_integers.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <canvas id="c" width="1" height="1"></canvas>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+ function testInt64NonFinite(arg) {
+ // We can use a WebGLRenderingContext to test conversion to 64-bit signed
+ // ints edge cases.
+ try {
+ var gl = $("c").getContext("experimental-webgl");
+ } catch (ex) {
+ // No WebGL support on MacOS 10.5. Just skip this test
+ todo(false, "WebGL not supported");
+ return;
+ }
+ is(gl.getError(), 0, "Should not start in an error state");
+
+ var b = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, b);
+
+ var a = new Float32Array(1);
+ gl.bufferData(gl.ARRAY_BUFFER, a, gl.STATIC_DRAW);
+
+ gl.bufferSubData(gl.ARRAY_BUFFER, arg, a);
+
+ is(gl.getError(), 0, "Should have treated non-finite double as 0");
+ }
+
+ testInt64NonFinite(NaN);
+ testInt64NonFinite(Infinity);
+ testInt64NonFinite(-Infinity);
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_interfaceToString.html b/components/script/dom/bindings/codegen/test/test_interfaceToString.html
new file mode 100644
index 00000000000..cf670bf2d54
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_interfaceToString.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 742156</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=742156">Mozilla Bug 742156</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742156 **/
+
+var nativeToString = ("" + String.replace).replace("replace", "EventTarget");
+try {
+ var eventTargetToString = "" + EventTarget;
+ is(eventTargetToString, nativeToString,
+ "Stringifying a DOM interface object should return the same string" +
+ "as stringifying a native function.");
+}
+catch (e) {
+ ok(false, "Stringifying a DOM interface object shouldn't throw.");
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_lookupGetter.html b/components/script/dom/bindings/codegen/test/test_lookupGetter.html
new file mode 100644
index 00000000000..306ee4f643c
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_lookupGetter.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462428
+-->
+<head>
+ <title>Test for Bug 462428</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=462428">Mozilla Bug 462428</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 462428 **/
+var x = new XMLHttpRequest;
+x.open("GET", "");
+var getter = x.__lookupGetter__('readyState');
+ok(getter !== undefined, "But able to look it up the normal way");
+ok(!x.hasOwnProperty('readyState'), "property should still be on the prototype");
+
+var sawProp = false;
+for (var i in x) {
+ if (i === "readyState") {
+ sawProp = true;
+ }
+}
+
+ok(sawProp, "property should be enumerable");
+
+is(getter.call(x), 1, "the getter actually works");
+
+Object.getPrototypeOf(x).__defineSetter__('readyState', function() {});
+is(getter.call(x), 1, "the getter works after defineSetter");
+
+is(x.responseType, "", "Should have correct responseType up front");
+var setter = x.__lookupSetter__('responseType');
+setter.call(x, "document");
+is(x.responseType, "document", "the setter is bound correctly");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html b/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html
new file mode 100644
index 00000000000..e4f18f9986c
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_sequence_wrapping.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=775852
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 775852</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=775852">Mozilla Bug 775852</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <canvas width="1" height="1" id="c"></canvas>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 775852 **/
+function doTest() {
+ try {
+ var gl = $("c").getContext("experimental-webgl");
+ } catch (e) {
+ // No WebGL support on MacOS 10.5. Just skip this test
+ todo(false, "WebGL not supported");
+ return;
+ }
+ var setterCalled = false;
+
+ extLength = gl.getSupportedExtensions().length;
+ ok(extLength > 0,
+ "This test won't work right if we have no supported extensions");
+
+ Object.defineProperty(Array.prototype, "0",
+ {
+ set: function(val) {
+ setterCalled = true;
+ }
+ });
+
+ // Test that our property got defined correctly
+ var arr = []
+ arr[0] = 5;
+ is(setterCalled, true, "Setter should be called when setting prop on array");
+
+ setterCalled = false;
+
+ is(gl.getSupportedExtensions().length, extLength,
+ "We should still have the same number of extensions");
+
+ is(setterCalled, false,
+ "Setter should not be called when getting supported extensions");
+}
+doTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/codegen/test/test_traceProtos.html b/components/script/dom/bindings/codegen/test/test_traceProtos.html
new file mode 100644
index 00000000000..195876744d6
--- /dev/null
+++ b/components/script/dom/bindings/codegen/test/test_traceProtos.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=744772
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 744772</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=744772">Mozilla Bug 744772</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 744772 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function callback() {
+ new XMLHttpRequest().upload;
+ ok(true, "Accessing unreferenced DOM interface objects shouldn't crash");
+ SimpleTest.finish();
+}
+
+delete window.XMLHttpRequestUpload;
+SpecialPowers.exactGC(window, callback);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/components/script/dom/bindings/conversions.rs b/components/script/dom/bindings/conversions.rs
new file mode 100644
index 00000000000..8ce5b55e9d3
--- /dev/null
+++ b/components/script/dom/bindings/conversions.rs
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Conversions of Rust values to and from `JSVal`.
+
+use dom::bindings::js::{JS, JSRef, Root};
+use dom::bindings::str::ByteString;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::bindings::utils::jsstring_to_str;
+use dom::bindings::utils::unwrap_jsmanaged;
+use servo_util::str::DOMString;
+
+use js::jsapi::{JSBool, JSContext, JSObject};
+use js::jsapi::{JS_ValueToUint64, JS_ValueToInt64};
+use js::jsapi::{JS_ValueToECMAUint32, JS_ValueToECMAInt32};
+use js::jsapi::{JS_ValueToUint16, JS_ValueToNumber, JS_ValueToBoolean};
+use js::jsapi::{JS_ValueToString, JS_GetStringCharsAndLength};
+use js::jsapi::{JS_NewUCStringCopyN, JS_NewStringCopyN};
+use js::jsapi::{JS_WrapValue};
+use js::jsval::JSVal;
+use js::jsval::{UndefinedValue, NullValue, BooleanValue, Int32Value, UInt32Value};
+use js::jsval::{StringValue, ObjectValue, ObjectOrNullValue};
+use js::glue::RUST_JS_NumberValue;
+use libc;
+use std::default::Default;
+use std::slice;
+
+use dom::bindings::codegen::PrototypeList;
+
+// FIXME (https://github.com/rust-lang/rfcs/pull/4)
+// remove Option<Self> arguments.
+pub trait IDLInterface {
+ fn get_prototype_id(_: Option<Self>) -> PrototypeList::id::ID;
+ fn get_prototype_depth(_: Option<Self>) -> uint;
+}
+
+/// A trait to convert Rust types to `JSVal`s.
+pub trait ToJSValConvertible {
+ /// Convert `self` to a `JSVal`. JSAPI failure causes a task failure.
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal;
+}
+
+/// A trait to convert `JSVal`s to Rust types.
+pub trait FromJSValConvertible<T> {
+ /// Convert `val` to type `Self`.
+ /// Optional configuration of type `T` can be passed as the `option`
+ /// argument.
+ /// If it returns `Err(())`, a JSAPI exception is pending.
+ fn from_jsval(cx: *mut JSContext, val: JSVal, option: T) -> Result<Self, ()>;
+}
+
+
+impl ToJSValConvertible for () {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ UndefinedValue()
+ }
+}
+
+impl ToJSValConvertible for JSVal {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ let mut value = *self;
+ if unsafe { JS_WrapValue(cx, &mut value) } == 0 {
+ fail!("JS_WrapValue failed.");
+ }
+ value
+ }
+}
+
+unsafe fn convert_from_jsval<T: Default>(
+ cx: *mut JSContext, value: JSVal,
+ convert_fn: unsafe extern "C" fn(*mut JSContext, JSVal, *mut T) -> JSBool) -> Result<T, ()> {
+ let mut ret = Default::default();
+ if convert_fn(cx, value, &mut ret) == 0 {
+ Err(())
+ } else {
+ Ok(ret)
+ }
+}
+
+
+impl ToJSValConvertible for bool {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ BooleanValue(*self)
+ }
+}
+
+impl FromJSValConvertible<()> for bool {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<bool, ()> {
+ let result = unsafe { convert_from_jsval(cx, val, JS_ValueToBoolean) };
+ result.map(|b| b != 0)
+ }
+}
+
+impl ToJSValConvertible for i8 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ Int32Value(*self as i32)
+ }
+}
+
+impl FromJSValConvertible<()> for i8 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<i8, ()> {
+ let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) };
+ result.map(|v| v as i8)
+ }
+}
+
+impl ToJSValConvertible for u8 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ Int32Value(*self as i32)
+ }
+}
+
+impl FromJSValConvertible<()> for u8 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<u8, ()> {
+ let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) };
+ result.map(|v| v as u8)
+ }
+}
+
+impl ToJSValConvertible for i16 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ Int32Value(*self as i32)
+ }
+}
+
+impl FromJSValConvertible<()> for i16 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<i16, ()> {
+ let result = unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) };
+ result.map(|v| v as i16)
+ }
+}
+
+impl ToJSValConvertible for u16 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ Int32Value(*self as i32)
+ }
+}
+
+impl FromJSValConvertible<()> for u16 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<u16, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToUint16) }
+ }
+}
+
+impl ToJSValConvertible for i32 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ Int32Value(*self)
+ }
+}
+
+impl FromJSValConvertible<()> for i32 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<i32, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToECMAInt32) }
+ }
+}
+
+impl ToJSValConvertible for u32 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ UInt32Value(*self)
+ }
+}
+
+impl FromJSValConvertible<()> for u32 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<u32, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToECMAUint32) }
+ }
+}
+
+impl ToJSValConvertible for i64 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ unsafe {
+ RUST_JS_NumberValue(*self as f64)
+ }
+ }
+}
+
+impl FromJSValConvertible<()> for i64 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<i64, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToInt64) }
+ }
+}
+
+impl ToJSValConvertible for u64 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ unsafe {
+ RUST_JS_NumberValue(*self as f64)
+ }
+ }
+}
+
+impl FromJSValConvertible<()> for u64 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<u64, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToUint64) }
+ }
+}
+
+impl ToJSValConvertible for f32 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ unsafe {
+ RUST_JS_NumberValue(*self as f64)
+ }
+ }
+}
+
+impl FromJSValConvertible<()> for f32 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<f32, ()> {
+ let result = unsafe { convert_from_jsval(cx, val, JS_ValueToNumber) };
+ result.map(|f| f as f32)
+ }
+}
+
+impl ToJSValConvertible for f64 {
+ fn to_jsval(&self, _cx: *mut JSContext) -> JSVal {
+ unsafe {
+ RUST_JS_NumberValue(*self)
+ }
+ }
+}
+
+impl FromJSValConvertible<()> for f64 {
+ fn from_jsval(cx: *mut JSContext, val: JSVal, _option: ()) -> Result<f64, ()> {
+ unsafe { convert_from_jsval(cx, val, JS_ValueToNumber) }
+ }
+}
+
+impl ToJSValConvertible for DOMString {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ unsafe {
+ let string_utf16: Vec<u16> = self.as_slice().utf16_units().collect();
+ let jsstr = JS_NewUCStringCopyN(cx, string_utf16.as_ptr(), string_utf16.len() as libc::size_t);
+ if jsstr.is_null() {
+ fail!("JS_NewUCStringCopyN failed");
+ }
+ StringValue(&*jsstr)
+ }
+ }
+}
+
+/// Behavior for stringification of `JSVal`s.
+#[deriving(PartialEq)]
+pub enum StringificationBehavior {
+ /// Convert `null` to the string `"null"`.
+ Default,
+ /// Convert `null` to the empty string.
+ Empty,
+}
+
+impl Default for StringificationBehavior {
+ fn default() -> StringificationBehavior {
+ Default
+ }
+}
+
+impl FromJSValConvertible<StringificationBehavior> for DOMString {
+ fn from_jsval(cx: *mut JSContext, value: JSVal, nullBehavior: StringificationBehavior) -> Result<DOMString, ()> {
+ if nullBehavior == Empty && value.is_null() {
+ Ok("".to_string())
+ } else {
+ let jsstr = unsafe { JS_ValueToString(cx, value) };
+ if jsstr.is_null() {
+ debug!("JS_ValueToString failed");
+ Err(())
+ } else {
+ Ok(jsstring_to_str(cx, jsstr))
+ }
+ }
+ }
+}
+
+impl ToJSValConvertible for ByteString {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ unsafe {
+ let slice = self.as_slice();
+ let jsstr = JS_NewStringCopyN(cx, slice.as_ptr() as *const libc::c_char,
+ slice.len() as libc::size_t);
+ if jsstr.is_null() {
+ fail!("JS_NewStringCopyN failed");
+ }
+ StringValue(&*jsstr)
+ }
+ }
+}
+
+impl FromJSValConvertible<()> for ByteString {
+ fn from_jsval(cx: *mut JSContext, value: JSVal, _option: ()) -> Result<ByteString, ()> {
+ unsafe {
+ let string = JS_ValueToString(cx, value);
+ if string.is_null() {
+ debug!("JS_ValueToString failed");
+ return Err(());
+ }
+
+ let mut length = 0;
+ let chars = JS_GetStringCharsAndLength(cx, string, &mut length);
+ slice::raw::buf_as_slice(chars, length as uint, |char_vec| {
+ if char_vec.iter().any(|&c| c > 0xFF) {
+ // XXX Throw
+ Err(())
+ } else {
+ Ok(ByteString::new(char_vec.iter().map(|&c| c as u8).collect()))
+ }
+ })
+ }
+ }
+}
+
+impl ToJSValConvertible for Reflector {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ let obj = self.get_jsobject();
+ assert!(obj.is_not_null());
+ let mut value = ObjectValue(unsafe { &*obj });
+ if unsafe { JS_WrapValue(cx, &mut value) } == 0 {
+ fail!("JS_WrapValue failed.");
+ }
+ value
+ }
+}
+
+impl<T: Reflectable+IDLInterface> FromJSValConvertible<()> for JS<T> {
+ fn from_jsval(_cx: *mut JSContext, value: JSVal, _option: ()) -> Result<JS<T>, ()> {
+ if !value.is_object() {
+ return Err(());
+ }
+ unwrap_jsmanaged(value.to_object(),
+ IDLInterface::get_prototype_id(None::<T>),
+ IDLInterface::get_prototype_depth(None::<T>))
+ }
+}
+
+impl<'a, 'b, T: Reflectable> ToJSValConvertible for Root<'a, 'b, T> {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ self.reflector().to_jsval(cx)
+ }
+}
+
+impl<'a, T: Reflectable> ToJSValConvertible for JSRef<'a, T> {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ self.reflector().to_jsval(cx)
+ }
+}
+
+impl<'a, T: Reflectable> ToJSValConvertible for JS<T> {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ self.reflector().to_jsval(cx)
+ }
+}
+
+impl<T: ToJSValConvertible> ToJSValConvertible for Option<T> {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ match self {
+ &Some(ref value) => value.to_jsval(cx),
+ &None => NullValue(),
+ }
+ }
+}
+
+impl<X: Default, T: FromJSValConvertible<X>> FromJSValConvertible<()> for Option<T> {
+ fn from_jsval(cx: *mut JSContext, value: JSVal, _: ()) -> Result<Option<T>, ()> {
+ if value.is_null_or_undefined() {
+ Ok(None)
+ } else {
+ let option: X = Default::default();
+ let result: Result<T, ()> = FromJSValConvertible::from_jsval(cx, value, option);
+ result.map(Some)
+ }
+ }
+}
+
+impl ToJSValConvertible for *mut JSObject {
+ fn to_jsval(&self, cx: *mut JSContext) -> JSVal {
+ let mut wrapped = ObjectOrNullValue(*self);
+ unsafe {
+ assert!(JS_WrapValue(cx, &mut wrapped) != 0);
+ }
+ wrapped
+ }
+}
diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs
new file mode 100644
index 00000000000..cb39e4f0755
--- /dev/null
+++ b/components/script/dom/bindings/error.rs
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Utilities to throw exceptions from Rust bindings.
+
+use dom::bindings::conversions::ToJSValConvertible;
+use dom::bindings::global::GlobalRef;
+use dom::domexception::DOMException;
+
+use js::jsapi::{JSContext, JSBool, JSObject};
+use js::jsapi::{JS_IsExceptionPending, JS_SetPendingException, JS_ReportPendingException};
+use js::jsapi::{JS_ReportErrorNumber, JSErrorFormatString, JSEXN_TYPEERR};
+use js::jsapi::{JS_SaveFrameChain, JS_RestoreFrameChain};
+use js::glue::{ReportError};
+use js::rust::with_compartment;
+
+use libc;
+use std::ptr;
+
+/// DOM exceptions that can be thrown by a native DOM method.
+#[deriving(Show)]
+pub enum Error {
+ IndexSize,
+ FailureUnknown,
+ NotFound,
+ HierarchyRequest,
+ InvalidCharacter,
+ NotSupported,
+ InvalidState,
+ Syntax,
+ NamespaceError,
+ InvalidAccess,
+ Security,
+ Network,
+ Abort,
+ Timeout
+}
+
+/// The return type for IDL operations that can throw DOM exceptions.
+pub type Fallible<T> = Result<T, Error>;
+
+/// The return type for IDL operations that can throw DOM exceptions and
+/// return `()`.
+pub type ErrorResult = Fallible<()>;
+
+/// Set a pending DOM exception for the given `result` on `cx`.
+pub fn throw_dom_exception(cx: *mut JSContext, global: &GlobalRef,
+ result: Error) {
+ assert!(unsafe { JS_IsExceptionPending(cx) } == 0);
+ let exception = DOMException::new_from_error(global, result).root();
+ let thrown = exception.to_jsval(cx);
+ unsafe {
+ JS_SetPendingException(cx, thrown);
+ }
+}
+
+/// Report a pending exception, thereby clearing it.
+pub fn report_pending_exception(cx: *mut JSContext, obj: *mut JSObject) {
+ unsafe {
+ if JS_IsExceptionPending(cx) != 0 {
+ let saved = JS_SaveFrameChain(cx);
+ with_compartment(cx, obj, || {
+ JS_ReportPendingException(cx);
+ });
+ if saved != 0 {
+ JS_RestoreFrameChain(cx);
+ }
+ }
+ }
+}
+
+/// Throw an exception to signal that a `JSVal` can not be converted to any of
+/// the types in an IDL union type.
+pub fn throw_not_in_union(cx: *mut JSContext, names: &'static str) -> JSBool {
+ assert!(unsafe { JS_IsExceptionPending(cx) } == 0);
+ let message = format!("argument could not be converted to any of: {}", names);
+ message.with_c_str(|string| {
+ unsafe { ReportError(cx, string) };
+ });
+ return 0;
+}
+
+/// Format string used to throw `TypeError`s.
+static ERROR_FORMAT_STRING_STRING: [libc::c_char, ..4] = [
+ '{' as libc::c_char,
+ '0' as libc::c_char,
+ '}' as libc::c_char,
+ 0 as libc::c_char,
+];
+
+/// Format string struct used to throw `TypeError`s.
+static ERROR_FORMAT_STRING: JSErrorFormatString = JSErrorFormatString {
+ format: &ERROR_FORMAT_STRING_STRING as *const libc::c_char,
+ argCount: 1,
+ exnType: JSEXN_TYPEERR as i16,
+};
+
+/// Callback used to throw `TypeError`s.
+extern fn get_error_message(_user_ref: *mut libc::c_void,
+ _locale: *const libc::c_char,
+ error_number: libc::c_uint) -> *const JSErrorFormatString
+{
+ assert_eq!(error_number, 0);
+ &ERROR_FORMAT_STRING as *const JSErrorFormatString
+}
+
+/// Throw a `TypeError` with the given message.
+pub fn throw_type_error(cx: *mut JSContext, error: &str) {
+ let error = error.to_c_str();
+ unsafe {
+ JS_ReportErrorNumber(cx, Some(get_error_message), ptr::mut_null(), 0, error.as_ptr());
+ }
+}
diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs
new file mode 100644
index 00000000000..35b94d0e472
--- /dev/null
+++ b/components/script/dom/bindings/global.rs
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Abstractions for global scopes.
+//!
+//! This module contains smart pointers to global scopes, to simplify writing
+//! code that works in workers as well as window scopes.
+
+use dom::bindings::js::{JS, JSRef, Root};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::workerglobalscope::WorkerGlobalScope;
+use dom::window::Window;
+use script_task::ScriptChan;
+
+use servo_net::resource_task::ResourceTask;
+
+use js::jsapi::JSContext;
+
+use url::Url;
+
+/// A freely-copyable reference to a rooted global object.
+pub enum GlobalRef<'a> {
+ Window(JSRef<'a, Window>),
+ Worker(JSRef<'a, WorkerGlobalScope>),
+}
+
+/// A stack-based rooted reference to a global object.
+pub enum GlobalRoot<'a, 'b> {
+ WindowRoot(Root<'a, 'b, Window>),
+ WorkerRoot(Root<'a, 'b, WorkerGlobalScope>),
+}
+
+/// A traced reference to a global object, for use in fields of traced Rust
+/// structures.
+#[deriving(Encodable)]
+pub enum GlobalField {
+ WindowField(JS<Window>),
+ WorkerField(JS<WorkerGlobalScope>),
+}
+
+impl<'a> GlobalRef<'a> {
+ /// Get the `JSContext` for the `JSRuntime` associated with the thread
+ /// this global object is on.
+ pub fn get_cx(&self) -> *mut JSContext {
+ match *self {
+ Window(ref window) => window.get_cx(),
+ Worker(ref worker) => worker.get_cx(),
+ }
+ }
+
+ /// Extract a `Window`, causing task failure if the global object is not
+ /// a `Window`.
+ pub fn as_window<'b>(&'b self) -> &'b JSRef<'b, Window> {
+ match *self {
+ Window(ref window) => window,
+ Worker(_) => fail!("expected a Window scope"),
+ }
+ }
+
+ pub fn resource_task(&self) -> ResourceTask {
+ match *self {
+ Window(ref window) => window.page().resource_task.deref().clone(),
+ Worker(ref worker) => worker.resource_task().clone(),
+ }
+ }
+
+ pub fn get_url(&self) -> Url {
+ match *self {
+ Window(ref window) => window.get_url(),
+ Worker(ref worker) => worker.get_url().clone(),
+ }
+ }
+
+ /// `ScriptChan` used to send messages to the event loop of this global's
+ /// thread.
+ pub fn script_chan<'b>(&'b self) -> &'b ScriptChan {
+ match *self {
+ Window(ref window) => &window.script_chan,
+ Worker(ref worker) => worker.script_chan(),
+ }
+ }
+}
+
+impl<'a> Reflectable for GlobalRef<'a> {
+ fn reflector<'b>(&'b self) -> &'b Reflector {
+ match *self {
+ Window(ref window) => window.reflector(),
+ Worker(ref worker) => worker.reflector(),
+ }
+ }
+}
+
+impl<'a, 'b> GlobalRoot<'a, 'b> {
+ /// Obtain a safe reference to the global object that cannot outlive the
+ /// lifetime of this root.
+ pub fn root_ref<'c>(&'c self) -> GlobalRef<'c> {
+ match *self {
+ WindowRoot(ref window) => Window(window.root_ref()),
+ WorkerRoot(ref worker) => Worker(worker.root_ref()),
+ }
+ }
+}
+
+impl GlobalField {
+ /// Create a new `GlobalField` from a rooted reference.
+ pub fn from_rooted(global: &GlobalRef) -> GlobalField {
+ match *global {
+ Window(ref window) => WindowField(JS::from_rooted(window)),
+ Worker(ref worker) => WorkerField(JS::from_rooted(worker)),
+ }
+ }
+
+ /// Create a stack-bounded root for this reference.
+ pub fn root(&self) -> GlobalRoot {
+ match *self {
+ WindowField(ref window) => WindowRoot(window.root()),
+ WorkerField(ref worker) => WorkerRoot(worker.root()),
+ }
+ }
+}
diff --git a/components/script/dom/bindings/js.rs b/components/script/dom/bindings/js.rs
new file mode 100644
index 00000000000..ab8b3e3c7f5
--- /dev/null
+++ b/components/script/dom/bindings/js.rs
@@ -0,0 +1,496 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Smart pointers for the JS-managed DOM objects.
+//!
+//! The DOM is made up of Rust types whose lifetime is entirely controlled by the whims of
+//! the SpiderMonkey garbage collector. The types in this module are designed to ensure
+//! that any interactions with said Rust types only occur on values that will remain alive
+//! the entire time.
+//!
+//! Here is a brief overview of the important types:
+//!
+//! - `JSRef<T>`: a freely-copyable reference to a rooted value.
+//! - `Root<T>`: a stack-based reference to a rooted value.
+//! - `JS<T>`: a pointer to JS-owned memory that can automatically be traced by the GC when
+//! encountered as a field of a Rust structure.
+//! - `Temporary<T>`: a value that will remain rooted for the duration of its lifetime.
+//!
+//! The rule of thumb is as follows:
+//!
+//! - All methods return `Temporary<T>`, to ensure the value remains alive until it is stored
+//! somewhere that is reachable by the GC.
+//! - All functions take `&JSRef<T>` arguments, to ensure that they will remain uncollected for
+//! the duration of their usage.
+//! - All types contain `JS<T>` fields and derive the `Encodable` trait, to ensure that they are
+//! transitively marked as reachable by the GC if the enclosing value is reachable.
+//! - All methods for type `T` are implemented for `JSRef<T>`, to ensure that the self value
+//! will not be collected for the duration of the method call.
+//!
+//! Both `Temporary<T>` and `JS<T>` do not allow access to their inner value without explicitly
+//! creating a stack-based root via the `root` method. This returns a `Root<T>`, which causes
+//! the JS-owned value to be uncollectable for the duration of the `Root` object's lifetime.
+//! A `JSRef<T>` can be obtained from a `Root<T>` either by dereferencing the `Root<T>` (`*rooted`)
+//! or explicitly calling the `root_ref` method. These `JSRef<T>` values are not allowed to
+//! outlive their originating `Root<T>`, to ensure that all interactions with the enclosed value
+//! only occur when said value is uncollectable, and will cause static lifetime errors if
+//! misused.
+//!
+//! Other miscellaneous helper traits:
+//!
+//! - `OptionalRootable` and `OptionalRootedRootable`: make rooting `Option` values easy via a `root` method
+//! - `ResultRootable`: make rooting successful `Result` values easy
+//! - `TemporaryPushable`: allows mutating vectors of `JS<T>` with new elements of `JSRef`/`Temporary`
+//! - `OptionalSettable`: allows assigning `Option` values of `JSRef`/`Temporary` to fields of `Option<JS<T>>`
+//! - `RootedReference`: makes obtaining an `Option<JSRef<T>>` from an `Option<Root<T>>` easy
+
+use dom::bindings::utils::{Reflector, Reflectable};
+use dom::node::Node;
+use dom::xmlhttprequest::{XMLHttpRequest, TrustedXHRAddress};
+use dom::worker::{Worker, TrustedWorkerAddress};
+use js::jsapi::JSObject;
+use layout_interface::TrustedNodeAddress;
+use script_task::StackRoots;
+
+use std::cell::{Cell, RefCell};
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+
+/// A type that represents a JS-owned value that is rooted for the lifetime of this value.
+/// Importantly, it requires explicit rooting in order to interact with the inner value.
+/// Can be assigned into JS-owned member fields (i.e. `JS<T>` types) safely via the
+/// `JS<T>::assign` method or `OptionalSettable::assign` (for `Option<JS<T>>` fields).
+pub struct Temporary<T> {
+ inner: JS<T>,
+ /// On-stack JS pointer to assuage conservative stack scanner
+ _js_ptr: *mut JSObject,
+}
+
+impl<T> PartialEq for Temporary<T> {
+ fn eq(&self, other: &Temporary<T>) -> bool {
+ self.inner == other.inner
+ }
+}
+
+impl<T: Reflectable> Temporary<T> {
+ /// Create a new `Temporary` value from a JS-owned value.
+ pub fn new(inner: JS<T>) -> Temporary<T> {
+ Temporary {
+ inner: inner,
+ _js_ptr: inner.reflector().get_jsobject(),
+ }
+ }
+
+ /// Create a new `Temporary` value from a rooted value.
+ pub fn from_rooted<'a>(root: &JSRef<'a, T>) -> Temporary<T> {
+ Temporary::new(JS::from_rooted(root))
+ }
+
+ /// Create a stack-bounded root for this value.
+ pub fn root<'a, 'b>(self) -> Root<'a, 'b, T> {
+ let collection = StackRoots.get().unwrap();
+ unsafe {
+ (**collection).new_root(&self.inner)
+ }
+ }
+
+ unsafe fn inner(&self) -> JS<T> {
+ self.inner.clone()
+ }
+
+ //XXXjdm It would be lovely if this could be private.
+ pub unsafe fn transmute<To>(self) -> Temporary<To> {
+ mem::transmute(self)
+ }
+}
+
+/// A rooted, JS-owned value. Must only be used as a field in other JS-owned types.
+pub struct JS<T> {
+ ptr: *const T
+}
+
+impl<T> PartialEq for JS<T> {
+ fn eq(&self, other: &JS<T>) -> bool {
+ self.ptr == other.ptr
+ }
+}
+
+impl <T> Clone for JS<T> {
+ #[inline]
+ fn clone(&self) -> JS<T> {
+ JS {
+ ptr: self.ptr.clone()
+ }
+ }
+}
+
+impl JS<Node> {
+ /// Create a new JS-owned value wrapped from an address known to be a `Node` pointer.
+ pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> JS<Node> {
+ let TrustedNodeAddress(addr) = inner;
+ JS {
+ ptr: addr as *const Node
+ }
+ }
+}
+
+impl JS<XMLHttpRequest> {
+ pub unsafe fn from_trusted_xhr_address(inner: TrustedXHRAddress) -> JS<XMLHttpRequest> {
+ let TrustedXHRAddress(addr) = inner;
+ JS {
+ ptr: addr as *const XMLHttpRequest
+ }
+ }
+}
+
+impl JS<Worker> {
+ pub unsafe fn from_trusted_worker_address(inner: TrustedWorkerAddress) -> JS<Worker> {
+ let TrustedWorkerAddress(addr) = inner;
+ JS {
+ ptr: addr as *const Worker
+ }
+ }
+}
+
+impl<T: Reflectable> JS<T> {
+ /// Create a new JS-owned value wrapped from a raw Rust pointer.
+ pub unsafe fn from_raw(raw: *const T) -> JS<T> {
+ JS {
+ ptr: raw
+ }
+ }
+
+
+ /// Root this JS-owned value to prevent its collection as garbage.
+ pub fn root<'a, 'b>(&self) -> Root<'a, 'b, T> {
+ let collection = StackRoots.get().unwrap();
+ unsafe {
+ (**collection).new_root(self)
+ }
+ }
+}
+
+impl<T: Assignable<U>, U: Reflectable> JS<U> {
+ pub fn from_rooted(root: &T) -> JS<U> {
+ unsafe {
+ root.get_js()
+ }
+ }
+}
+
+//XXXjdm This is disappointing. This only gets called from trace hooks, in theory,
+// so it's safe to assume that self is rooted and thereby safe to access.
+impl<T: Reflectable> Reflectable for JS<T> {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ unsafe {
+ (*self.unsafe_get()).reflector()
+ }
+ }
+}
+
+impl<T: Reflectable> JS<T> {
+ /// Returns an unsafe pointer to the interior of this JS object without touching the borrow
+ /// flags. This is the only method that be safely accessed from layout. (The fact that this
+ /// is unsafe is what necessitates the layout wrappers.)
+ pub unsafe fn unsafe_get(&self) -> *mut T {
+ mem::transmute_copy(&self.ptr)
+ }
+
+ /// Store an unrooted value in this field. This is safe under the assumption that JS<T>
+ /// values are only used as fields in DOM types that are reachable in the GC graph,
+ /// so this unrooted value becomes transitively rooted for the lifetime of its new owner.
+ pub fn assign(&mut self, val: Temporary<T>) {
+ *self = unsafe { val.inner() };
+ }
+}
+
+impl<From, To> JS<From> {
+ //XXXjdm It would be lovely if this could be private.
+ pub unsafe fn transmute(self) -> JS<To> {
+ mem::transmute(self)
+ }
+
+ pub unsafe fn transmute_copy(&self) -> JS<To> {
+ mem::transmute_copy(self)
+ }
+}
+
+
+/// Get an `Option<JSRef<T>>` out of an `Option<Root<T>>`
+pub trait RootedReference<T> {
+ fn root_ref<'a>(&'a self) -> Option<JSRef<'a, T>>;
+}
+
+impl<'a, 'b, T: Reflectable> RootedReference<T> for Option<Root<'a, 'b, T>> {
+ fn root_ref<'a>(&'a self) -> Option<JSRef<'a, T>> {
+ self.as_ref().map(|root| root.root_ref())
+ }
+}
+
+/// Get an `Option<Option<JSRef<T>>>` out of an `Option<Option<Root<T>>>`
+pub trait OptionalRootedReference<T> {
+ fn root_ref<'a>(&'a self) -> Option<Option<JSRef<'a, T>>>;
+}
+
+impl<'a, 'b, T: Reflectable> OptionalRootedReference<T> for Option<Option<Root<'a, 'b, T>>> {
+ fn root_ref<'a>(&'a self) -> Option<Option<JSRef<'a, T>>> {
+ self.as_ref().map(|inner| inner.root_ref())
+ }
+}
+
+/// Trait that allows extracting a `JS<T>` value from a variety of rooting-related containers,
+/// which in general is an unsafe operation since they can outlive the rooted lifetime of the
+/// original value.
+/*definitely not public*/ trait Assignable<T> {
+ unsafe fn get_js(&self) -> JS<T>;
+}
+
+impl<T> Assignable<T> for JS<T> {
+ unsafe fn get_js(&self) -> JS<T> {
+ self.clone()
+ }
+}
+
+impl<'a, T> Assignable<T> for JSRef<'a, T> {
+ unsafe fn get_js(&self) -> JS<T> {
+ self.unrooted()
+ }
+}
+
+impl<T: Reflectable> Assignable<T> for Temporary<T> {
+ unsafe fn get_js(&self) -> JS<T> {
+ self.inner()
+ }
+}
+
+/// Assign an optional rootable value (either of `JS<T>` or `Temporary<T>`) to an optional
+/// field of a DOM type (ie. `Option<JS<T>>`)
+pub trait OptionalSettable<T> {
+ fn assign(&self, val: Option<T>);
+}
+
+impl<T: Assignable<U>, U: Reflectable> OptionalSettable<T> for Cell<Option<JS<U>>> {
+ fn assign(&self, val: Option<T>) {
+ self.set(val.map(|val| unsafe { val.get_js() }));
+ }
+}
+
+
+/// Root a rootable `Option` type (used for `Option<Temporary<T>>`)
+pub trait OptionalRootable<T> {
+ fn root<'a, 'b>(self) -> Option<Root<'a, 'b, T>>;
+}
+
+impl<T: Reflectable> OptionalRootable<T> for Option<Temporary<T>> {
+ fn root<'a, 'b>(self) -> Option<Root<'a, 'b, T>> {
+ self.map(|inner| inner.root())
+ }
+}
+
+/// Return an unrooted type for storing in optional DOM fields
+pub trait OptionalUnrootable<T> {
+ fn unrooted(&self) -> Option<JS<T>>;
+}
+
+impl<'a, T: Reflectable> OptionalUnrootable<T> for Option<JSRef<'a, T>> {
+ fn unrooted(&self) -> Option<JS<T>> {
+ self.as_ref().map(|inner| JS::from_rooted(inner))
+ }
+}
+
+/// Root a rootable `Option` type (used for `Option<JS<T>>`)
+pub trait OptionalRootedRootable<T> {
+ fn root<'a, 'b>(&self) -> Option<Root<'a, 'b, T>>;
+}
+
+impl<T: Reflectable> OptionalRootedRootable<T> for Option<JS<T>> {
+ fn root<'a, 'b>(&self) -> Option<Root<'a, 'b, T>> {
+ self.as_ref().map(|inner| inner.root())
+ }
+}
+
+/// Root a rootable `Option<Option>` type (used for `Option<Option<JS<T>>>`)
+pub trait OptionalOptionalRootedRootable<T> {
+ fn root<'a, 'b>(&self) -> Option<Option<Root<'a, 'b, T>>>;
+}
+
+impl<T: Reflectable> OptionalOptionalRootedRootable<T> for Option<Option<JS<T>>> {
+ fn root<'a, 'b>(&self) -> Option<Option<Root<'a, 'b, T>>> {
+ self.as_ref().map(|inner| inner.root())
+ }
+}
+
+
+/// Root a rootable `Result` type (any of `Temporary<T>` or `JS<T>`)
+pub trait ResultRootable<T,U> {
+ fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U>;
+}
+
+impl<T: Reflectable, U> ResultRootable<T, U> for Result<Temporary<T>, U> {
+ fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U> {
+ self.map(|inner| inner.root())
+ }
+}
+
+impl<T: Reflectable, U> ResultRootable<T, U> for Result<JS<T>, U> {
+ fn root<'a, 'b>(self) -> Result<Root<'a, 'b, T>, U> {
+ self.map(|inner| inner.root())
+ }
+}
+
+/// Provides a facility to push unrooted values onto lists of rooted values. This is safe
+/// under the assumption that said lists are reachable via the GC graph, and therefore the
+/// new values are transitively rooted for the lifetime of their new owner.
+pub trait TemporaryPushable<T> {
+ fn push_unrooted(&mut self, val: &T);
+ fn insert_unrooted(&mut self, index: uint, val: &T);
+}
+
+impl<T: Assignable<U>, U: Reflectable> TemporaryPushable<T> for Vec<JS<U>> {
+ fn push_unrooted(&mut self, val: &T) {
+ self.push(unsafe { val.get_js() });
+ }
+
+ fn insert_unrooted(&mut self, index: uint, val: &T) {
+ self.insert(index, unsafe { val.get_js() });
+ }
+}
+
+/// An opaque, LIFO rooting mechanism.
+pub struct RootCollection {
+ roots: RefCell<Vec<*mut JSObject>>,
+}
+
+impl RootCollection {
+ /// Create an empty collection of roots
+ pub fn new() -> RootCollection {
+ RootCollection {
+ roots: RefCell::new(vec!()),
+ }
+ }
+
+ /// Create a new stack-bounded root that will not outlive this collection
+ fn new_root<'a, 'b, T: Reflectable>(&'a self, unrooted: &JS<T>) -> Root<'a, 'b, T> {
+ Root::new(self, unrooted)
+ }
+
+ /// Track a stack-based root to ensure LIFO root ordering
+ fn root<'a, 'b, T: Reflectable>(&self, untracked: &Root<'a, 'b, T>) {
+ let mut roots = self.roots.borrow_mut();
+ roots.push(untracked.js_ptr);
+ debug!(" rooting {:?}", untracked.js_ptr);
+ }
+
+ /// Stop tracking a stack-based root, asserting if LIFO root ordering has been violated
+ fn unroot<'a, 'b, T: Reflectable>(&self, rooted: &Root<'a, 'b, T>) {
+ let mut roots = self.roots.borrow_mut();
+ debug!("unrooting {:?} (expecting {:?}", roots.last().unwrap(), rooted.js_ptr);
+ assert!(*roots.last().unwrap() == rooted.js_ptr);
+ roots.pop().unwrap();
+ }
+}
+
+/// A rooted JS value. The JS value is pinned for the duration of this object's lifetime;
+/// roots are additive, so this object's destruction will not invalidate other roots
+/// for the same JS value. `Root`s cannot outlive the associated `RootCollection` object.
+/// Attempts to transfer ownership of a `Root` via moving will trigger dynamic unrooting
+/// failures due to incorrect ordering.
+pub struct Root<'a, 'b, T> {
+ /// List that ensures correct dynamic root ordering
+ root_list: &'a RootCollection,
+ /// Reference to rooted value that must not outlive this container
+ jsref: JSRef<'b, T>,
+ /// On-stack JS pointer to assuage conservative stack scanner
+ js_ptr: *mut JSObject,
+}
+
+impl<'a, 'b, T: Reflectable> Root<'a, 'b, T> {
+ /// Create a new stack-bounded root for the provided JS-owned value.
+ /// It cannot not outlive its associated `RootCollection`, and it contains a `JSRef`
+ /// which cannot outlive this new `Root`.
+ fn new(roots: &'a RootCollection, unrooted: &JS<T>) -> Root<'a, 'b, T> {
+ let root = Root {
+ root_list: roots,
+ jsref: JSRef {
+ ptr: unrooted.ptr.clone(),
+ chain: ContravariantLifetime,
+ },
+ js_ptr: unrooted.reflector().get_jsobject(),
+ };
+ roots.root(&root);
+ root
+ }
+
+ /// Obtain a safe reference to the wrapped JS owned-value that cannot outlive
+ /// the lifetime of this root.
+ pub fn root_ref<'b>(&'b self) -> JSRef<'b,T> {
+ self.jsref.clone()
+ }
+}
+
+#[unsafe_destructor]
+impl<'a, 'b, T: Reflectable> Drop for Root<'a, 'b, T> {
+ fn drop(&mut self) {
+ self.root_list.unroot(self);
+ }
+}
+
+impl<'a, 'b, T: Reflectable> Deref<JSRef<'b, T>> for Root<'a, 'b, T> {
+ fn deref<'c>(&'c self) -> &'c JSRef<'b, T> {
+ &self.jsref
+ }
+}
+
+impl<'a, T: Reflectable> Deref<T> for JSRef<'a, T> {
+ fn deref<'b>(&'b self) -> &'b T {
+ unsafe {
+ &*self.ptr
+ }
+ }
+}
+
+/// Encapsulates a reference to something that is guaranteed to be alive. This is freely copyable.
+pub struct JSRef<'a, T> {
+ ptr: *const T,
+ chain: ContravariantLifetime<'a>,
+}
+
+impl<'a, T> Clone for JSRef<'a, T> {
+ fn clone(&self) -> JSRef<'a, T> {
+ JSRef {
+ ptr: self.ptr.clone(),
+ chain: self.chain,
+ }
+ }
+}
+
+impl<'a, T> PartialEq for JSRef<'a, T> {
+ fn eq(&self, other: &JSRef<T>) -> bool {
+ self.ptr == other.ptr
+ }
+}
+
+impl<'a,T> JSRef<'a,T> {
+ //XXXjdm It would be lovely if this could be private.
+ pub unsafe fn transmute<'b, To>(&'b self) -> &'b JSRef<'a, To> {
+ mem::transmute(self)
+ }
+
+ //XXXjdm It would be lovely if this could be private.
+ pub unsafe fn transmute_mut<'b, To>(&'b mut self) -> &'b mut JSRef<'a, To> {
+ mem::transmute(self)
+ }
+
+ pub fn unrooted(&self) -> JS<T> {
+ JS {
+ ptr: self.ptr
+ }
+ }
+}
+
+impl<'a, T: Reflectable> Reflectable for JSRef<'a, T> {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.deref().reflector()
+ }
+}
diff --git a/components/script/dom/bindings/proxyhandler.rs b/components/script/dom/bindings/proxyhandler.rs
new file mode 100644
index 00000000000..f2c1486280d
--- /dev/null
+++ b/components/script/dom/bindings/proxyhandler.rs
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+///! Utilities for the implementation of JSAPI proxy handlers.
+
+use dom::bindings::utils::delete_property_by_id;
+use dom::bindings::utils::is_dom_proxy;
+use js::jsapi::{JSContext, jsid, JSPropertyDescriptor, JSObject, JSString, jschar};
+use js::jsapi::{JS_GetPropertyDescriptorById, JS_NewUCString, JS_malloc, JS_free};
+use js::jsapi::{JS_DefinePropertyById, JS_NewObjectWithGivenProto};
+use js::jsapi::{JS_ReportErrorFlagsAndNumber, JS_StrictPropertyStub};
+use js::jsapi::{JSREPORT_WARNING, JSREPORT_STRICT, JSREPORT_STRICT_MODE_ERROR};
+use js::jsval::ObjectValue;
+use js::glue::GetProxyExtra;
+use js::glue::{GetObjectProto, GetObjectParent, SetProxyExtra, GetProxyHandler};
+use js::glue::InvokeGetOwnPropertyDescriptor;
+use js::glue::RUST_js_GetErrorMessage;
+use js::{JSPROP_GETTER, JSPROP_ENUMERATE, JSPROP_READONLY, JSRESOLVE_QUALIFIED};
+
+use libc;
+use std::mem;
+use std::ptr;
+use std::string;
+use std::mem::size_of;
+
+static JSPROXYSLOT_EXPANDO: u32 = 0;
+
+pub extern fn getPropertyDescriptor(cx: *mut JSContext, proxy: *mut JSObject,
+ id: jsid, set: bool,
+ desc: *mut JSPropertyDescriptor)
+ -> bool {
+ unsafe {
+ let handler = GetProxyHandler(proxy);
+ if !InvokeGetOwnPropertyDescriptor(handler, cx, proxy, id, set, desc) {
+ return false;
+ }
+ if (*desc).obj.is_not_null() {
+ return true;
+ }
+
+ //let proto = JS_GetPrototype(proxy);
+ let proto = GetObjectProto(proxy);
+ if proto.is_null() {
+ (*desc).obj = ptr::mut_null();
+ return true;
+ }
+
+ JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, desc) != 0
+ }
+}
+
+pub fn defineProperty_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
+ desc: *mut JSPropertyDescriptor) -> bool {
+ static JSMSG_GETTER_ONLY: libc::c_uint = 160;
+
+ unsafe {
+ //FIXME: Workaround for https://github.com/mozilla/rust/issues/13385
+ let setter: *const libc::c_void = mem::transmute((*desc).setter);
+ let setter_stub: *const libc::c_void = mem::transmute(JS_StrictPropertyStub);
+ if ((*desc).attrs & JSPROP_GETTER) != 0 && setter == setter_stub {
+ return JS_ReportErrorFlagsAndNumber(cx,
+ JSREPORT_WARNING | JSREPORT_STRICT |
+ JSREPORT_STRICT_MODE_ERROR,
+ Some(RUST_js_GetErrorMessage), ptr::mut_null(),
+ JSMSG_GETTER_ONLY) != 0;
+ }
+
+ let expando = EnsureExpandoObject(cx, proxy);
+ if expando.is_null() {
+ return false;
+ }
+
+ return JS_DefinePropertyById(cx, expando, id, (*desc).value, (*desc).getter,
+ (*desc).setter, (*desc).attrs) != 0;
+ }
+}
+
+pub extern fn defineProperty(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
+ desc: *mut JSPropertyDescriptor) -> bool {
+ defineProperty_(cx, proxy, id, desc)
+}
+
+pub extern fn delete_(cx: *mut JSContext, proxy: *mut JSObject, id: jsid,
+ bp: *mut bool) -> bool {
+ unsafe {
+ let expando = EnsureExpandoObject(cx, proxy);
+ if expando.is_null() {
+ return false;
+ }
+
+ return delete_property_by_id(cx, expando, id, &mut *bp);
+ }
+}
+
+pub fn _obj_toString(cx: *mut JSContext, className: *const libc::c_char) -> *mut JSString {
+ unsafe {
+ let name = string::raw::from_buf(className as *const i8 as *const u8);
+ let nchars = "[object ]".len() + name.len();
+ let chars: *mut jschar = JS_malloc(cx, (nchars + 1) as libc::size_t * (size_of::<jschar>() as libc::size_t)) as *mut jschar;
+ if chars.is_null() {
+ return ptr::mut_null();
+ }
+
+ let result = format!("[object {}]", name);
+ let result = result.as_slice();
+ for (i, c) in result.chars().enumerate() {
+ *chars.offset(i as int) = c as jschar;
+ }
+ *chars.offset(nchars as int) = 0;
+ let jsstr = JS_NewUCString(cx, chars, nchars as libc::size_t);
+ if jsstr.is_null() {
+ JS_free(cx, chars as *mut libc::c_void);
+ }
+ jsstr
+ }
+}
+
+pub fn GetExpandoObject(obj: *mut JSObject) -> *mut JSObject {
+ unsafe {
+ assert!(is_dom_proxy(obj));
+ let val = GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
+ if val.is_undefined() {
+ ptr::mut_null()
+ } else {
+ val.to_object()
+ }
+ }
+}
+
+pub fn EnsureExpandoObject(cx: *mut JSContext, obj: *mut JSObject) -> *mut JSObject {
+ unsafe {
+ assert!(is_dom_proxy(obj));
+ let mut expando = GetExpandoObject(obj);
+ if expando.is_null() {
+ expando = JS_NewObjectWithGivenProto(cx, ptr::mut_null(),
+ ptr::mut_null(),
+ GetObjectParent(obj));
+ if expando.is_null() {
+ return ptr::mut_null();
+ }
+
+ SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(&*expando));
+ }
+ return expando;
+ }
+}
+
+pub fn FillPropertyDescriptor(desc: &mut JSPropertyDescriptor, obj: *mut JSObject, readonly: bool) {
+ desc.obj = obj;
+ desc.attrs = if readonly { JSPROP_READONLY } else { 0 } | JSPROP_ENUMERATE;
+ desc.getter = None;
+ desc.setter = None;
+ desc.shortid = 0;
+}
diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs
new file mode 100644
index 00000000000..825716bdf3e
--- /dev/null
+++ b/components/script/dom/bindings/str.rs
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The `ByteString` struct.
+
+use std::from_str::FromStr;
+use std::hash::{Hash, sip};
+use std::path::BytesContainer;
+use std::str;
+
+/// Encapsulates the IDL `ByteString` type.
+#[deriving(Encodable,Clone,Eq,PartialEq)]
+pub struct ByteString(Vec<u8>);
+
+impl ByteString {
+ /// Creates a new `ByteString`.
+ pub fn new(value: Vec<u8>) -> ByteString {
+ ByteString(value)
+ }
+
+ /// Returns `self` as a string, if it encodes valid UTF-8, and `None`
+ /// otherwise.
+ pub fn as_str<'a>(&'a self) -> Option<&'a str> {
+ let ByteString(ref vec) = *self;
+ str::from_utf8(vec.as_slice())
+ }
+
+ /// Returns the underlying vector as a slice.
+ pub fn as_slice<'a>(&'a self) -> &'a [u8] {
+ let ByteString(ref vector) = *self;
+ vector.as_slice()
+ }
+
+ /// Returns the length.
+ pub fn len(&self) -> uint {
+ let ByteString(ref vector) = *self;
+ vector.len()
+ }
+
+ /// Compare `self` to `other`, matching A–Z and a–z as equal.
+ pub fn eq_ignore_case(&self, other: &ByteString) -> bool {
+ // XXXManishearth make this more efficient
+ self.to_lower() == other.to_lower()
+ }
+
+ /// Returns `self` with A–Z replaced by a–z.
+ pub fn to_lower(&self) -> ByteString {
+ let ByteString(ref vec) = *self;
+ ByteString::new(vec.iter().map(|&x| {
+ if x > 'A' as u8 && x < 'Z' as u8 {
+ x + ('a' as u8) - ('A' as u8)
+ } else {
+ x
+ }
+ }).collect())
+ }
+
+ /// Returns whether `self` is a `token`, as defined by
+ /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-17).
+ pub fn is_token(&self) -> bool {
+ let ByteString(ref vec) = *self;
+ if vec.len() == 0 {
+ return false; // A token must be at least a single character
+ }
+ vec.iter().all(|&x| {
+ // http://tools.ietf.org/html/rfc2616#section-2.2
+ match x {
+ 0..31 | 127 => false, // CTLs
+ 40 | 41 | 60 | 62 | 64 |
+ 44 | 59 | 58 | 92 | 34 |
+ 47 | 91 | 93 | 63 | 61 |
+ 123 | 125 | 32 => false, // separators
+ x if x > 127 => false, // non-CHARs
+ _ => true
+ }
+ })
+ }
+
+ /// Returns whether `self` is a `field-value`, as defined by
+ /// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
+ pub fn is_field_value(&self) -> bool {
+ // Classifications of characters necessary for the [CRLF] (SP|HT) rule
+ #[deriving(PartialEq)]
+ enum PreviousCharacter {
+ Other,
+ CR,
+ LF,
+ SPHT // SP or HT
+ }
+ let ByteString(ref vec) = *self;
+ let mut prev = Other; // The previous character
+ vec.iter().all(|&x| {
+ // http://tools.ietf.org/html/rfc2616#section-2.2
+ match x {
+ 13 => { // CR
+ if prev == Other || prev == SPHT {
+ prev = CR;
+ true
+ } else {
+ false
+ }
+ },
+ 10 => { // LF
+ if prev == CR {
+ prev = LF;
+ true
+ } else {
+ false
+ }
+ },
+ 32 => { // SP
+ if prev == LF || prev == SPHT {
+ prev = SPHT;
+ true
+ } else if prev == Other {
+ // Counts as an Other here, since it's not preceded by a CRLF
+ // SP is not a CTL, so it can be used anywhere
+ // though if used immediately after a CR the CR is invalid
+ // We don't change prev since it's already Other
+ true
+ } else {
+ false
+ }
+ },
+ 9 => { // HT
+ if prev == LF || prev == SPHT {
+ prev = SPHT;
+ true
+ } else {
+ false
+ }
+ },
+ 0..31 | 127 => false, // CTLs
+ x if x > 127 => false, // non ASCII
+ _ if prev == Other || prev == SPHT => {
+ prev = Other;
+ true
+ },
+ _ => false // Previous character was a CR/LF but not part of the [CRLF] (SP|HT) rule
+ }
+ })
+ }
+}
+
+impl Hash for ByteString {
+ fn hash(&self, state: &mut sip::SipState) {
+ let ByteString(ref vec) = *self;
+ vec.hash(state);
+ }
+}
+
+impl FromStr for ByteString {
+ fn from_str(s: &str) -> Option<ByteString> {
+ Some(ByteString::new(s.container_into_owned_bytes()))
+ }
+}
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs
new file mode 100644
index 00000000000..42d944e9781
--- /dev/null
+++ b/components/script/dom/bindings/trace.rs
@@ -0,0 +1,185 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Utilities for tracing JS-managed values.
+//!
+//! The lifetime of DOM objects is managed by the SpiderMonkey Garbage
+//! Collector. A rooted DOM object implementing the interface `Foo` is traced
+//! as follows:
+//!
+//! 1. The GC calls `_trace` defined in `FooBinding` during the marking
+//! phase. (This happens through `JSClass.trace` for non-proxy bindings, and
+//! through `ProxyTraps.trace` otherwise.)
+//! 2. `_trace` calls `Foo::trace()` (an implementation of `JSTraceable`,
+//! defined in `InheritTypes.rs`).
+//! 3. `Foo::trace()` calls `Foo::encode()` (an implementation of `Encodable`).
+//! This implementation is typically derived by a `#[deriving(Encodable)]`
+//! annotation on the Rust struct.
+//! 4. For all fields (except those wrapped in `Untraceable`), `Foo::encode()`
+//! calls `encode()` on the field.
+//!
+//! For example, for fields of type `JS<T>`, `JS<T>::encode()` calls
+//! `trace_reflector()`.
+//! 6. `trace_reflector()` calls `trace_object()` with the `JSObject` for the
+//! reflector.
+//! 7. `trace_object()` calls `JS_CallTracer()` to notify the GC, which will
+//! add the object to the graph, and will trace that object as well.
+
+use dom::bindings::js::JS;
+use dom::bindings::utils::{Reflectable, Reflector};
+
+use js::jsapi::{JSObject, JSTracer, JS_CallTracer, JSTRACE_OBJECT};
+use js::jsval::JSVal;
+
+use libc;
+use std::mem;
+use std::cell::{Cell, RefCell};
+use serialize::{Encodable, Encoder};
+
+// IMPORTANT: We rely on the fact that we never attempt to encode DOM objects using
+// any encoder but JSTracer. Since we derive trace hooks automatically,
+// we are unfortunately required to use generic types everywhere and
+// unsafely cast to the concrete JSTracer we actually require.
+
+fn get_jstracer<'a, S: Encoder<E>, E>(s: &'a mut S) -> &'a mut JSTracer {
+ unsafe {
+ mem::transmute(s)
+ }
+}
+
+impl<T: Reflectable+Encodable<S, E>, S: Encoder<E>, E> Encodable<S, E> for JS<T> {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ trace_reflector(get_jstracer(s), "", self.reflector());
+ Ok(())
+ }
+}
+
+impl<S: Encoder<E>, E> Encodable<S, E> for Reflector {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+/// A trait to allow tracing (only) DOM objects.
+pub trait JSTraceable {
+ fn trace(&self, trc: *mut JSTracer);
+}
+
+/// Trace a `JSVal`.
+pub fn trace_jsval(tracer: *mut JSTracer, description: &str, val: JSVal) {
+ if !val.is_markable() {
+ return;
+ }
+
+ unsafe {
+ let name = description.to_c_str();
+ (*tracer).debugPrinter = None;
+ (*tracer).debugPrintIndex = -1;
+ (*tracer).debugPrintArg = name.as_ptr() as *const libc::c_void;
+ debug!("tracing value {:s}", description);
+ JS_CallTracer(tracer, val.to_gcthing(), val.trace_kind());
+ }
+}
+
+/// Trace the `JSObject` held by `reflector`.
+pub fn trace_reflector(tracer: *mut JSTracer, description: &str, reflector: &Reflector) {
+ trace_object(tracer, description, reflector.get_jsobject())
+}
+
+/// Trace a `JSObject`.
+pub fn trace_object(tracer: *mut JSTracer, description: &str, obj: *mut JSObject) {
+ unsafe {
+ let name = description.to_c_str();
+ (*tracer).debugPrinter = None;
+ (*tracer).debugPrintIndex = -1;
+ (*tracer).debugPrintArg = name.as_ptr() as *const libc::c_void;
+ debug!("tracing {:s}", description);
+ JS_CallTracer(tracer, obj as *mut libc::c_void, JSTRACE_OBJECT);
+ }
+}
+
+/// Encapsulates a type that cannot easily have `Encodable` derived automagically,
+/// but also does not need to be made known to the SpiderMonkey garbage collector.
+///
+/// Use only with types that are not associated with a JS reflector and do not contain
+/// fields of types associated with JS reflectors.
+///
+/// This should really only be used for types that are from other crates,
+/// so we can't implement `Encodable`. See more details: mozilla#2662.
+pub struct Untraceable<T> {
+ inner: T,
+}
+
+impl<T> Untraceable<T> {
+ pub fn new(val: T) -> Untraceable<T> {
+ Untraceable {
+ inner: val
+ }
+ }
+}
+
+impl<S: Encoder<E>, E, T> Encodable<S, E> for Untraceable<T> {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+impl<T> Deref<T> for Untraceable<T> {
+ fn deref<'a>(&'a self) -> &'a T {
+ &self.inner
+ }
+}
+
+/// Encapsulates a type that can be traced but is boxed in a type we don't
+/// control (such as `RefCell`).
+///
+/// Wrap a field in Traceable and implement the `Encodable` trait
+/// for that new concrete type to achieve magic compiler-derived trace hooks.
+///
+/// We always prefer this, in case the contained type ever changes to something that should be traced.
+/// See more details: mozilla#2662.
+#[deriving(PartialEq, Clone)]
+pub struct Traceable<T> {
+ inner: T
+}
+
+impl<T> Traceable<T> {
+ pub fn new(val: T) -> Traceable<T> {
+ Traceable {
+ inner: val
+ }
+ }
+}
+
+impl<T> Deref<T> for Traceable<T> {
+ fn deref<'a>(&'a self) -> &'a T {
+ &self.inner
+ }
+}
+
+impl<S: Encoder<E>, E, T: Encodable<S, E>> Encodable<S, E> for Traceable<RefCell<T>> {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ self.borrow().encode(s)
+ }
+}
+
+impl<S: Encoder<E>, E, T: Encodable<S, E>+Copy> Encodable<S, E> for Traceable<Cell<T>> {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ self.deref().get().encode(s)
+ }
+}
+
+impl<S: Encoder<E>, E> Encodable<S, E> for Traceable<*mut JSObject> {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ trace_object(get_jstracer(s), "object", **self);
+ Ok(())
+ }
+}
+
+impl<S: Encoder<E>, E> Encodable<S, E> for Traceable<JSVal> {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ trace_jsval(get_jstracer(s), "val", **self);
+ Ok(())
+ }
+}
diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs
new file mode 100644
index 00000000000..08b65dd084d
--- /dev/null
+++ b/components/script/dom/bindings/utils.rs
@@ -0,0 +1,791 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Various utilities to glue JavaScript and the DOM implementation together.
+
+use dom::bindings::codegen::PrototypeList;
+use dom::bindings::codegen::PrototypeList::MAX_PROTO_CHAIN_LENGTH;
+use dom::bindings::conversions::{FromJSValConvertible, IDLInterface};
+use dom::bindings::error::throw_type_error;
+use dom::bindings::global::{GlobalRef, GlobalField, WindowField, WorkerField};
+use dom::bindings::js::{JS, Temporary, Root};
+use dom::bindings::trace::Untraceable;
+use dom::browsercontext;
+use dom::window;
+use servo_util::str::DOMString;
+
+use libc;
+use libc::c_uint;
+use std::cell::Cell;
+use std::mem;
+use std::cmp::PartialEq;
+use std::ptr;
+use std::slice;
+use js::glue::{js_IsObjectProxyClass, js_IsFunctionProxyClass, IsProxyHandlerFamily};
+use js::glue::{GetGlobalForObjectCrossCompartment, UnwrapObject, GetProxyHandlerExtra};
+use js::glue::{IsWrapper, RUST_JSID_TO_STRING, RUST_JSID_IS_INT};
+use js::glue::{RUST_JSID_IS_STRING, RUST_JSID_TO_INT};
+use js::jsapi::{JS_AlreadyHasOwnProperty, JS_NewFunction};
+use js::jsapi::{JS_DefineProperties, JS_ForwardGetPropertyTo};
+use js::jsapi::{JS_GetClass, JS_LinkConstructorAndPrototype, JS_GetStringCharsAndLength};
+use js::jsapi::{JS_ObjectIsRegExp, JS_ObjectIsDate, JSHandleObject};
+use js::jsapi::JS_GetFunctionObject;
+use js::jsapi::{JS_HasPropertyById, JS_GetPrototype};
+use js::jsapi::{JS_GetProperty, JS_HasProperty};
+use js::jsapi::{JS_DefineFunctions, JS_DefineProperty};
+use js::jsapi::{JS_ValueToString, JS_GetReservedSlot, JS_SetReservedSlot};
+use js::jsapi::{JSContext, JSObject, JSBool, jsid, JSClass};
+use js::jsapi::{JSFunctionSpec, JSPropertySpec};
+use js::jsapi::{JS_NewGlobalObject, JS_InitStandardClasses};
+use js::jsapi::{JSString};
+use js::jsapi::JS_DeletePropertyById2;
+use js::jsfriendapi::JS_ObjectToOuterObject;
+use js::jsfriendapi::bindgen::JS_NewObjectWithUniqueType;
+use js::jsval::JSVal;
+use js::jsval::{PrivateValue, ObjectValue, NullValue, ObjectOrNullValue};
+use js::jsval::{Int32Value, UInt32Value, DoubleValue, BooleanValue, UndefinedValue};
+use js::rust::with_compartment;
+use js::{JSPROP_ENUMERATE, JSCLASS_IS_GLOBAL, JSCLASS_IS_DOMJSCLASS};
+use js::JSPROP_PERMANENT;
+use js::{JSFUN_CONSTRUCTOR, JSPROP_READONLY};
+use js;
+
+#[allow(raw_pointer_deriving)]
+#[deriving(Encodable)]
+pub struct GlobalStaticData {
+ pub windowproxy_handler: Untraceable<*const libc::c_void>,
+}
+
+pub fn GlobalStaticData() -> GlobalStaticData {
+ GlobalStaticData {
+ windowproxy_handler: Untraceable::new(browsercontext::new_window_proxy_handler()),
+ }
+}
+
+/// Returns whether the given `clasp` is one for a DOM object.
+fn is_dom_class(clasp: *const JSClass) -> bool {
+ unsafe {
+ ((*clasp).flags & js::JSCLASS_IS_DOMJSCLASS) != 0
+ }
+}
+
+/// Returns whether `obj` is a DOM object implemented as a proxy.
+pub fn is_dom_proxy(obj: *mut JSObject) -> bool {
+ unsafe {
+ (js_IsObjectProxyClass(obj) || js_IsFunctionProxyClass(obj)) &&
+ IsProxyHandlerFamily(obj)
+ }
+}
+
+/// Returns the index of the slot wherein a pointer to the reflected DOM object
+/// is stored.
+///
+/// Fails if `obj` is not a DOM object.
+pub unsafe fn dom_object_slot(obj: *mut JSObject) -> u32 {
+ let clasp = JS_GetClass(obj);
+ if is_dom_class(&*clasp) {
+ DOM_OBJECT_SLOT as u32
+ } else {
+ assert!(is_dom_proxy(obj));
+ DOM_PROXY_OBJECT_SLOT as u32
+ }
+}
+
+/// Get the DOM object from the given reflector.
+pub unsafe fn unwrap<T>(obj: *mut JSObject) -> *const T {
+ let slot = dom_object_slot(obj);
+ let val = JS_GetReservedSlot(obj, slot);
+ val.to_private() as *const T
+}
+
+/// Get the `DOMClass` from `obj`, or `Err(())` if `obj` is not a DOM object.
+pub unsafe fn get_dom_class(obj: *mut JSObject) -> Result<DOMClass, ()> {
+ let clasp = JS_GetClass(obj);
+ if is_dom_class(&*clasp) {
+ debug!("plain old dom object");
+ let domjsclass: *const DOMJSClass = clasp as *const DOMJSClass;
+ return Ok((*domjsclass).dom_class);
+ }
+ if is_dom_proxy(obj) {
+ debug!("proxy dom object");
+ let dom_class: *const DOMClass = GetProxyHandlerExtra(obj) as *const DOMClass;
+ return Ok(*dom_class);
+ }
+ debug!("not a dom object");
+ return Err(());
+}
+
+/// Get a `JS<T>` for the given DOM object, unwrapping any wrapper around it
+/// first, and checking if the object is of the correct type.
+///
+/// Returns Err(()) if `obj` is an opaque security wrapper or if the object is
+/// not a reflector for a DOM object of the given type (as defined by the
+/// proto_id and proto_depth).
+pub fn unwrap_jsmanaged<T: Reflectable>(mut obj: *mut JSObject,
+ proto_id: PrototypeList::id::ID,
+ proto_depth: uint) -> Result<JS<T>, ()> {
+ unsafe {
+ let dom_class = get_dom_class(obj).or_else(|_| {
+ if IsWrapper(obj) == 1 {
+ debug!("found wrapper");
+ obj = UnwrapObject(obj, /* stopAtOuter = */ 0, ptr::mut_null());
+ if obj.is_null() {
+ debug!("unwrapping security wrapper failed");
+ Err(())
+ } else {
+ assert!(IsWrapper(obj) == 0);
+ debug!("unwrapped successfully");
+ get_dom_class(obj)
+ }
+ } else {
+ debug!("not a dom wrapper");
+ Err(())
+ }
+ });
+
+ dom_class.and_then(|dom_class| {
+ if dom_class.interface_chain[proto_depth] == proto_id {
+ debug!("good prototype");
+ Ok(JS::from_raw(unwrap(obj)))
+ } else {
+ debug!("bad prototype");
+ Err(())
+ }
+ })
+ }
+}
+
+/// Leak the given pointer.
+pub unsafe fn squirrel_away_unique<T>(x: Box<T>) -> *const T {
+ mem::transmute(x)
+}
+
+/// Convert the given `JSString` to a `DOMString`. Fails if the string does not
+/// contain valid UTF-16.
+pub fn jsstring_to_str(cx: *mut JSContext, s: *mut JSString) -> DOMString {
+ unsafe {
+ let mut length = 0;
+ let chars = JS_GetStringCharsAndLength(cx, s, &mut length);
+ slice::raw::buf_as_slice(chars, length as uint, |char_vec| {
+ String::from_utf16(char_vec).unwrap()
+ })
+ }
+}
+
+/// Convert the given `jsid` to a `DOMString`. Fails if the `jsid` is not a
+/// string, or if the string does not contain valid UTF-16.
+pub fn jsid_to_str(cx: *mut JSContext, id: jsid) -> DOMString {
+ unsafe {
+ assert!(RUST_JSID_IS_STRING(id) != 0);
+ jsstring_to_str(cx, RUST_JSID_TO_STRING(id))
+ }
+}
+
+/// The index of the slot wherein a pointer to the reflected DOM object is
+/// stored for non-proxy bindings.
+// We use slot 0 for holding the raw object. This is safe for both
+// globals and non-globals.
+pub static DOM_OBJECT_SLOT: uint = 0;
+static DOM_PROXY_OBJECT_SLOT: uint = js::JSSLOT_PROXY_PRIVATE as uint;
+
+// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
+// LSetDOMProperty. Those constants need to be changed accordingly if this value
+// changes.
+static DOM_PROTO_INSTANCE_CLASS_SLOT: u32 = 0;
+
+/// The index of the slot that contains a reference to the ProtoOrIfaceArray.
+// All DOM globals must have a slot at DOM_PROTOTYPE_SLOT.
+pub static DOM_PROTOTYPE_SLOT: u32 = js::JSCLASS_GLOBAL_SLOT_COUNT;
+
+/// The flag set on the `JSClass`es for DOM global objects.
+// NOTE: This is baked into the Ion JIT as 0 in codegen for LGetDOMProperty and
+// LSetDOMProperty. Those constants need to be changed accordingly if this value
+// changes.
+pub static JSCLASS_DOM_GLOBAL: u32 = js::JSCLASS_USERBIT1;
+
+/// Representation of an IDL constant value.
+#[deriving(Clone)]
+pub enum ConstantVal {
+ IntVal(i32),
+ UintVal(u32),
+ DoubleVal(f64),
+ BoolVal(bool),
+ NullVal,
+ VoidVal
+}
+
+/// Representation of an IDL constant.
+#[deriving(Clone)]
+pub struct ConstantSpec {
+ pub name: &'static [u8],
+ pub value: ConstantVal
+}
+
+impl ConstantSpec {
+ /// Returns a `JSVal` that represents the value of this `ConstantSpec`.
+ pub fn get_value(&self) -> JSVal {
+ match self.value {
+ NullVal => NullValue(),
+ IntVal(i) => Int32Value(i),
+ UintVal(u) => UInt32Value(u),
+ DoubleVal(d) => DoubleValue(d),
+ BoolVal(b) => BooleanValue(b),
+ VoidVal => UndefinedValue(),
+ }
+ }
+}
+
+/// Helper structure for cross-origin wrappers for DOM binding objects.
+pub struct NativePropertyHooks {
+ /// The property arrays for this interface.
+ pub native_properties: &'static NativeProperties,
+
+ /// The NativePropertyHooks instance for the parent interface, if any.
+ pub proto_hooks: Option<&'static NativePropertyHooks>,
+}
+
+/// The struct that holds inheritance information for DOM object reflectors.
+pub struct DOMClass {
+ /// A list of interfaces that this object implements, in order of decreasing
+ /// derivedness.
+ pub interface_chain: [PrototypeList::id::ID, ..MAX_PROTO_CHAIN_LENGTH],
+
+ /// The NativePropertyHooks for the interface associated with this class.
+ pub native_hooks: &'static NativePropertyHooks,
+}
+
+/// The JSClass used for DOM object reflectors.
+pub struct DOMJSClass {
+ pub base: js::Class,
+ pub dom_class: DOMClass
+}
+
+/// Returns the ProtoOrIfaceArray for the given global object.
+/// Fails if `global` is not a DOM global object.
+pub fn GetProtoOrIfaceArray(global: *mut JSObject) -> *mut *mut JSObject {
+ unsafe {
+ assert!(((*JS_GetClass(global)).flags & JSCLASS_DOM_GLOBAL) != 0);
+ JS_GetReservedSlot(global, DOM_PROTOTYPE_SLOT).to_private() as *mut *mut JSObject
+ }
+}
+
+/// Contains references to lists of methods, attributes, and constants for a
+/// given interface.
+pub struct NativeProperties {
+ pub methods: Option<&'static [JSFunctionSpec]>,
+ pub attrs: Option<&'static [JSPropertySpec]>,
+ pub consts: Option<&'static [ConstantSpec]>,
+ pub staticMethods: Option<&'static [JSFunctionSpec]>,
+ pub staticAttrs: Option<&'static [JSPropertySpec]>,
+}
+
+/// A JSNative that cannot be null.
+pub type NonNullJSNative =
+ unsafe extern "C" fn (arg1: *mut JSContext, arg2: c_uint, arg3: *mut JSVal) -> JSBool;
+
+/// Creates the *interface prototype object* and the *interface object* (if
+/// needed).
+/// Fails on JSAPI failure.
+pub fn CreateInterfaceObjects2(cx: *mut JSContext, global: *mut JSObject, receiver: *mut JSObject,
+ protoProto: *mut JSObject,
+ protoClass: &'static JSClass,
+ constructor: Option<(NonNullJSNative, &'static str, u32)>,
+ domClass: *const DOMClass,
+ members: &'static NativeProperties) -> *mut JSObject {
+ let proto = CreateInterfacePrototypeObject(cx, global, protoProto,
+ protoClass, members);
+
+ unsafe {
+ JS_SetReservedSlot(proto, DOM_PROTO_INSTANCE_CLASS_SLOT,
+ PrivateValue(domClass as *const libc::c_void));
+ }
+
+ match constructor {
+ Some((native, name, nargs)) => {
+ let s = name.to_c_str();
+ CreateInterfaceObject(cx, global, receiver,
+ native, nargs, proto,
+ members, s.as_ptr())
+ },
+ None => (),
+ }
+
+ proto
+}
+
+/// Creates the *interface object*.
+/// Fails on JSAPI failure.
+fn CreateInterfaceObject(cx: *mut JSContext, global: *mut JSObject, receiver: *mut JSObject,
+ constructorNative: NonNullJSNative,
+ ctorNargs: u32, proto: *mut JSObject,
+ members: &'static NativeProperties,
+ name: *const libc::c_char) {
+ unsafe {
+ let fun = JS_NewFunction(cx, Some(constructorNative), ctorNargs,
+ JSFUN_CONSTRUCTOR, global, name);
+ assert!(fun.is_not_null());
+
+ let constructor = JS_GetFunctionObject(fun);
+ assert!(constructor.is_not_null());
+
+ match members.staticMethods {
+ Some(staticMethods) => DefineMethods(cx, constructor, staticMethods),
+ _ => (),
+ }
+
+ match members.staticAttrs {
+ Some(staticProperties) => DefineProperties(cx, constructor, staticProperties),
+ _ => (),
+ }
+
+ match members.consts {
+ Some(constants) => DefineConstants(cx, constructor, constants),
+ _ => (),
+ }
+
+ if proto.is_not_null() {
+ assert!(JS_LinkConstructorAndPrototype(cx, constructor, proto) != 0);
+ }
+
+ let mut alreadyDefined = 0;
+ assert!(JS_AlreadyHasOwnProperty(cx, receiver, name, &mut alreadyDefined) != 0);
+
+ if alreadyDefined == 0 {
+ assert!(JS_DefineProperty(cx, receiver, name,
+ ObjectValue(&*constructor),
+ None, None, 0) != 0);
+ }
+ }
+}
+
+/// Defines constants on `obj`.
+/// Fails on JSAPI failure.
+fn DefineConstants(cx: *mut JSContext, obj: *mut JSObject, constants: &'static [ConstantSpec]) {
+ for spec in constants.iter() {
+ unsafe {
+ assert!(JS_DefineProperty(cx, obj, spec.name.as_ptr() as *const libc::c_char,
+ spec.get_value(), None, None,
+ JSPROP_ENUMERATE | JSPROP_READONLY |
+ JSPROP_PERMANENT) != 0);
+ }
+ }
+}
+
+/// Defines methods on `obj`. The last entry of `methods` must contain zeroed
+/// memory.
+/// Fails on JSAPI failure.
+fn DefineMethods(cx: *mut JSContext, obj: *mut JSObject, methods: &'static [JSFunctionSpec]) {
+ unsafe {
+ assert!(JS_DefineFunctions(cx, obj, methods.as_ptr()) != 0);
+ }
+}
+
+/// Defines attributes on `obj`. The last entry of `properties` must contain
+/// zeroed memory.
+/// Fails on JSAPI failure.
+fn DefineProperties(cx: *mut JSContext, obj: *mut JSObject, properties: &'static [JSPropertySpec]) {
+ unsafe {
+ assert!(JS_DefineProperties(cx, obj, properties.as_ptr()) != 0);
+ }
+}
+
+/// Creates the *interface prototype object*.
+/// Fails on JSAPI failure.
+fn CreateInterfacePrototypeObject(cx: *mut JSContext, global: *mut JSObject,
+ parentProto: *mut JSObject,
+ protoClass: &'static JSClass,
+ members: &'static NativeProperties) -> *mut JSObject {
+ unsafe {
+ let ourProto = JS_NewObjectWithUniqueType(cx, protoClass, &*parentProto, &*global);
+ assert!(ourProto.is_not_null());
+
+ match members.methods {
+ Some(methods) => DefineMethods(cx, ourProto, methods),
+ _ => (),
+ }
+
+ match members.attrs {
+ Some(properties) => DefineProperties(cx, ourProto, properties),
+ _ => (),
+ }
+
+ match members.consts {
+ Some(constants) => DefineConstants(cx, ourProto, constants),
+ _ => (),
+ }
+
+ return ourProto;
+ }
+}
+
+/// A throwing constructor, for those interfaces that have neither
+/// `NoInterfaceObject` nor `Constructor`.
+pub extern fn ThrowingConstructor(cx: *mut JSContext, _argc: c_uint, _vp: *mut JSVal) -> JSBool {
+ throw_type_error(cx, "Illegal constructor.");
+ return 0;
+}
+
+/// Construct and cache the ProtoOrIfaceArray for the given global.
+/// Fails if the argument is not a DOM global.
+pub fn initialize_global(global: *mut JSObject) {
+ let protoArray = box () ([0 as *mut JSObject, ..PrototypeList::id::IDCount as uint]);
+ unsafe {
+ assert!(((*JS_GetClass(global)).flags & JSCLASS_DOM_GLOBAL) != 0);
+ let box_ = squirrel_away_unique(protoArray);
+ JS_SetReservedSlot(global,
+ DOM_PROTOTYPE_SLOT,
+ PrivateValue(box_ as *const libc::c_void));
+ }
+}
+
+/// A trait to provide access to the `Reflector` for a DOM object.
+pub trait Reflectable {
+ fn reflector<'a>(&'a self) -> &'a Reflector;
+}
+
+/// Create the reflector for a new DOM object and yield ownership to the
+/// reflector.
+pub fn reflect_dom_object<T: Reflectable>
+ (obj: Box<T>,
+ global: &GlobalRef,
+ wrap_fn: extern "Rust" fn(*mut JSContext, &GlobalRef, Box<T>) -> Temporary<T>)
+ -> Temporary<T> {
+ wrap_fn(global.get_cx(), global, obj)
+}
+
+/// A struct to store a reference to the reflector of a DOM object.
+#[allow(raw_pointer_deriving)]
+#[deriving(PartialEq)]
+pub struct Reflector {
+ object: Cell<*mut JSObject>,
+}
+
+impl Reflector {
+ /// Get the reflector.
+ #[inline]
+ pub fn get_jsobject(&self) -> *mut JSObject {
+ self.object.get()
+ }
+
+ /// Initialize the reflector. (May be called only once.)
+ pub fn set_jsobject(&self, object: *mut JSObject) {
+ assert!(self.object.get().is_null());
+ assert!(object.is_not_null());
+ self.object.set(object);
+ }
+
+ /// Return a pointer to the memory location at which the JS reflector object is stored.
+ /// Used by Temporary values to root the reflector, as required by the JSAPI rooting
+ /// APIs.
+ pub fn rootable(&self) -> *mut *mut JSObject {
+ &self.object as *const Cell<*mut JSObject>
+ as *mut Cell<*mut JSObject>
+ as *mut *mut JSObject
+ }
+
+ /// Create an uninitialized `Reflector`.
+ pub fn new() -> Reflector {
+ Reflector {
+ object: Cell::new(ptr::mut_null()),
+ }
+ }
+}
+
+pub fn GetPropertyOnPrototype(cx: *mut JSContext, proxy: *mut JSObject, id: jsid, found: *mut bool,
+ vp: *mut JSVal) -> bool {
+ unsafe {
+ //let proto = GetObjectProto(proxy);
+ let proto = JS_GetPrototype(proxy);
+ if proto.is_null() {
+ *found = false;
+ return true;
+ }
+ let mut hasProp = 0;
+ if JS_HasPropertyById(cx, proto, id, &mut hasProp) == 0 {
+ return false;
+ }
+ *found = hasProp != 0;
+ let no_output = vp.is_null();
+ if hasProp == 0 || no_output {
+ return true;
+ }
+
+ JS_ForwardGetPropertyTo(cx, proto, id, proxy, vp) != 0
+ }
+}
+
+/// Get an array index from the given `jsid`. Returns `None` if the given
+/// `jsid` is not an integer.
+pub fn GetArrayIndexFromId(_cx: *mut JSContext, id: jsid) -> Option<u32> {
+ unsafe {
+ if RUST_JSID_IS_INT(id) != 0 {
+ return Some(RUST_JSID_TO_INT(id) as u32);
+ }
+ return None;
+ }
+ // if id is length atom, -1, otherwise
+ /*return if JSID_IS_ATOM(id) {
+ let atom = JSID_TO_ATOM(id);
+ //let s = *GetAtomChars(id);
+ if s > 'a' && s < 'z' {
+ return -1;
+ }
+
+ let i = 0;
+ let str = AtomToLinearString(JSID_TO_ATOM(id));
+ return if StringIsArray(str, &mut i) != 0 { i } else { -1 }
+ } else {
+ IdToInt32(cx, id);
+ }*/
+}
+
+/// Find the index of a string given by `v` in `values`.
+/// Returns `Err(())` on JSAPI failure (there is a pending exception), and
+/// `Ok(None)` if there was no matching string.
+pub fn FindEnumStringIndex(cx: *mut JSContext,
+ v: JSVal,
+ values: &[&'static str]) -> Result<Option<uint>, ()> {
+ unsafe {
+ let jsstr = JS_ValueToString(cx, v);
+ if jsstr.is_null() {
+ return Err(());
+ }
+
+ let mut length = 0;
+ let chars = JS_GetStringCharsAndLength(cx, jsstr, &mut length);
+ if chars.is_null() {
+ return Err(());
+ }
+
+ Ok(values.iter().position(|value| {
+ value.len() == length as uint &&
+ range(0, length as uint).all(|j| {
+ value.as_bytes()[j] as u16 == *chars.offset(j as int)
+ })
+ }))
+ }
+}
+
+/// Get the property with name `property` from `object`.
+/// Returns `Err(())` on JSAPI failure (there is a pending exception), and
+/// `Ok(None)` if there was no property with the given name.
+pub fn get_dictionary_property(cx: *mut JSContext,
+ object: *mut JSObject,
+ property: &str) -> Result<Option<JSVal>, ()> {
+ use std::c_str::CString;
+ fn has_property(cx: *mut JSContext, object: *mut JSObject, property: &CString,
+ found: &mut JSBool) -> bool {
+ unsafe {
+ JS_HasProperty(cx, object, property.as_ptr(), found) != 0
+ }
+ }
+ fn get_property(cx: *mut JSContext, object: *mut JSObject, property: &CString,
+ value: &mut JSVal) -> bool {
+ unsafe {
+ JS_GetProperty(cx, object, property.as_ptr(), value) != 0
+ }
+ }
+
+ let property = property.to_c_str();
+ if object.is_null() {
+ return Ok(None);
+ }
+
+ let mut found: JSBool = 0;
+ if !has_property(cx, object, &property, &mut found) {
+ return Err(());
+ }
+
+ if found == 0 {
+ return Ok(None);
+ }
+
+ let mut value = NullValue();
+ if !get_property(cx, object, &property, &mut value) {
+ return Err(());
+ }
+
+ Ok(Some(value))
+}
+
+pub fn HasPropertyOnPrototype(cx: *mut JSContext, proxy: *mut JSObject, id: jsid) -> bool {
+ // MOZ_ASSERT(js::IsProxy(proxy) && js::GetProxyHandler(proxy) == handler);
+ let mut found = false;
+ return !GetPropertyOnPrototype(cx, proxy, id, &mut found, ptr::mut_null()) || found;
+}
+
+/// Returns whether `obj` can be converted to a callback interface per IDL.
+pub fn IsConvertibleToCallbackInterface(cx: *mut JSContext, obj: *mut JSObject) -> bool {
+ unsafe {
+ JS_ObjectIsDate(cx, obj) == 0 && JS_ObjectIsRegExp(cx, obj) == 0
+ }
+}
+
+/// Create a DOM global object with the given class.
+pub fn CreateDOMGlobal(cx: *mut JSContext, class: *const JSClass) -> *mut JSObject {
+ unsafe {
+ let obj = JS_NewGlobalObject(cx, class, ptr::mut_null());
+ if obj.is_null() {
+ return ptr::mut_null();
+ }
+ with_compartment(cx, obj, || {
+ JS_InitStandardClasses(cx, obj);
+ });
+ initialize_global(obj);
+ obj
+ }
+}
+
+/// Callback to outerize windows when wrapping.
+pub extern fn wrap_for_same_compartment(cx: *mut JSContext, obj: *mut JSObject) -> *mut JSObject {
+ unsafe {
+ JS_ObjectToOuterObject(cx, obj)
+ }
+}
+
+/// Callback to outerize windows before wrapping.
+pub extern fn pre_wrap(cx: *mut JSContext, _scope: *mut JSObject,
+ obj: *mut JSObject, _flags: c_uint) -> *mut JSObject {
+ unsafe {
+ JS_ObjectToOuterObject(cx, obj)
+ }
+}
+
+/// Callback to outerize windows.
+pub extern fn outerize_global(_cx: *mut JSContext, obj: JSHandleObject) -> *mut JSObject {
+ unsafe {
+ debug!("outerizing");
+ let obj = *obj.unnamed_field1;
+ let win: Root<window::Window> =
+ unwrap_jsmanaged(obj,
+ IDLInterface::get_prototype_id(None::<window::Window>),
+ IDLInterface::get_prototype_depth(None::<window::Window>))
+ .unwrap()
+ .root();
+ win.deref().browser_context.deref().borrow().get_ref().window_proxy()
+ }
+}
+
+/// Returns the global object of the realm that the given JS object was created in.
+pub fn global_object_for_js_object(obj: *mut JSObject) -> GlobalField {
+ unsafe {
+ let global = GetGlobalForObjectCrossCompartment(obj);
+ let clasp = JS_GetClass(global);
+ assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
+ match FromJSValConvertible::from_jsval(ptr::mut_null(), ObjectOrNullValue(global), ()) {
+ Ok(window) => return WindowField(window),
+ Err(_) => (),
+ }
+
+ match FromJSValConvertible::from_jsval(ptr::mut_null(), ObjectOrNullValue(global), ()) {
+ Ok(worker) => return WorkerField(worker),
+ Err(_) => (),
+ }
+
+ fail!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope")
+ }
+}
+
+/// Get the `JSContext` for the `JSRuntime` associated with the thread
+/// this object is on.
+fn cx_for_dom_reflector(obj: *mut JSObject) -> *mut JSContext {
+ let global = global_object_for_js_object(obj);
+ let global = global.root();
+ global.root_ref().get_cx()
+}
+
+/// Get the `JSContext` for the `JSRuntime` associated with the thread
+/// this DOM object is on.
+pub fn cx_for_dom_object<T: Reflectable>(obj: &T) -> *mut JSContext {
+ cx_for_dom_reflector(obj.reflector().get_jsobject())
+}
+
+pub unsafe fn delete_property_by_id(cx: *mut JSContext, object: *mut JSObject,
+ id: jsid, bp: &mut bool) -> bool {
+ let mut value = UndefinedValue();
+ if JS_DeletePropertyById2(cx, object, id, &mut value) == 0 {
+ return false;
+ }
+
+ *bp = value.to_boolean();
+ return true;
+}
+
+/// Results of `xml_name_type`.
+#[deriving(PartialEq)]
+pub enum XMLName {
+ QName,
+ Name,
+ InvalidXMLName
+}
+
+/// Check if an element name is valid. See http://www.w3.org/TR/xml/#NT-Name
+/// for details.
+pub fn xml_name_type(name: &str) -> XMLName {
+ fn is_valid_start(c: char) -> bool {
+ match c {
+ ':' |
+ 'A' .. 'Z' |
+ '_' |
+ 'a' .. 'z' |
+ '\xC0' .. '\xD6' |
+ '\xD8' .. '\xF6' |
+ '\xF8' .. '\u02FF' |
+ '\u0370' .. '\u037D' |
+ '\u037F' .. '\u1FFF' |
+ '\u200C' .. '\u200D' |
+ '\u2070' .. '\u218F' |
+ '\u2C00' .. '\u2FEF' |
+ '\u3001' .. '\uD7FF' |
+ '\uF900' .. '\uFDCF' |
+ '\uFDF0' .. '\uFFFD' |
+ '\U00010000' .. '\U000EFFFF' => true,
+ _ => false,
+ }
+ }
+
+ fn is_valid_continuation(c: char) -> bool {
+ is_valid_start(c) || match c {
+ '-' |
+ '.' |
+ '0' .. '9' |
+ '\xB7' |
+ '\u0300' .. '\u036F' |
+ '\u203F' .. '\u2040' => true,
+ _ => false,
+ }
+ }
+
+ let mut iter = name.chars();
+ let mut non_qname_colons = false;
+ let mut seen_colon = false;
+ match iter.next() {
+ None => return InvalidXMLName,
+ Some(c) => {
+ if !is_valid_start(c) {
+ return InvalidXMLName;
+ }
+ if c == ':' {
+ non_qname_colons = true;
+ }
+ }
+ }
+
+ for c in name.chars() {
+ if !is_valid_continuation(c) {
+ return InvalidXMLName;
+ }
+ if c == ':' {
+ match seen_colon {
+ true => non_qname_colons = true,
+ false => seen_colon = true
+ }
+ }
+ }
+
+ match non_qname_colons {
+ false => QName,
+ true => Name
+ }
+}
diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs
new file mode 100644
index 00000000000..1a7d2a21636
--- /dev/null
+++ b/components/script/dom/blob.rs
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::InheritTypes::FileDerived;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::Temporary;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::bindings::error::Fallible;
+use dom::bindings::codegen::Bindings::BlobBinding;
+
+#[deriving(Encodable)]
+pub enum BlobType {
+ BlobTypeId,
+ FileTypeId
+}
+
+#[deriving(Encodable)]
+pub struct Blob {
+ reflector_: Reflector,
+ type_: BlobType
+}
+
+impl Blob {
+ pub fn new_inherited() -> Blob {
+ Blob {
+ reflector_: Reflector::new(),
+ type_: BlobTypeId
+ }
+ }
+
+ pub fn new(global: &GlobalRef) -> Temporary<Blob> {
+ reflect_dom_object(box Blob::new_inherited(),
+ global,
+ BlobBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<Blob>> {
+ Ok(Blob::new(global))
+ }
+}
+
+pub trait BlobMethods {
+}
+
+impl Reflectable for Blob {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+impl FileDerived for Blob {
+ fn is_file(&self) -> bool {
+ match self.type_ {
+ FileTypeId => true,
+ _ => false
+ }
+ }
+}
diff --git a/components/script/dom/browsercontext.rs b/components/script/dom/browsercontext.rs
new file mode 100644
index 00000000000..a54477a2ff8
--- /dev/null
+++ b/components/script/dom/browsercontext.rs
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::Reflectable;
+use dom::document::Document;
+use dom::window::Window;
+
+use js::jsapi::JSObject;
+use js::glue::{WrapperNew, CreateWrapperProxyHandler, ProxyTraps};
+use js::rust::with_compartment;
+
+use libc::c_void;
+use std::ptr;
+
+#[allow(raw_pointer_deriving)]
+#[deriving(Encodable)]
+pub struct BrowserContext {
+ history: Vec<SessionHistoryEntry>,
+ active_index: uint,
+ window_proxy: Traceable<*mut JSObject>,
+}
+
+impl BrowserContext {
+ pub fn new(document: &JSRef<Document>) -> BrowserContext {
+ let mut context = BrowserContext {
+ history: vec!(SessionHistoryEntry::new(document)),
+ active_index: 0,
+ window_proxy: Traceable::new(ptr::mut_null()),
+ };
+ context.create_window_proxy();
+ context
+ }
+
+ pub fn active_document(&self) -> Temporary<Document> {
+ Temporary::new(self.history[self.active_index].document.clone())
+ }
+
+ pub fn active_window(&self) -> Temporary<Window> {
+ let doc = self.active_document().root();
+ Temporary::new(doc.deref().window.clone())
+ }
+
+ pub fn window_proxy(&self) -> *mut JSObject {
+ assert!(self.window_proxy.deref().is_not_null());
+ *self.window_proxy
+ }
+
+ fn create_window_proxy(&mut self) {
+ let win = self.active_window().root();
+ let page = win.deref().page();
+ let js_info = page.js_info();
+
+ let handler = js_info.get_ref().dom_static.windowproxy_handler;
+ assert!(handler.deref().is_not_null());
+
+ let parent = win.deref().reflector().get_jsobject();
+ let cx = js_info.get_ref().js_context.deref().deref().ptr;
+ let wrapper = with_compartment(cx, parent, || unsafe {
+ WrapperNew(cx, parent, *handler.deref())
+ });
+ assert!(wrapper.is_not_null());
+ self.window_proxy = Traceable::new(wrapper);
+ }
+}
+
+#[deriving(Encodable)]
+pub struct SessionHistoryEntry {
+ document: JS<Document>,
+ children: Vec<BrowserContext>
+}
+
+impl SessionHistoryEntry {
+ fn new(document: &JSRef<Document>) -> SessionHistoryEntry {
+ SessionHistoryEntry {
+ document: JS::from_rooted(document),
+ children: vec!()
+ }
+ }
+}
+
+static proxy_handler: ProxyTraps = ProxyTraps {
+ getPropertyDescriptor: None,
+ getOwnPropertyDescriptor: None,
+ defineProperty: None,
+ getOwnPropertyNames: 0 as *const u8,
+ delete_: None,
+ enumerate: 0 as *const u8,
+
+ has: None,
+ hasOwn: None,
+ get: None,
+ set: None,
+ keys: 0 as *const u8,
+ iterate: None,
+
+ call: None,
+ construct: None,
+ nativeCall: 0 as *const u8,
+ hasInstance: None,
+ typeOf: None,
+ objectClassIs: None,
+ obj_toString: None,
+ fun_toString: None,
+ //regexp_toShared: 0 as *u8,
+ defaultValue: None,
+ iteratorNext: None,
+ finalize: None,
+ getElementIfPresent: None,
+ getPrototypeOf: None,
+ trace: None
+};
+
+pub fn new_window_proxy_handler() -> *const c_void {
+ unsafe {
+ CreateWrapperProxyHandler(&proxy_handler)
+ }
+}
diff --git a/components/script/dom/canvasrenderingcontext2d.rs b/components/script/dom/canvasrenderingcontext2d.rs
new file mode 100644
index 00000000000..6b3f898123f
--- /dev/null
+++ b/components/script/dom/canvasrenderingcontext2d.rs
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding;
+use dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::CanvasRenderingContext2DMethods;
+use dom::bindings::global::{GlobalRef, GlobalField};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Untraceable;
+use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
+use dom::htmlcanvaselement::HTMLCanvasElement;
+
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+
+use canvas::canvas_render_task::{CanvasMsg, CanvasRenderTask, ClearRect, Close, FillRect, Recreate, StrokeRect};
+
+#[deriving(Encodable)]
+pub struct CanvasRenderingContext2D {
+ reflector_: Reflector,
+ global: GlobalField,
+ renderer: Untraceable<Sender<CanvasMsg>>,
+ canvas: JS<HTMLCanvasElement>,
+}
+
+impl CanvasRenderingContext2D {
+ pub fn new_inherited(global: &GlobalRef, canvas: &JSRef<HTMLCanvasElement>, size: Size2D<i32>) -> CanvasRenderingContext2D {
+ CanvasRenderingContext2D {
+ reflector_: Reflector::new(),
+ global: GlobalField::from_rooted(global),
+ renderer: Untraceable::new(CanvasRenderTask::start(size)),
+ canvas: JS::from_rooted(canvas),
+ }
+ }
+
+ pub fn new(global: &GlobalRef, canvas: &JSRef<HTMLCanvasElement>, size: Size2D<i32>) -> Temporary<CanvasRenderingContext2D> {
+ reflect_dom_object(box CanvasRenderingContext2D::new_inherited(global, canvas, size),
+ global, CanvasRenderingContext2DBinding::Wrap)
+ }
+
+ pub fn recreate(&self, size: Size2D<i32>) {
+ self.renderer.send(Recreate(size));
+ }
+}
+
+impl<'a> CanvasRenderingContext2DMethods for JSRef<'a, CanvasRenderingContext2D> {
+ fn Canvas(&self) -> Temporary<HTMLCanvasElement> {
+ Temporary::new(self.canvas)
+ }
+
+ fn FillRect(&self, x: f64, y: f64, width: f64, height: f64) {
+ let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32));
+ self.renderer.send(FillRect(rect));
+ }
+
+ fn ClearRect(&self, x: f64, y: f64, width: f64, height: f64) {
+ let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32));
+ self.renderer.send(ClearRect(rect));
+ }
+
+ fn StrokeRect(&self, x: f64, y: f64, width: f64, height: f64) {
+ let rect = Rect(Point2D(x as f32, y as f32), Size2D(width as f32, height as f32));
+ self.renderer.send(StrokeRect(rect));
+ }
+}
+
+impl Reflectable for CanvasRenderingContext2D {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for CanvasRenderingContext2D {
+ fn drop(&mut self) {
+ self.renderer.send(Close);
+ }
+}
diff --git a/components/script/dom/characterdata.rs b/components/script/dom/characterdata.rs
new file mode 100644
index 00000000000..ebb17cf6993
--- /dev/null
+++ b/components/script/dom/characterdata.rs
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! DOM bindings for `CharacterData`.
+
+use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
+use dom::bindings::codegen::InheritTypes::{CharacterDataDerived, NodeCast};
+use dom::bindings::error::{Fallible, ErrorResult, IndexSize};
+use dom::bindings::js::JSRef;
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::node::{CommentNodeTypeId, Node, NodeTypeId, TextNodeTypeId, ProcessingInstructionNodeTypeId, NodeHelpers};
+use servo_util::str::DOMString;
+
+use std::cell::RefCell;
+
+#[deriving(Encodable)]
+pub struct CharacterData {
+ pub node: Node,
+ pub data: Traceable<RefCell<DOMString>>,
+}
+
+impl CharacterDataDerived for EventTarget {
+ fn is_characterdata(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(TextNodeTypeId) |
+ NodeTargetTypeId(CommentNodeTypeId) |
+ NodeTargetTypeId(ProcessingInstructionNodeTypeId) => true,
+ _ => false
+ }
+ }
+}
+
+impl CharacterData {
+ pub fn new_inherited(id: NodeTypeId, data: DOMString, document: &JSRef<Document>) -> CharacterData {
+ CharacterData {
+ node: Node::new_inherited(id, document),
+ data: Traceable::new(RefCell::new(data)),
+ }
+ }
+}
+
+impl<'a> CharacterDataMethods for JSRef<'a, CharacterData> {
+ fn Data(&self) -> DOMString {
+ self.data.deref().borrow().clone()
+ }
+
+ fn SetData(&self, arg: DOMString) -> ErrorResult {
+ *self.data.deref().borrow_mut() = arg;
+ Ok(())
+ }
+
+ fn Length(&self) -> u32 {
+ self.data.deref().borrow().len() as u32
+ }
+
+ fn SubstringData(&self, offset: u32, count: u32) -> Fallible<DOMString> {
+ Ok(self.data.deref().borrow().as_slice().slice(offset as uint, count as uint).to_string())
+ }
+
+ fn AppendData(&self, arg: DOMString) -> ErrorResult {
+ self.data.deref().borrow_mut().push_str(arg.as_slice());
+ Ok(())
+ }
+
+ fn InsertData(&self, offset: u32, arg: DOMString) -> ErrorResult {
+ self.ReplaceData(offset, 0, arg)
+ }
+
+ fn DeleteData(&self, offset: u32, count: u32) -> ErrorResult {
+ self.ReplaceData(offset, count, "".to_string())
+ }
+
+ fn ReplaceData(&self, offset: u32, count: u32, arg: DOMString) -> ErrorResult {
+ let length = self.data.deref().borrow().len() as u32;
+ if offset > length {
+ return Err(IndexSize);
+ }
+ let count = if offset + count > length {
+ length - offset
+ } else {
+ count
+ };
+ let mut data = self.data.deref().borrow().as_slice().slice(0, offset as uint).to_string();
+ data.push_str(arg.as_slice());
+ data.push_str(self.data.deref().borrow().as_slice().slice((offset + count) as uint, length as uint));
+ *self.data.deref().borrow_mut() = data;
+ // FIXME: Once we have `Range`, we should implement step7 to step11
+ Ok(())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-childnode-remove
+ fn Remove(&self) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.remove_self();
+ }
+}
+
+impl Reflectable for CharacterData {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.node.reflector()
+ }
+}
diff --git a/components/script/dom/comment.rs b/components/script/dom/comment.rs
new file mode 100644
index 00000000000..e50b24b2a58
--- /dev/null
+++ b/components/script/dom/comment.rs
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::CommentBinding;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::CommentDerived;
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::characterdata::CharacterData;
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::node::{CommentNodeTypeId, Node};
+use servo_util::str::DOMString;
+
+/// An HTML comment.
+#[deriving(Encodable)]
+pub struct Comment {
+ pub characterdata: CharacterData,
+}
+
+impl CommentDerived for EventTarget {
+ fn is_comment(&self) -> bool {
+ self.type_id == NodeTargetTypeId(CommentNodeTypeId)
+ }
+}
+
+impl Comment {
+ pub fn new_inherited(text: DOMString, document: &JSRef<Document>) -> Comment {
+ Comment {
+ characterdata: CharacterData::new_inherited(CommentNodeTypeId, text, document)
+ }
+ }
+
+ pub fn new(text: DOMString, document: &JSRef<Document>) -> Temporary<Comment> {
+ let node = Comment::new_inherited(text, document);
+ Node::reflect_node(box node, document, CommentBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef, data: DOMString) -> Fallible<Temporary<Comment>> {
+ let document = global.as_window().Document().root();
+ Ok(Comment::new(data, &*document))
+ }
+}
+
+pub trait CommentMethods {
+}
+
+impl Reflectable for Comment {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.characterdata.reflector()
+ }
+}
diff --git a/components/script/dom/console.rs b/components/script/dom/console.rs
new file mode 100644
index 00000000000..3e74617ebcf
--- /dev/null
+++ b/components/script/dom/console.rs
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::ConsoleBinding;
+use dom::bindings::codegen::Bindings::ConsoleBinding::ConsoleMethods;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct Console {
+ pub reflector_: Reflector
+}
+
+impl Console {
+ pub fn new_inherited() -> Console {
+ Console {
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(global: &GlobalRef) -> Temporary<Console> {
+ reflect_dom_object(box Console::new_inherited(), global, ConsoleBinding::Wrap)
+ }
+}
+
+impl<'a> ConsoleMethods for JSRef<'a, Console> {
+ fn Log(&self, message: DOMString) {
+ println!("{:s}", message);
+ }
+
+ fn Debug(&self, message: DOMString) {
+ println!("{:s}", message);
+ }
+
+ fn Info(&self, message: DOMString) {
+ println!("{:s}", message);
+ }
+
+ fn Warn(&self, message: DOMString) {
+ println!("{:s}", message);
+ }
+
+ fn Error(&self, message: DOMString) {
+ println!("{:s}", message);
+ }
+
+ fn Assert(&self, condition: bool, message: Option<DOMString>) {
+ if !condition {
+ let message = match message {
+ Some(ref message) => message.as_slice(),
+ None => "no message",
+ };
+ println!("Assertion failed: {:s}", message);
+ }
+ }
+}
+
+impl Reflectable for Console {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/customevent.rs b/components/script/dom/customevent.rs
new file mode 100644
index 00000000000..159601783ac
--- /dev/null
+++ b/components/script/dom/customevent.rs
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::CustomEventBinding;
+use dom::bindings::codegen::Bindings::CustomEventBinding::CustomEventMethods;
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::InheritTypes::{EventCast, CustomEventDerived};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::event::{Event, EventTypeId, CustomEventTypeId};
+use js::jsapi::JSContext;
+use js::jsval::{JSVal, NullValue};
+use servo_util::str::DOMString;
+
+use std::cell::Cell;
+
+#[deriving(Encodable)]
+pub struct CustomEvent {
+ event: Event,
+ detail: Traceable<Cell<Traceable<JSVal>>>,
+}
+
+impl CustomEventDerived for Event {
+ fn is_customevent(&self) -> bool {
+ self.type_id == CustomEventTypeId
+ }
+}
+
+impl CustomEvent {
+ pub fn new_inherited(type_id: EventTypeId) -> CustomEvent {
+ CustomEvent {
+ event: Event::new_inherited(type_id),
+ detail: Traceable::new(Cell::new(Traceable::new(NullValue()))),
+ }
+ }
+
+ pub fn new_uninitialized(global: &GlobalRef) -> Temporary<CustomEvent> {
+ reflect_dom_object(box CustomEvent::new_inherited(CustomEventTypeId),
+ global,
+ CustomEventBinding::Wrap)
+ }
+ pub fn new(global: &GlobalRef, type_: DOMString, bubbles: bool, cancelable: bool, detail: JSVal) -> Temporary<CustomEvent> {
+ let ev = CustomEvent::new_uninitialized(global).root();
+ ev.deref().InitCustomEvent(global.get_cx(), type_, bubbles, cancelable, detail);
+ Temporary::from_rooted(&*ev)
+ }
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &CustomEventBinding::CustomEventInit) -> Fallible<Temporary<CustomEvent>>{
+ Ok(CustomEvent::new(global, type_, init.parent.bubbles, init.parent.cancelable, init.detail))
+ }
+}
+
+impl<'a> CustomEventMethods for JSRef<'a, CustomEvent> {
+ fn Detail(&self, _cx: *mut JSContext) -> JSVal {
+ *self.detail.deref().get()
+ }
+
+ fn InitCustomEvent(&self,
+ _cx: *mut JSContext,
+ type_: DOMString,
+ can_bubble: bool,
+ cancelable: bool,
+ detail: JSVal) {
+ self.detail.deref().set(Traceable::new(detail));
+ let event: &JSRef<Event> = EventCast::from_ref(self);
+ event.InitEvent(type_, can_bubble, cancelable);
+ }
+}
+
+impl Reflectable for CustomEvent {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.event.reflector()
+ }
+}
diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs
new file mode 100644
index 00000000000..15bf075df44
--- /dev/null
+++ b/components/script/dom/dedicatedworkerglobalscope.rs
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
+use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::InheritTypes::DedicatedWorkerGlobalScopeDerived;
+use dom::bindings::codegen::InheritTypes::{EventTargetCast, WorkerGlobalScopeCast};
+use dom::bindings::global::Worker;
+use dom::bindings::js::{JSRef, Temporary, RootCollection};
+use dom::bindings::trace::Untraceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::eventtarget::{EventTarget, EventTargetHelpers};
+use dom::eventtarget::WorkerGlobalScopeTypeId;
+use dom::messageevent::MessageEvent;
+use dom::worker::{Worker, TrustedWorkerAddress};
+use dom::workerglobalscope::DedicatedGlobalScope;
+use dom::workerglobalscope::WorkerGlobalScope;
+use dom::xmlhttprequest::XMLHttpRequest;
+use script_task::{ScriptTask, ScriptChan};
+use script_task::{ScriptMsg, DOMMessage, XHRProgressMsg, WorkerRelease};
+use script_task::WorkerPostMessage;
+use script_task::StackRootTLS;
+
+use servo_net::resource_task::{ResourceTask, load_whole_resource};
+
+use js::glue::JS_STRUCTURED_CLONE_VERSION;
+use js::jsapi::{JSContext, JS_ReadStructuredClone, JS_WriteStructuredClone};
+use js::jsval::{JSVal, UndefinedValue};
+use js::rust::Cx;
+
+use std::rc::Rc;
+use std::ptr;
+use std::task::TaskBuilder;
+use native::task::NativeTaskBuilder;
+use url::Url;
+
+#[deriving(Encodable)]
+pub struct DedicatedWorkerGlobalScope {
+ workerglobalscope: WorkerGlobalScope,
+ receiver: Untraceable<Receiver<ScriptMsg>>,
+ /// Sender to the parent thread.
+ parent_sender: ScriptChan,
+ worker: Untraceable<TrustedWorkerAddress>,
+}
+
+impl DedicatedWorkerGlobalScope {
+ pub fn new_inherited(worker_url: Url,
+ worker: TrustedWorkerAddress,
+ cx: Rc<Cx>,
+ resource_task: ResourceTask,
+ parent_sender: ScriptChan,
+ own_sender: ScriptChan,
+ receiver: Receiver<ScriptMsg>)
+ -> DedicatedWorkerGlobalScope {
+ DedicatedWorkerGlobalScope {
+ workerglobalscope: WorkerGlobalScope::new_inherited(
+ DedicatedGlobalScope, worker_url, cx, resource_task,
+ own_sender),
+ receiver: Untraceable::new(receiver),
+ parent_sender: parent_sender,
+ worker: Untraceable::new(worker),
+ }
+ }
+
+ pub fn new(worker_url: Url,
+ worker: TrustedWorkerAddress,
+ cx: Rc<Cx>,
+ resource_task: ResourceTask,
+ parent_sender: ScriptChan,
+ own_sender: ScriptChan,
+ receiver: Receiver<ScriptMsg>)
+ -> Temporary<DedicatedWorkerGlobalScope> {
+ let scope = box DedicatedWorkerGlobalScope::new_inherited(
+ worker_url, worker, cx.clone(), resource_task, parent_sender,
+ own_sender, receiver);
+ DedicatedWorkerGlobalScopeBinding::Wrap(cx.ptr, scope)
+ }
+}
+
+impl DedicatedWorkerGlobalScope {
+ pub fn run_worker_scope(worker_url: Url,
+ worker: TrustedWorkerAddress,
+ resource_task: ResourceTask,
+ parent_sender: ScriptChan,
+ own_sender: ScriptChan,
+ receiver: Receiver<ScriptMsg>) {
+ TaskBuilder::new()
+ .native()
+ .named(format!("Web Worker at {}", worker_url.serialize()))
+ .spawn(proc() {
+ let roots = RootCollection::new();
+ let _stack_roots_tls = StackRootTLS::new(&roots);
+
+ let (url, source) = match load_whole_resource(&resource_task, worker_url.clone()) {
+ Err(_) => {
+ println!("error loading script {}", worker_url.serialize());
+ return;
+ }
+ Ok((metadata, bytes)) => {
+ (metadata.final_url, String::from_utf8(bytes).unwrap())
+ }
+ };
+
+ let (_js_runtime, js_context) = ScriptTask::new_rt_and_cx();
+ let global = DedicatedWorkerGlobalScope::new(
+ worker_url, worker, js_context.clone(), resource_task,
+ parent_sender, own_sender, receiver).root();
+ match js_context.evaluate_script(
+ global.reflector().get_jsobject(), source, url.serialize(), 1) {
+ Ok(_) => (),
+ Err(_) => println!("evaluate_script failed")
+ }
+ global.delayed_release_worker();
+
+ let scope: &JSRef<WorkerGlobalScope> =
+ WorkerGlobalScopeCast::from_ref(&*global);
+ let target: &JSRef<EventTarget> =
+ EventTargetCast::from_ref(&*global);
+ loop {
+ match global.receiver.recv_opt() {
+ Ok(DOMMessage(data, nbytes)) => {
+ let mut message = UndefinedValue();
+ unsafe {
+ assert!(JS_ReadStructuredClone(
+ js_context.ptr, data as *const u64, nbytes,
+ JS_STRUCTURED_CLONE_VERSION, &mut message,
+ ptr::null(), ptr::mut_null()) != 0);
+ }
+
+ MessageEvent::dispatch_jsval(target, &Worker(*scope), message);
+ global.delayed_release_worker();
+ },
+ Ok(XHRProgressMsg(addr, progress)) => {
+ XMLHttpRequest::handle_xhr_progress(addr, progress)
+ },
+ Ok(WorkerPostMessage(addr, data, nbytes)) => {
+ Worker::handle_message(addr, data, nbytes);
+ },
+ Ok(WorkerRelease(addr)) => {
+ Worker::handle_release(addr)
+ },
+ Ok(_) => fail!("Unexpected message"),
+ Err(_) => break,
+ }
+ }
+ });
+ }
+}
+
+impl<'a> DedicatedWorkerGlobalScopeMethods for JSRef<'a, DedicatedWorkerGlobalScope> {
+ fn PostMessage(&self, cx: *mut JSContext, message: JSVal) {
+ let mut data = ptr::mut_null();
+ let mut nbytes = 0;
+ unsafe {
+ assert!(JS_WriteStructuredClone(cx, message, &mut data, &mut nbytes,
+ ptr::null(), ptr::mut_null()) != 0);
+ }
+
+ let ScriptChan(ref sender) = self.parent_sender;
+ sender.send(WorkerPostMessage(*self.worker, data, nbytes));
+ }
+
+ fn GetOnmessage(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("message")
+ }
+
+ fn SetOnmessage(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("message", listener)
+ }
+}
+
+trait PrivateDedicatedWorkerGlobalScopeHelpers {
+ fn delayed_release_worker(&self);
+}
+
+impl<'a> PrivateDedicatedWorkerGlobalScopeHelpers for JSRef<'a, DedicatedWorkerGlobalScope> {
+ fn delayed_release_worker(&self) {
+ let ScriptChan(ref sender) = self.parent_sender;
+ sender.send(WorkerRelease(*self.worker));
+ }
+}
+
+impl Reflectable for DedicatedWorkerGlobalScope {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.workerglobalscope.reflector()
+ }
+}
+
+impl DedicatedWorkerGlobalScopeDerived for EventTarget {
+ fn is_dedicatedworkerglobalscope(&self) -> bool {
+ match self.type_id {
+ WorkerGlobalScopeTypeId(DedicatedGlobalScope) => true,
+ _ => false
+ }
+ }
+}
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
new file mode 100644
index 00000000000..503f618384d
--- /dev/null
+++ b/components/script/dom/document.rs
@@ -0,0 +1,855 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DocumentBinding;
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::{DocumentDerived, EventCast, HTMLElementCast};
+use dom::bindings::codegen::InheritTypes::{HTMLHeadElementCast, TextCast, ElementCast};
+use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, HTMLHtmlElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::codegen::InheritTypes::{HTMLAnchorElementDerived, HTMLAppletElementDerived};
+use dom::bindings::codegen::InheritTypes::{HTMLAreaElementDerived, HTMLEmbedElementDerived};
+use dom::bindings::codegen::InheritTypes::{HTMLFormElementDerived, HTMLImageElementDerived};
+use dom::bindings::codegen::InheritTypes::{HTMLScriptElementDerived};
+use dom::bindings::error::{ErrorResult, Fallible, NotSupported, InvalidCharacter};
+use dom::bindings::error::{HierarchyRequest, NamespaceError};
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable, TemporaryPushable};
+use dom::bindings::js::OptionalRootable;
+use dom::bindings::trace::{Traceable, Untraceable};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::bindings::utils::{xml_name_type, InvalidXMLName, Name, QName};
+use dom::comment::Comment;
+use dom::customevent::CustomEvent;
+use dom::documentfragment::DocumentFragment;
+use dom::documenttype::DocumentType;
+use dom::domimplementation::DOMImplementation;
+use dom::element::{Element, AttributeHandlers, get_attribute_parts};
+use dom::element::{HTMLHtmlElementTypeId, HTMLHeadElementTypeId, HTMLTitleElementTypeId};
+use dom::element::{HTMLBodyElementTypeId, HTMLFrameSetElementTypeId};
+use dom::event::Event;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers};
+use dom::htmlcollection::{HTMLCollection, CollectionFilter};
+use dom::htmlelement::HTMLElement;
+use dom::htmlheadelement::HTMLHeadElement;
+use dom::htmlhtmlelement::HTMLHtmlElement;
+use dom::htmltitleelement::HTMLTitleElement;
+use dom::location::Location;
+use dom::mouseevent::MouseEvent;
+use dom::node::{Node, ElementNodeTypeId, DocumentNodeTypeId, NodeHelpers};
+use dom::node::{CloneChildren, DoNotCloneChildren};
+use dom::nodelist::NodeList;
+use dom::text::Text;
+use dom::processinginstruction::ProcessingInstruction;
+use dom::range::Range;
+use dom::uievent::UIEvent;
+use dom::window::{Window, WindowHelpers};
+use html::hubbub_html_parser::build_element_from_tag;
+use hubbub::hubbub::{QuirksMode, NoQuirks, LimitedQuirks, FullQuirks};
+use layout_interface::{DocumentDamageLevel, ContentChangedDocumentDamage};
+use servo_util::namespace;
+use servo_util::namespace::{Namespace, Null};
+use servo_util::str::{DOMString, null_str_as_empty_ref, split_html_space_chars};
+
+use std::collections::hashmap::HashMap;
+use std::ascii::StrAsciiExt;
+use std::cell::{Cell, RefCell};
+use url::Url;
+use time;
+
+#[deriving(PartialEq,Encodable)]
+pub enum IsHTMLDocument {
+ HTMLDocument,
+ NonHTMLDocument,
+}
+
+#[deriving(Encodable)]
+pub struct Document {
+ pub node: Node,
+ reflector_: Reflector,
+ pub window: JS<Window>,
+ idmap: Traceable<RefCell<HashMap<DOMString, Vec<JS<Element>>>>>,
+ implementation: Cell<Option<JS<DOMImplementation>>>,
+ content_type: DOMString,
+ last_modified: Traceable<RefCell<Option<DOMString>>>,
+ pub encoding_name: Traceable<RefCell<DOMString>>,
+ pub is_html_document: bool,
+ url: Untraceable<Url>,
+ quirks_mode: Untraceable<Cell<QuirksMode>>,
+ images: Cell<Option<JS<HTMLCollection>>>,
+ embeds: Cell<Option<JS<HTMLCollection>>>,
+ links: Cell<Option<JS<HTMLCollection>>>,
+ forms: Cell<Option<JS<HTMLCollection>>>,
+ scripts: Cell<Option<JS<HTMLCollection>>>,
+ anchors: Cell<Option<JS<HTMLCollection>>>,
+ applets: Cell<Option<JS<HTMLCollection>>>,
+}
+
+impl DocumentDerived for EventTarget {
+ fn is_document(&self) -> bool {
+ self.type_id == NodeTargetTypeId(DocumentNodeTypeId)
+ }
+}
+
+struct ImagesFilter;
+impl CollectionFilter for ImagesFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlimageelement()
+ }
+}
+
+struct EmbedsFilter;
+impl CollectionFilter for EmbedsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlembedelement()
+ }
+}
+
+struct LinksFilter;
+impl CollectionFilter for LinksFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ (elem.is_htmlanchorelement() || elem.is_htmlareaelement()) && elem.has_attribute("href")
+ }
+}
+
+struct FormsFilter;
+impl CollectionFilter for FormsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlformelement()
+ }
+}
+
+struct ScriptsFilter;
+impl CollectionFilter for ScriptsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlscriptelement()
+ }
+}
+
+struct AnchorsFilter;
+impl CollectionFilter for AnchorsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlanchorelement() && elem.has_attribute("href")
+ }
+}
+
+struct AppletsFilter;
+impl CollectionFilter for AppletsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmlappletelement()
+ }
+}
+
+pub trait DocumentHelpers {
+ fn url<'a>(&'a self) -> &'a Url;
+ fn quirks_mode(&self) -> QuirksMode;
+ fn set_quirks_mode(&self, mode: QuirksMode);
+ fn set_last_modified(&self, value: DOMString);
+ fn set_encoding_name(&self, name: DOMString);
+ fn content_changed(&self);
+ fn damage_and_reflow(&self, damage: DocumentDamageLevel);
+ fn wait_until_safe_to_modify_dom(&self);
+ fn unregister_named_element(&self, to_unregister: &JSRef<Element>, id: DOMString);
+ fn register_named_element(&self, element: &JSRef<Element>, id: DOMString);
+ fn load_anchor_href(&self, href: DOMString);
+}
+
+impl<'a> DocumentHelpers for JSRef<'a, Document> {
+ fn url<'a>(&'a self) -> &'a Url {
+ &*self.url
+ }
+
+ fn quirks_mode(&self) -> QuirksMode {
+ self.quirks_mode.deref().get()
+ }
+
+ fn set_quirks_mode(&self, mode: QuirksMode) {
+ self.quirks_mode.deref().set(mode);
+ }
+
+ fn set_last_modified(&self, value: DOMString) {
+ *self.last_modified.deref().borrow_mut() = Some(value);
+ }
+
+ fn set_encoding_name(&self, name: DOMString) {
+ *self.encoding_name.deref().borrow_mut() = name;
+ }
+
+ fn content_changed(&self) {
+ self.damage_and_reflow(ContentChangedDocumentDamage);
+ }
+
+ fn damage_and_reflow(&self, damage: DocumentDamageLevel) {
+ self.window.root().damage_and_reflow(damage);
+ }
+
+ fn wait_until_safe_to_modify_dom(&self) {
+ self.window.root().wait_until_safe_to_modify_dom();
+ }
+
+
+ /// Remove any existing association between the provided id and any elements in this document.
+ fn unregister_named_element(&self,
+ to_unregister: &JSRef<Element>,
+ id: DOMString) {
+ let mut idmap = self.idmap.deref().borrow_mut();
+ let is_empty = match idmap.find_mut(&id) {
+ None => false,
+ Some(elements) => {
+ let position = elements.iter()
+ .map(|elem| elem.root())
+ .position(|element| &*element == to_unregister)
+ .expect("This element should be in registered.");
+ elements.remove(position);
+ elements.is_empty()
+ }
+ };
+ if is_empty {
+ idmap.remove(&id);
+ }
+ }
+
+ /// Associate an element present in this document with the provided id.
+ fn register_named_element(&self,
+ element: &JSRef<Element>,
+ id: DOMString) {
+ assert!({
+ let node: &JSRef<Node> = NodeCast::from_ref(element);
+ node.is_in_doc()
+ });
+ assert!(!id.is_empty());
+
+ let mut idmap = self.idmap.deref().borrow_mut();
+
+ // FIXME https://github.com/mozilla/rust/issues/13195
+ // Use mangle() when it exists again.
+ let root = self.GetDocumentElement().expect("The element is in the document, so there must be a document element.").root();
+ match idmap.find_mut(&id) {
+ Some(elements) => {
+ let new_node: &JSRef<Node> = NodeCast::from_ref(element);
+ let mut head : uint = 0u;
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ for node in root.traverse_preorder() {
+ let elem: Option<&JSRef<Element>> = ElementCast::to_ref(&node);
+ match elem {
+ Some(elem) => {
+ if &*(*elements)[head].root() == elem {
+ head = head + 1;
+ }
+ if new_node == &node || head == elements.len() {
+ break;
+ }
+ }
+ None => {}
+ }
+ }
+ elements.insert_unrooted(head, element);
+ return;
+ },
+ None => (),
+ }
+ let mut elements = vec!();
+ elements.push_unrooted(element);
+ idmap.insert(id, elements);
+ }
+
+ fn load_anchor_href(&self, href: DOMString) {
+ let window = self.window.root();
+ window.load_url(href);
+ }
+}
+
+impl Document {
+ pub fn new_inherited(window: &JSRef<Window>,
+ url: Option<Url>,
+ is_html_document: IsHTMLDocument,
+ content_type: Option<DOMString>) -> Document {
+ let url = url.unwrap_or_else(|| Url::parse("about:blank").unwrap());
+
+ Document {
+ node: Node::new_without_doc(DocumentNodeTypeId),
+ reflector_: Reflector::new(),
+ window: JS::from_rooted(window),
+ idmap: Traceable::new(RefCell::new(HashMap::new())),
+ implementation: Cell::new(None),
+ content_type: match content_type {
+ Some(string) => string.clone(),
+ None => match is_html_document {
+ // http://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
+ HTMLDocument => "text/html".to_string(),
+ // http://dom.spec.whatwg.org/#concept-document-content-type
+ NonHTMLDocument => "application/xml".to_string()
+ }
+ },
+ last_modified: Traceable::new(RefCell::new(None)),
+ url: Untraceable::new(url),
+ // http://dom.spec.whatwg.org/#concept-document-quirks
+ quirks_mode: Untraceable::new(Cell::new(NoQuirks)),
+ // http://dom.spec.whatwg.org/#concept-document-encoding
+ encoding_name: Traceable::new(RefCell::new("utf-8".to_string())),
+ is_html_document: is_html_document == HTMLDocument,
+ images: Cell::new(None),
+ embeds: Cell::new(None),
+ links: Cell::new(None),
+ forms: Cell::new(None),
+ scripts: Cell::new(None),
+ anchors: Cell::new(None),
+ applets: Cell::new(None),
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<Document>> {
+ Ok(Document::new(global.as_window(), None, NonHTMLDocument, None))
+ }
+
+ pub fn new(window: &JSRef<Window>, url: Option<Url>, doctype: IsHTMLDocument, content_type: Option<DOMString>) -> Temporary<Document> {
+ let document = Document::new_inherited(window, url, doctype, content_type);
+ let document = reflect_dom_object(box document, &Window(*window),
+ DocumentBinding::Wrap).root();
+
+ let node: &JSRef<Node> = NodeCast::from_ref(&*document);
+ node.set_owner_doc(&*document);
+ Temporary::from_rooted(&*document)
+ }
+}
+
+impl Reflectable for Document {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.node.reflector()
+ }
+}
+
+trait PrivateDocumentHelpers {
+ fn createNodeList(&self, callback: |node: &JSRef<Node>| -> bool) -> Temporary<NodeList>;
+ fn get_html_element(&self) -> Option<Temporary<HTMLHtmlElement>>;
+}
+
+impl<'a> PrivateDocumentHelpers for JSRef<'a, Document> {
+ fn createNodeList(&self, callback: |node: &JSRef<Node>| -> bool) -> Temporary<NodeList> {
+ let window = self.window.root();
+
+ match self.GetDocumentElement().root() {
+ None => {
+ NodeList::new_simple_list(&*window, vec!())
+ },
+ Some(root) => {
+ let mut nodes = vec!();
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ for child in root.traverse_preorder() {
+ if callback(&child) {
+ nodes.push(child);
+ }
+ }
+ NodeList::new_simple_list(&*window, nodes)
+ }
+ }
+
+ }
+
+ fn get_html_element(&self) -> Option<Temporary<HTMLHtmlElement>> {
+ self.GetDocumentElement().root().filtered(|root| {
+ let root: &JSRef<Node> = NodeCast::from_ref(&**root);
+ root.type_id() == ElementNodeTypeId(HTMLHtmlElementTypeId)
+ }).map(|elem| {
+ Temporary::from_rooted(HTMLHtmlElementCast::to_ref(&*elem).unwrap())
+ })
+ }
+}
+
+impl<'a> DocumentMethods for JSRef<'a, Document> {
+ // http://dom.spec.whatwg.org/#dom-document-implementation
+ fn Implementation(&self) -> Temporary<DOMImplementation> {
+ if self.implementation.get().is_none() {
+ self.implementation.assign(Some(DOMImplementation::new(self)));
+ }
+ Temporary::new(self.implementation.get().get_ref().clone())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-url
+ fn URL(&self) -> DOMString {
+ self.url().serialize()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-documenturi
+ fn DocumentURI(&self) -> DOMString {
+ self.URL()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-compatmode
+ fn CompatMode(&self) -> DOMString {
+ match self.quirks_mode.deref().get() {
+ LimitedQuirks | NoQuirks => "CSS1Compat".to_string(),
+ FullQuirks => "BackCompat".to_string()
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-characterset
+ fn CharacterSet(&self) -> DOMString {
+ self.encoding_name.deref().borrow().as_slice().to_ascii_lower()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-content_type
+ fn ContentType(&self) -> DOMString {
+ self.content_type.clone()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-doctype
+ fn GetDoctype(&self) -> Option<Temporary<DocumentType>> {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.children().find(|child| {
+ child.is_doctype()
+ }).map(|node| {
+ let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(&node).unwrap();
+ Temporary::from_rooted(doctype)
+ })
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-documentelement
+ fn GetDocumentElement(&self) -> Option<Temporary<Element>> {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.child_elements().next().map(|elem| Temporary::from_rooted(&elem))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-getelementsbytagname
+ fn GetElementsByTagName(&self, tag_name: DOMString) -> Temporary<HTMLCollection> {
+ let window = self.window.root();
+ HTMLCollection::by_tag_name(&*window, NodeCast::from_ref(self), tag_name)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-getelementsbytagnamens
+ fn GetElementsByTagNameNS(&self, maybe_ns: Option<DOMString>, tag_name: DOMString) -> Temporary<HTMLCollection> {
+ let window = self.window.root();
+ HTMLCollection::by_tag_name_ns(&*window, NodeCast::from_ref(self), tag_name, maybe_ns)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-getelementsbyclassname
+ fn GetElementsByClassName(&self, classes: DOMString) -> Temporary<HTMLCollection> {
+ let window = self.window.root();
+
+ HTMLCollection::by_class_name(&*window, NodeCast::from_ref(self), classes)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid
+ fn GetElementById(&self, id: DOMString) -> Option<Temporary<Element>> {
+ match self.idmap.deref().borrow().find_equiv(&id) {
+ None => None,
+ Some(ref elements) => Some(Temporary::new((*elements)[0].clone())),
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createelement
+ fn CreateElement(&self, local_name: DOMString) -> Fallible<Temporary<Element>> {
+ if xml_name_type(local_name.as_slice()) == InvalidXMLName {
+ debug!("Not a valid element name");
+ return Err(InvalidCharacter);
+ }
+ let local_name = local_name.as_slice().to_ascii_lower();
+ Ok(build_element_from_tag(local_name, namespace::HTML, self))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createelementns
+ fn CreateElementNS(&self,
+ namespace: Option<DOMString>,
+ qualified_name: DOMString) -> Fallible<Temporary<Element>> {
+ let ns = Namespace::from_str(null_str_as_empty_ref(&namespace));
+ match xml_name_type(qualified_name.as_slice()) {
+ InvalidXMLName => {
+ debug!("Not a valid element name");
+ return Err(InvalidCharacter);
+ },
+ Name => {
+ debug!("Not a valid qualified element name");
+ return Err(NamespaceError);
+ },
+ QName => {}
+ }
+
+ let (prefix_from_qname,
+ local_name_from_qname) = get_attribute_parts(qualified_name.as_slice());
+ match (&ns, prefix_from_qname.clone(), local_name_from_qname.as_slice()) {
+ // throw if prefix is not null and namespace is null
+ (&namespace::Null, Some(_), _) => {
+ debug!("Namespace can't be null with a non-null prefix");
+ return Err(NamespaceError);
+ },
+ // throw if prefix is "xml" and namespace is not the XML namespace
+ (_, Some(ref prefix), _) if "xml" == prefix.as_slice() && ns != namespace::XML => {
+ debug!("Namespace must be the xml namespace if the prefix is 'xml'");
+ return Err(NamespaceError);
+ },
+ // throw if namespace is the XMLNS namespace and neither qualifiedName nor prefix is "xmlns"
+ (&namespace::XMLNS, Some(ref prefix), _) if "xmlns" == prefix.as_slice() => {},
+ (&namespace::XMLNS, _, "xmlns") => {},
+ (&namespace::XMLNS, _, _) => {
+ debug!("The prefix or the qualified name must be 'xmlns' if namespace is the XMLNS namespace ");
+ return Err(NamespaceError);
+ },
+ _ => {}
+ }
+
+ if ns == namespace::HTML {
+ Ok(build_element_from_tag(local_name_from_qname.to_string(), ns, self))
+ } else {
+ Ok(Element::new(local_name_from_qname.to_string(), ns,
+ prefix_from_qname.map(|s| s.to_string()), self))
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createdocumentfragment
+ fn CreateDocumentFragment(&self) -> Temporary<DocumentFragment> {
+ DocumentFragment::new(self)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createtextnode
+ fn CreateTextNode(&self, data: DOMString)
+ -> Temporary<Text> {
+ Text::new(data, self)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createcomment
+ fn CreateComment(&self, data: DOMString) -> Temporary<Comment> {
+ Comment::new(data, self)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createprocessinginstruction
+ fn CreateProcessingInstruction(&self, target: DOMString,
+ data: DOMString) -> Fallible<Temporary<ProcessingInstruction>> {
+ // Step 1.
+ if xml_name_type(target.as_slice()) == InvalidXMLName {
+ return Err(InvalidCharacter);
+ }
+
+ // Step 2.
+ if data.as_slice().contains("?>") {
+ return Err(InvalidCharacter);
+ }
+
+ // Step 3.
+ Ok(ProcessingInstruction::new(target, data, self))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-importnode
+ fn ImportNode(&self, node: &JSRef<Node>, deep: bool) -> Fallible<Temporary<Node>> {
+ // Step 1.
+ if node.is_document() {
+ return Err(NotSupported);
+ }
+
+ // Step 2.
+ let clone_children = match deep {
+ true => CloneChildren,
+ false => DoNotCloneChildren
+ };
+
+ Ok(Node::clone(node, Some(self), clone_children))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-adoptnode
+ fn AdoptNode(&self, node: &JSRef<Node>) -> Fallible<Temporary<Node>> {
+ // Step 1.
+ if node.is_document() {
+ return Err(NotSupported);
+ }
+
+ // Step 2.
+ Node::adopt(node, self);
+
+ // Step 3.
+ Ok(Temporary::from_rooted(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createevent
+ fn CreateEvent(&self, interface: DOMString) -> Fallible<Temporary<Event>> {
+ let window = self.window.root();
+
+ match interface.as_slice().to_ascii_lower().as_slice() {
+ // FIXME: Implement CustomEvent (http://dom.spec.whatwg.org/#customevent)
+ "uievents" | "uievent" => Ok(EventCast::from_temporary(UIEvent::new_uninitialized(&*window))),
+ "mouseevents" | "mouseevent" => Ok(EventCast::from_temporary(MouseEvent::new_uninitialized(&*window))),
+ "customevent" => Ok(EventCast::from_temporary(CustomEvent::new_uninitialized(&Window(*window)))),
+ "htmlevents" | "events" | "event" => Ok(Event::new_uninitialized(&Window(*window))),
+ _ => Err(NotSupported)
+ }
+ }
+
+ // http://www.whatwg.org/html/#dom-document-lastmodified
+ fn LastModified(&self) -> DOMString {
+ match *self.last_modified.borrow() {
+ Some(ref t) => t.clone(),
+ None => time::now().strftime("%m/%d/%Y %H:%M:%S"),
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-document-createrange
+ fn CreateRange(&self) -> Temporary<Range> {
+ Range::new(self)
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#document.title
+ fn Title(&self) -> DOMString {
+ let mut title = String::new();
+ self.GetDocumentElement().root().map(|root| {
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ root.traverse_preorder()
+ .find(|node| node.type_id() == ElementNodeTypeId(HTMLTitleElementTypeId))
+ .map(|title_elem| {
+ for child in title_elem.children() {
+ if child.is_text() {
+ let text: &JSRef<Text> = TextCast::to_ref(&child).unwrap();
+ title.push_str(text.deref().characterdata.data.deref().borrow().as_slice());
+ }
+ }
+ });
+ });
+ let v: Vec<&str> = split_html_space_chars(title.as_slice()).collect();
+ v.connect(" ")
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#document.title
+ fn SetTitle(&self, title: DOMString) -> ErrorResult {
+ self.GetDocumentElement().root().map(|root| {
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ let head_node = root.traverse_preorder().find(|child| {
+ child.type_id() == ElementNodeTypeId(HTMLHeadElementTypeId)
+ });
+ head_node.map(|head| {
+ let title_node = head.children().find(|child| {
+ child.type_id() == ElementNodeTypeId(HTMLTitleElementTypeId)
+ });
+
+ match title_node {
+ Some(ref title_node) => {
+ for title_child in title_node.children() {
+ assert!(title_node.RemoveChild(&title_child).is_ok());
+ }
+ if !title.is_empty() {
+ let new_text = self.CreateTextNode(title.clone()).root();
+ assert!(title_node.AppendChild(NodeCast::from_ref(&*new_text)).is_ok());
+ }
+ },
+ None => {
+ let new_title = HTMLTitleElement::new("title".to_string(), self).root();
+ let new_title: &JSRef<Node> = NodeCast::from_ref(&*new_title);
+
+ if !title.is_empty() {
+ let new_text = self.CreateTextNode(title.clone()).root();
+ assert!(new_title.AppendChild(NodeCast::from_ref(&*new_text)).is_ok());
+ }
+ assert!(head.AppendChild(new_title).is_ok());
+ },
+ }
+ });
+ });
+ Ok(())
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-head
+ fn GetHead(&self) -> Option<Temporary<HTMLHeadElement>> {
+ self.get_html_element().and_then(|root| {
+ let root = root.root();
+ let node: &JSRef<Node> = NodeCast::from_ref(&*root);
+ node.children().find(|child| {
+ child.type_id() == ElementNodeTypeId(HTMLHeadElementTypeId)
+ }).map(|node| {
+ Temporary::from_rooted(HTMLHeadElementCast::to_ref(&node).unwrap())
+ })
+ })
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-body
+ fn GetBody(&self) -> Option<Temporary<HTMLElement>> {
+ self.get_html_element().and_then(|root| {
+ let root = root.root();
+ let node: &JSRef<Node> = NodeCast::from_ref(&*root);
+ node.children().find(|child| {
+ match child.type_id() {
+ ElementNodeTypeId(HTMLBodyElementTypeId) |
+ ElementNodeTypeId(HTMLFrameSetElementTypeId) => true,
+ _ => false
+ }
+ }).map(|node| {
+ Temporary::from_rooted(HTMLElementCast::to_ref(&node).unwrap())
+ })
+ })
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-body
+ fn SetBody(&self, new_body: Option<JSRef<HTMLElement>>) -> ErrorResult {
+ // Step 1.
+ match new_body {
+ Some(ref htmlelem) => {
+ let node: &JSRef<Node> = NodeCast::from_ref(htmlelem);
+ match node.type_id() {
+ ElementNodeTypeId(HTMLBodyElementTypeId) | ElementNodeTypeId(HTMLFrameSetElementTypeId) => {}
+ _ => return Err(HierarchyRequest)
+ }
+ }
+ None => return Err(HierarchyRequest)
+ }
+
+ // Step 2.
+ let old_body = self.GetBody().root();
+ //FIXME: covariant lifetime workaround. do not judge.
+ if old_body.as_ref().map(|body| body.deref()) == new_body.as_ref().map(|a| &*a) {
+ return Ok(());
+ }
+
+ // Step 3.
+ match self.get_html_element().root() {
+ // Step 4.
+ None => return Err(HierarchyRequest),
+ Some(ref root) => {
+ let new_body_unwrapped = new_body.unwrap();
+ let new_body: &JSRef<Node> = NodeCast::from_ref(&new_body_unwrapped);
+
+ let root: &JSRef<Node> = NodeCast::from_ref(&**root);
+ match old_body {
+ Some(ref child) => {
+ let child: &JSRef<Node> = NodeCast::from_ref(&**child);
+
+ assert!(root.ReplaceChild(new_body, child).is_ok())
+ }
+ None => assert!(root.AppendChild(new_body).is_ok())
+ };
+ }
+ }
+ Ok(())
+ }
+
+ // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-getelementsbyname
+ fn GetElementsByName(&self, name: DOMString) -> Temporary<NodeList> {
+ self.createNodeList(|node| {
+ if !node.is_element() {
+ return false;
+ }
+
+ let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ element.get_attribute(Null, "name").root().map_or(false, |attr| {
+ attr.value().as_slice() == name.as_slice()
+ })
+ })
+ }
+
+ fn Images(&self) -> Temporary<HTMLCollection> {
+ if self.images.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box ImagesFilter;
+ self.images.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.images.get().get_ref().clone())
+ }
+
+ fn Embeds(&self) -> Temporary<HTMLCollection> {
+ if self.embeds.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box EmbedsFilter;
+ self.embeds.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.embeds.get().get_ref().clone())
+ }
+
+ fn Plugins(&self) -> Temporary<HTMLCollection> {
+ self.Embeds()
+ }
+
+ fn Links(&self) -> Temporary<HTMLCollection> {
+ if self.links.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box LinksFilter;
+ self.links.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.links.get().get_ref().clone())
+ }
+
+ fn Forms(&self) -> Temporary<HTMLCollection> {
+ if self.forms.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box FormsFilter;
+ self.forms.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.forms.get().get_ref().clone())
+ }
+
+ fn Scripts(&self) -> Temporary<HTMLCollection> {
+ if self.scripts.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box ScriptsFilter;
+ self.scripts.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.scripts.get().get_ref().clone())
+ }
+
+ fn Anchors(&self) -> Temporary<HTMLCollection> {
+ if self.anchors.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box AnchorsFilter;
+ self.anchors.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.anchors.get().get_ref().clone())
+ }
+
+ fn Applets(&self) -> Temporary<HTMLCollection> {
+ // FIXME: This should be return OBJECT elements containing applets.
+ if self.applets.get().is_none() {
+ let window = self.window.root();
+ let root = NodeCast::from_ref(self);
+ let filter = box AppletsFilter;
+ self.applets.assign(Some(HTMLCollection::create(&*window, root, filter)));
+ }
+ Temporary::new(self.applets.get().get_ref().clone())
+ }
+
+ fn Location(&self) -> Temporary<Location> {
+ let window = self.window.root();
+ window.Location()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-children
+ fn Children(&self) -> Temporary<HTMLCollection> {
+ let window = self.window.root();
+ HTMLCollection::children(&*window, NodeCast::from_ref(self))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselector
+ fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector(selectors)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
+ fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector_all(selectors)
+ }
+
+ fn GetOnclick(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("click")
+ }
+
+ fn SetOnclick(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("click", listener)
+ }
+
+ fn GetOnload(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("load")
+ }
+
+ fn SetOnload(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("load", listener)
+ }
+}
diff --git a/components/script/dom/documentfragment.rs b/components/script/dom/documentfragment.rs
new file mode 100644
index 00000000000..1f3fcb29424
--- /dev/null
+++ b/components/script/dom/documentfragment.rs
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DocumentFragmentBinding;
+use dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::{DocumentFragmentDerived, NodeCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::Element;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlcollection::HTMLCollection;
+use dom::node::{DocumentFragmentNodeTypeId, Node, NodeHelpers, window_from_node};
+use dom::nodelist::NodeList;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct DocumentFragment {
+ pub node: Node,
+}
+
+impl DocumentFragmentDerived for EventTarget {
+ fn is_documentfragment(&self) -> bool {
+ self.type_id == NodeTargetTypeId(DocumentFragmentNodeTypeId)
+ }
+}
+
+impl DocumentFragment {
+ /// Creates a new DocumentFragment.
+ pub fn new_inherited(document: &JSRef<Document>) -> DocumentFragment {
+ DocumentFragment {
+ node: Node::new_inherited(DocumentFragmentNodeTypeId, document),
+ }
+ }
+
+ pub fn new(document: &JSRef<Document>) -> Temporary<DocumentFragment> {
+ let node = DocumentFragment::new_inherited(document);
+ Node::reflect_node(box node, document, DocumentFragmentBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<DocumentFragment>> {
+ let document = global.as_window().Document();
+ let document = document.root();
+
+ Ok(DocumentFragment::new(&document.root_ref()))
+ }
+}
+
+impl<'a> DocumentFragmentMethods for JSRef<'a, DocumentFragment> {
+ // http://dom.spec.whatwg.org/#dom-parentnode-children
+ fn Children(&self) -> Temporary<HTMLCollection> {
+ let window = window_from_node(self).root();
+ HTMLCollection::children(&window.root_ref(), NodeCast::from_ref(self))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselector
+ fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector(selectors)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
+ fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector_all(selectors)
+ }
+
+}
+
+impl Reflectable for DocumentFragment {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.node.reflector()
+ }
+}
diff --git a/components/script/dom/documenttype.rs b/components/script/dom/documenttype.rs
new file mode 100644
index 00000000000..5f101942139
--- /dev/null
+++ b/components/script/dom/documenttype.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DocumentTypeBinding;
+use dom::bindings::codegen::Bindings::DocumentTypeBinding::DocumentTypeMethods;
+use dom::bindings::codegen::InheritTypes::{DocumentTypeDerived, NodeCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::node::{Node, DoctypeNodeTypeId, NodeHelpers};
+use servo_util::str::DOMString;
+
+/// The `DOCTYPE` tag.
+#[deriving(Encodable)]
+pub struct DocumentType {
+ pub node: Node,
+ pub name: DOMString,
+ pub public_id: DOMString,
+ pub system_id: DOMString,
+}
+
+impl DocumentTypeDerived for EventTarget {
+ fn is_documenttype(&self) -> bool {
+ self.type_id == NodeTargetTypeId(DoctypeNodeTypeId)
+ }
+}
+
+impl DocumentType {
+ pub fn new_inherited(name: DOMString,
+ public_id: Option<DOMString>,
+ system_id: Option<DOMString>,
+ document: &JSRef<Document>)
+ -> DocumentType {
+ DocumentType {
+ node: Node::new_inherited(DoctypeNodeTypeId, document),
+ name: name,
+ public_id: public_id.unwrap_or("".to_string()),
+ system_id: system_id.unwrap_or("".to_string())
+ }
+ }
+
+ pub fn new(name: DOMString,
+ public_id: Option<DOMString>,
+ system_id: Option<DOMString>,
+ document: &JSRef<Document>)
+ -> Temporary<DocumentType> {
+ let documenttype = DocumentType::new_inherited(name,
+ public_id,
+ system_id,
+ document);
+ Node::reflect_node(box documenttype, document, DocumentTypeBinding::Wrap)
+ }
+}
+
+impl<'a> DocumentTypeMethods for JSRef<'a, DocumentType> {
+ fn Name(&self) -> DOMString {
+ self.name.clone()
+ }
+
+ fn PublicId(&self) -> DOMString {
+ self.public_id.clone()
+ }
+
+ fn SystemId(&self) -> DOMString {
+ self.system_id.clone()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-childnode-remove
+ fn Remove(&self) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.remove_self();
+ }
+}
+
+impl Reflectable for DocumentType {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.node.reflector()
+ }
+}
diff --git a/components/script/dom/domexception.rs b/components/script/dom/domexception.rs
new file mode 100644
index 00000000000..7d1ba33ffb8
--- /dev/null
+++ b/components/script/dom/domexception.rs
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DOMExceptionBinding;
+use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionConstants;
+use dom::bindings::codegen::Bindings::DOMExceptionBinding::DOMExceptionMethods;
+use dom::bindings::error;
+use dom::bindings::error::Error;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use servo_util::str::DOMString;
+
+#[repr(uint)]
+#[deriving(Show, Encodable)]
+pub enum DOMErrorName {
+ IndexSizeError = DOMExceptionConstants::INDEX_SIZE_ERR as uint,
+ HierarchyRequestError = DOMExceptionConstants::HIERARCHY_REQUEST_ERR as uint,
+ WrongDocumentError = DOMExceptionConstants::WRONG_DOCUMENT_ERR as uint,
+ InvalidCharacterError = DOMExceptionConstants::INVALID_CHARACTER_ERR as uint,
+ NoModificationAllowedError = DOMExceptionConstants::NO_MODIFICATION_ALLOWED_ERR as uint,
+ NotFoundError = DOMExceptionConstants::NOT_FOUND_ERR as uint,
+ NotSupportedError = DOMExceptionConstants::NOT_SUPPORTED_ERR as uint,
+ InvalidStateError = DOMExceptionConstants::INVALID_STATE_ERR as uint,
+ SyntaxError = DOMExceptionConstants::SYNTAX_ERR as uint,
+ InvalidModificationError = DOMExceptionConstants::INVALID_MODIFICATION_ERR as uint,
+ NamespaceError = DOMExceptionConstants::NAMESPACE_ERR as uint,
+ InvalidAccessError = DOMExceptionConstants::INVALID_ACCESS_ERR as uint,
+ SecurityError = DOMExceptionConstants::SECURITY_ERR as uint,
+ NetworkError = DOMExceptionConstants::NETWORK_ERR as uint,
+ AbortError = DOMExceptionConstants::ABORT_ERR as uint,
+ URLMismatchError = DOMExceptionConstants::URL_MISMATCH_ERR as uint,
+ QuotaExceededError = DOMExceptionConstants::QUOTA_EXCEEDED_ERR as uint,
+ TimeoutError = DOMExceptionConstants::TIMEOUT_ERR as uint,
+ InvalidNodeTypeError = DOMExceptionConstants::INVALID_NODE_TYPE_ERR as uint,
+ DataCloneError = DOMExceptionConstants::DATA_CLONE_ERR as uint,
+ EncodingError
+}
+
+impl DOMErrorName {
+ fn from_error(error: Error) -> DOMErrorName {
+ match error {
+ error::IndexSize => IndexSizeError,
+ error::NotFound => NotFoundError,
+ error::HierarchyRequest => HierarchyRequestError,
+ error::InvalidCharacter => InvalidCharacterError,
+ error::NotSupported => NotSupportedError,
+ error::InvalidState => InvalidStateError,
+ error::Syntax => SyntaxError,
+ error::NamespaceError => NamespaceError,
+ error::InvalidAccess => InvalidAccessError,
+ error::Security => SecurityError,
+ error::Network => NetworkError,
+ error::Abort => AbortError,
+ error::Timeout => TimeoutError,
+ error::FailureUnknown => fail!(),
+ }
+ }
+}
+
+#[deriving(Encodable)]
+pub struct DOMException {
+ pub code: DOMErrorName,
+ pub reflector_: Reflector
+}
+
+impl DOMException {
+ pub fn new_inherited(code: DOMErrorName) -> DOMException {
+ DOMException {
+ code: code,
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(global: &GlobalRef, code: DOMErrorName) -> Temporary<DOMException> {
+ reflect_dom_object(box DOMException::new_inherited(code), global, DOMExceptionBinding::Wrap)
+ }
+
+ pub fn new_from_error(global: &GlobalRef, code: Error) -> Temporary<DOMException> {
+ DOMException::new(global, DOMErrorName::from_error(code))
+ }
+}
+
+impl Reflectable for DOMException {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+impl<'a> DOMExceptionMethods for JSRef<'a, DOMException> {
+ // http://dom.spec.whatwg.org/#dom-domexception-code
+ fn Code(&self) -> u16 {
+ match self.code {
+ // http://dom.spec.whatwg.org/#concept-throw
+ EncodingError => 0,
+ code => code as u16
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#error-names-0
+ fn Name(&self) -> DOMString {
+ self.code.to_string()
+ }
+
+ // http://dom.spec.whatwg.org/#error-names-0
+ fn Message(&self) -> DOMString {
+ match self.code {
+ IndexSizeError => "The index is not in the allowed range.".to_string(),
+ HierarchyRequestError => "The operation would yield an incorrect node tree.".to_string(),
+ WrongDocumentError => "The object is in the wrong document.".to_string(),
+ InvalidCharacterError => "The string contains invalid characters.".to_string(),
+ NoModificationAllowedError => "The object can not be modified.".to_string(),
+ NotFoundError => "The object can not be found here.".to_string(),
+ NotSupportedError => "The operation is not supported.".to_string(),
+ InvalidStateError => "The object is in an invalid state.".to_string(),
+ SyntaxError => "The string did not match the expected pattern.".to_string(),
+ InvalidModificationError => "The object can not be modified in this way.".to_string(),
+ NamespaceError => "The operation is not allowed by Namespaces in XML.".to_string(),
+ InvalidAccessError => "The object does not support the operation or argument.".to_string(),
+ SecurityError => "The operation is insecure.".to_string(),
+ NetworkError => "A network error occurred.".to_string(),
+ AbortError => "The operation was aborted.".to_string(),
+ URLMismatchError => "The given URL does not match another URL.".to_string(),
+ QuotaExceededError => "The quota has been exceeded.".to_string(),
+ TimeoutError => "The operation timed out.".to_string(),
+ InvalidNodeTypeError => "The supplied node is incorrect or has an incorrect ancestor for this operation.".to_string(),
+ DataCloneError => "The object can not be cloned.".to_string(),
+ EncodingError => "The encoding operation (either encoded or decoding) failed.".to_string()
+ }
+ }
+}
diff --git a/components/script/dom/domimplementation.rs b/components/script/dom/domimplementation.rs
new file mode 100644
index 00000000000..0dbb842ebe4
--- /dev/null
+++ b/components/script/dom/domimplementation.rs
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::Bindings::DOMImplementationBinding;
+use dom::bindings::codegen::Bindings::DOMImplementationBinding::DOMImplementationMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::NodeCast;
+use dom::bindings::error::{Fallible, InvalidCharacter, NamespaceError};
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Root, Temporary, OptionalRootable};
+use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
+use dom::bindings::utils::{QName, Name, InvalidXMLName, xml_name_type};
+use dom::document::{Document, HTMLDocument, NonHTMLDocument};
+use dom::documenttype::DocumentType;
+use dom::htmlbodyelement::HTMLBodyElement;
+use dom::htmlheadelement::HTMLHeadElement;
+use dom::htmlhtmlelement::HTMLHtmlElement;
+use dom::htmltitleelement::HTMLTitleElement;
+use dom::node::Node;
+use dom::text::Text;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct DOMImplementation {
+ document: JS<Document>,
+ reflector_: Reflector,
+}
+
+impl DOMImplementation {
+ pub fn new_inherited(document: &JSRef<Document>) -> DOMImplementation {
+ DOMImplementation {
+ document: JS::from_rooted(document),
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(document: &JSRef<Document>) -> Temporary<DOMImplementation> {
+ let window = document.window.root();
+ reflect_dom_object(box DOMImplementation::new_inherited(document),
+ &Window(*window),
+ DOMImplementationBinding::Wrap)
+ }
+}
+
+impl Reflectable for DOMImplementation {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+// http://dom.spec.whatwg.org/#domimplementation
+impl<'a> DOMImplementationMethods for JSRef<'a, DOMImplementation> {
+ // http://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype
+ fn CreateDocumentType(&self, qname: DOMString, pubid: DOMString, sysid: DOMString) -> Fallible<Temporary<DocumentType>> {
+ match xml_name_type(qname.as_slice()) {
+ // Step 1.
+ InvalidXMLName => Err(InvalidCharacter),
+ // Step 2.
+ Name => Err(NamespaceError),
+ // Step 3.
+ QName => {
+ let document = self.document.root();
+ Ok(DocumentType::new(qname, Some(pubid), Some(sysid), &*document))
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-domimplementation-createdocument
+ fn CreateDocument(&self, namespace: Option<DOMString>, qname: DOMString,
+ maybe_doctype: Option<JSRef<DocumentType>>) -> Fallible<Temporary<Document>> {
+ let doc = self.document.root();
+ let win = doc.window.root();
+
+ // Step 1.
+ let doc = Document::new(&win.root_ref(), None, NonHTMLDocument, None).root();
+ // Step 2-3.
+ let maybe_elem = if qname.is_empty() {
+ None
+ } else {
+ match doc.CreateElementNS(namespace, qname) {
+ Err(error) => return Err(error),
+ Ok(elem) => Some(elem)
+ }
+ };
+
+ {
+ let doc_node: &JSRef<Node> = NodeCast::from_ref(&*doc);
+
+ // Step 4.
+ match maybe_doctype {
+ None => (),
+ Some(ref doctype) => {
+ let doc_type: &JSRef<Node> = NodeCast::from_ref(doctype);
+ assert!(doc_node.AppendChild(doc_type).is_ok())
+ }
+ }
+
+ // Step 5.
+ match maybe_elem.root() {
+ None => (),
+ Some(elem) => {
+ assert!(doc_node.AppendChild(NodeCast::from_ref(&*elem)).is_ok())
+ }
+ }
+ }
+
+ // Step 6.
+ // FIXME: https://github.com/mozilla/servo/issues/1522
+
+ // Step 7.
+ Ok(Temporary::from_rooted(&*doc))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-domimplementation-createhtmldocument
+ fn CreateHTMLDocument(&self, title: Option<DOMString>) -> Temporary<Document> {
+ let document = self.document.root();
+ let win = document.window.root();
+
+ // Step 1-2.
+ let doc = Document::new(&win.root_ref(), None, HTMLDocument, None).root();
+ let doc_node: &JSRef<Node> = NodeCast::from_ref(&*doc);
+
+ {
+ // Step 3.
+ let doc_type = DocumentType::new("html".to_string(), None, None, &*doc).root();
+ assert!(doc_node.AppendChild(NodeCast::from_ref(&*doc_type)).is_ok());
+ }
+
+ {
+ // Step 4.
+ let doc_html: Root<Node> = NodeCast::from_temporary(HTMLHtmlElement::new("html".to_string(), &*doc)).root();
+ let doc_html = doc_html.deref();
+ assert!(doc_node.AppendChild(doc_html).is_ok());
+
+ {
+ // Step 5.
+ let doc_head: Root<Node> = NodeCast::from_temporary(HTMLHeadElement::new("head".to_string(), &*doc)).root();
+ let doc_head = doc_head.deref();
+ assert!(doc_html.AppendChild(doc_head).is_ok());
+
+ // Step 6.
+ match title {
+ None => (),
+ Some(title_str) => {
+ // Step 6.1.
+ let doc_title: Root<Node> = NodeCast::from_temporary(HTMLTitleElement::new("title".to_string(), &*doc)).root();
+ let doc_title = doc_title.deref();
+ assert!(doc_head.AppendChild(doc_title).is_ok());
+
+ // Step 6.2.
+ let title_text: Root<Text> = Text::new(title_str, &*doc).root();
+ let title_text = title_text.deref();
+ assert!(doc_title.AppendChild(NodeCast::from_ref(title_text)).is_ok());
+ }
+ }
+ }
+
+ // Step 7.
+ let doc_body: Root<HTMLBodyElement> = HTMLBodyElement::new("body".to_string(), &*doc).root();
+ let doc_body = doc_body.deref();
+ assert!(doc_html.AppendChild(NodeCast::from_ref(doc_body)).is_ok());
+ }
+
+ // Step 8.
+ // FIXME: https://github.com/mozilla/servo/issues/1522
+
+ // Step 9.
+ Temporary::from_rooted(&*doc)
+ }
+}
diff --git a/components/script/dom/domparser.rs b/components/script/dom/domparser.rs
new file mode 100644
index 00000000000..65273cab756
--- /dev/null
+++ b/components/script/dom/domparser.rs
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DOMParserBinding;
+use dom::bindings::codegen::Bindings::DOMParserBinding::DOMParserMethods;
+use dom::bindings::codegen::Bindings::DOMParserBinding::SupportedTypeValues::{Text_html, Text_xml};
+use dom::bindings::error::{Fallible, FailureUnknown};
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
+use dom::document::{Document, HTMLDocument, NonHTMLDocument};
+use dom::window::Window;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct DOMParser {
+ window: JS<Window>, //XXXjdm Document instead?
+ reflector_: Reflector
+}
+
+impl DOMParser {
+ pub fn new_inherited(window: &JSRef<Window>) -> DOMParser {
+ DOMParser {
+ window: JS::from_rooted(window),
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<DOMParser> {
+ reflect_dom_object(box DOMParser::new_inherited(window), &Window(*window),
+ DOMParserBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<DOMParser>> {
+ Ok(DOMParser::new(global.as_window()))
+ }
+}
+
+impl<'a> DOMParserMethods for JSRef<'a, DOMParser> {
+ fn ParseFromString(&self,
+ _s: DOMString,
+ ty: DOMParserBinding::SupportedType)
+ -> Fallible<Temporary<Document>> {
+ let window = self.window.root();
+ match ty {
+ Text_html => {
+ Ok(Document::new(&window.root_ref(), None, HTMLDocument, Some("text/html".to_string())))
+ }
+ Text_xml => {
+ Ok(Document::new(&window.root_ref(), None, NonHTMLDocument, Some("text/xml".to_string())))
+ }
+ _ => {
+ Err(FailureUnknown)
+ }
+ }
+ }
+}
+
+impl Reflectable for DOMParser {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/domrect.rs b/components/script/dom/domrect.rs
new file mode 100644
index 00000000000..2cf75ee4eb9
--- /dev/null
+++ b/components/script/dom/domrect.rs
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DOMRectBinding;
+use dom::bindings::codegen::Bindings::DOMRectBinding::DOMRectMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+use servo_util::geometry::Au;
+
+#[deriving(Encodable)]
+pub struct DOMRect {
+ reflector_: Reflector,
+ top: f32,
+ bottom: f32,
+ left: f32,
+ right: f32,
+}
+
+impl DOMRect {
+ pub fn new_inherited(top: Au, bottom: Au,
+ left: Au, right: Au) -> DOMRect {
+ DOMRect {
+ top: top.to_nearest_px() as f32,
+ bottom: bottom.to_nearest_px() as f32,
+ left: left.to_nearest_px() as f32,
+ right: right.to_nearest_px() as f32,
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>,
+ top: Au, bottom: Au,
+ left: Au, right: Au) -> Temporary<DOMRect> {
+ let rect = DOMRect::new_inherited(top, bottom, left, right);
+ reflect_dom_object(box rect, &Window(*window), DOMRectBinding::Wrap)
+ }
+}
+
+impl<'a> DOMRectMethods for JSRef<'a, DOMRect> {
+ fn Top(&self) -> f32 {
+ self.top
+ }
+
+ fn Bottom(&self) -> f32 {
+ self.bottom
+ }
+
+ fn Left(&self) -> f32 {
+ self.left
+ }
+
+ fn Right(&self) -> f32 {
+ self.right
+ }
+
+ fn Width(&self) -> f32 {
+ (self.right - self.left).abs()
+ }
+
+ fn Height(&self) -> f32 {
+ (self.bottom - self.top).abs()
+ }
+}
+
+impl Reflectable for DOMRect {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/domrectlist.rs b/components/script/dom/domrectlist.rs
new file mode 100644
index 00000000000..0c661c4a51d
--- /dev/null
+++ b/components/script/dom/domrectlist.rs
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DOMRectListBinding;
+use dom::bindings::codegen::Bindings::DOMRectListBinding::DOMRectListMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::domrect::DOMRect;
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub struct DOMRectList {
+ reflector_: Reflector,
+ rects: Vec<JS<DOMRect>>,
+ window: JS<Window>,
+}
+
+impl DOMRectList {
+ pub fn new_inherited(window: &JSRef<Window>,
+ rects: Vec<JSRef<DOMRect>>) -> DOMRectList {
+ let rects = rects.iter().map(|rect| JS::from_rooted(rect)).collect();
+ DOMRectList {
+ reflector_: Reflector::new(),
+ rects: rects,
+ window: JS::from_rooted(window),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>,
+ rects: Vec<JSRef<DOMRect>>) -> Temporary<DOMRectList> {
+ reflect_dom_object(box DOMRectList::new_inherited(window, rects),
+ &Window(*window), DOMRectListBinding::Wrap)
+ }
+}
+
+impl<'a> DOMRectListMethods for JSRef<'a, DOMRectList> {
+ fn Length(&self) -> u32 {
+ self.rects.len() as u32
+ }
+
+ fn Item(&self, index: u32) -> Option<Temporary<DOMRect>> {
+ let rects = &self.rects;
+ if index < rects.len() as u32 {
+ Some(Temporary::new(rects[index as uint].clone()))
+ } else {
+ None
+ }
+ }
+
+ fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Temporary<DOMRect>> {
+ *found = index < self.rects.len() as u32;
+ self.Item(index)
+ }
+}
+
+impl Reflectable for DOMRectList {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/domtokenlist.rs b/components/script/dom/domtokenlist.rs
new file mode 100644
index 00000000000..11f7eaf59d0
--- /dev/null
+++ b/components/script/dom/domtokenlist.rs
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::attr::Attr;
+use dom::bindings::codegen::Bindings::DOMTokenListBinding;
+use dom::bindings::codegen::Bindings::DOMTokenListBinding::DOMTokenListMethods;
+use dom::bindings::error::{Fallible, InvalidCharacter, Syntax};
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable};
+use dom::bindings::utils::{Reflector, Reflectable, reflect_dom_object};
+use dom::element::{Element, AttributeHandlers};
+use dom::node::window_from_node;
+
+use servo_util::atom::Atom;
+use servo_util::namespace::Null;
+use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS};
+
+#[deriving(Encodable)]
+pub struct DOMTokenList {
+ reflector_: Reflector,
+ element: JS<Element>,
+ local_name: &'static str,
+}
+
+impl DOMTokenList {
+ pub fn new_inherited(element: &JSRef<Element>,
+ local_name: &'static str) -> DOMTokenList {
+ DOMTokenList {
+ reflector_: Reflector::new(),
+ element: JS::from_rooted(element),
+ local_name: local_name,
+ }
+ }
+
+ pub fn new(element: &JSRef<Element>,
+ local_name: &'static str) -> Temporary<DOMTokenList> {
+ let window = window_from_node(element).root();
+ reflect_dom_object(box DOMTokenList::new_inherited(element, local_name),
+ &Window(*window), DOMTokenListBinding::Wrap)
+ }
+}
+
+impl Reflectable for DOMTokenList {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+trait PrivateDOMTokenListHelpers {
+ fn attribute(&self) -> Option<Temporary<Attr>>;
+ fn check_token_exceptions<'a>(&self, token: &'a str) -> Fallible<&'a str>;
+}
+
+impl<'a> PrivateDOMTokenListHelpers for JSRef<'a, DOMTokenList> {
+ fn attribute(&self) -> Option<Temporary<Attr>> {
+ let element = self.element.root();
+ element.deref().get_attribute(Null, self.local_name)
+ }
+
+ fn check_token_exceptions<'a>(&self, token: &'a str) -> Fallible<&'a str> {
+ match token {
+ "" => Err(Syntax),
+ token if token.find(HTML_SPACE_CHARACTERS).is_some() => Err(InvalidCharacter),
+ token => Ok(token)
+ }
+ }
+}
+
+// http://dom.spec.whatwg.org/#domtokenlist
+impl<'a> DOMTokenListMethods for JSRef<'a, DOMTokenList> {
+ // http://dom.spec.whatwg.org/#dom-domtokenlist-length
+ fn Length(&self) -> u32 {
+ self.attribute().root().map(|attr| {
+ attr.value().tokens().map(|tokens| tokens.len()).unwrap_or(0)
+ }).unwrap_or(0) as u32
+ }
+
+ // http://dom.spec.whatwg.org/#dom-domtokenlist-item
+ fn Item(&self, index: u32) -> Option<DOMString> {
+ self.attribute().root().and_then(|attr| attr.value().tokens().and_then(|mut tokens| {
+ tokens.idx(index as uint).map(|token| token.as_slice().to_string())
+ }))
+ }
+
+ fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<DOMString> {
+ let item = self.Item(index);
+ *found = item.is_some();
+ item
+ }
+
+ // http://dom.spec.whatwg.org/#dom-domtokenlist-contains
+ fn Contains(&self, token: DOMString) -> Fallible<bool> {
+ self.check_token_exceptions(token.as_slice()).map(|slice| {
+ self.attribute().root().and_then(|attr| attr.value().tokens().map(|mut tokens| {
+ let atom = Atom::from_slice(slice);
+ tokens.any(|token| *token == atom)
+ })).unwrap_or(false)
+ })
+ }
+}
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
new file mode 100644
index 00000000000..1fe2c9f80bd
--- /dev/null
+++ b/components/script/dom/element.rs
@@ -0,0 +1,958 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Element nodes.
+
+use dom::attr::{Attr, ReplacedAttr, FirstSetAttr, AttrHelpersForLayout};
+use dom::attr::{AttrValue, StringAttrValue, UIntAttrValue, AtomAttrValue};
+use dom::namednodemap::NamedNodeMap;
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::ElementBinding;
+use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementDerived, NodeCast};
+use dom::bindings::js::{JS, JSRef, Temporary, TemporaryPushable};
+use dom::bindings::js::{OptionalSettable, OptionalRootable, Root};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::bindings::error::{ErrorResult, Fallible, NamespaceError, InvalidCharacter, Syntax};
+use dom::bindings::utils::{QName, Name, InvalidXMLName, xml_name_type};
+use dom::domrect::DOMRect;
+use dom::domrectlist::DOMRectList;
+use dom::document::{Document, DocumentHelpers};
+use dom::domtokenlist::DOMTokenList;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlcollection::HTMLCollection;
+use dom::htmlserializer::serialize;
+use dom::node::{ElementNodeTypeId, Node, NodeHelpers, NodeIterator, document_from_node};
+use dom::node::{window_from_node, LayoutNodeHelpers};
+use dom::nodelist::NodeList;
+use dom::virtualmethods::{VirtualMethods, vtable_for};
+use layout_interface::ContentChangedDocumentDamage;
+use layout_interface::MatchSelectorsDocumentDamage;
+use style::{matches, parse_selector_list_from_str};
+use style;
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::namespace::{Namespace, Null};
+use servo_util::str::{DOMString, null_str_as_empty_ref};
+
+use std::ascii::StrAsciiExt;
+use std::cell::{Cell, RefCell};
+use std::mem;
+
+#[deriving(Encodable)]
+pub struct Element {
+ pub node: Node,
+ pub local_name: Atom,
+ pub namespace: Namespace,
+ pub prefix: Option<DOMString>,
+ pub attrs: RefCell<Vec<JS<Attr>>>,
+ pub style_attribute: Traceable<RefCell<Option<style::PropertyDeclarationBlock>>>,
+ pub attr_list: Cell<Option<JS<NamedNodeMap>>>,
+ class_list: Cell<Option<JS<DOMTokenList>>>,
+}
+
+impl ElementDerived for EventTarget {
+ fn is_element(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(ElementNodeTypeId(_)) => true,
+ _ => false
+ }
+ }
+}
+
+impl Reflectable for Element {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.node.reflector()
+ }
+}
+
+#[deriving(PartialEq,Encodable)]
+pub enum ElementTypeId {
+ HTMLElementTypeId,
+ HTMLAnchorElementTypeId,
+ HTMLAppletElementTypeId,
+ HTMLAreaElementTypeId,
+ HTMLAudioElementTypeId,
+ HTMLBaseElementTypeId,
+ HTMLBRElementTypeId,
+ HTMLBodyElementTypeId,
+ HTMLButtonElementTypeId,
+ HTMLCanvasElementTypeId,
+ HTMLDataElementTypeId,
+ HTMLDataListElementTypeId,
+ HTMLDirectoryElementTypeId,
+ HTMLDListElementTypeId,
+ HTMLDivElementTypeId,
+ HTMLEmbedElementTypeId,
+ HTMLFieldSetElementTypeId,
+ HTMLFontElementTypeId,
+ HTMLFormElementTypeId,
+ HTMLFrameElementTypeId,
+ HTMLFrameSetElementTypeId,
+ HTMLHRElementTypeId,
+ HTMLHeadElementTypeId,
+ HTMLHeadingElementTypeId,
+ HTMLHtmlElementTypeId,
+ HTMLIFrameElementTypeId,
+ HTMLImageElementTypeId,
+ HTMLInputElementTypeId,
+ HTMLLabelElementTypeId,
+ HTMLLegendElementTypeId,
+ HTMLLinkElementTypeId,
+ HTMLLIElementTypeId,
+ HTMLMapElementTypeId,
+ HTMLMediaElementTypeId,
+ HTMLMetaElementTypeId,
+ HTMLMeterElementTypeId,
+ HTMLModElementTypeId,
+ HTMLObjectElementTypeId,
+ HTMLOListElementTypeId,
+ HTMLOptGroupElementTypeId,
+ HTMLOptionElementTypeId,
+ HTMLOutputElementTypeId,
+ HTMLParagraphElementTypeId,
+ HTMLParamElementTypeId,
+ HTMLPreElementTypeId,
+ HTMLProgressElementTypeId,
+ HTMLQuoteElementTypeId,
+ HTMLScriptElementTypeId,
+ HTMLSelectElementTypeId,
+ HTMLSourceElementTypeId,
+ HTMLSpanElementTypeId,
+ HTMLStyleElementTypeId,
+ HTMLTableElementTypeId,
+ HTMLTableCaptionElementTypeId,
+ HTMLTableDataCellElementTypeId,
+ HTMLTableHeaderCellElementTypeId,
+ HTMLTableColElementTypeId,
+ HTMLTableRowElementTypeId,
+ HTMLTableSectionElementTypeId,
+ HTMLTemplateElementTypeId,
+ HTMLTextAreaElementTypeId,
+ HTMLTimeElementTypeId,
+ HTMLTitleElementTypeId,
+ HTMLTrackElementTypeId,
+ HTMLUListElementTypeId,
+ HTMLVideoElementTypeId,
+ HTMLUnknownElementTypeId,
+
+ ElementTypeId,
+}
+
+//
+// Element methods
+//
+
+impl Element {
+ pub fn new_inherited(type_id: ElementTypeId, local_name: DOMString, namespace: Namespace, prefix: Option<DOMString>, document: &JSRef<Document>) -> Element {
+ Element {
+ node: Node::new_inherited(ElementNodeTypeId(type_id), document),
+ local_name: Atom::from_slice(local_name.as_slice()),
+ namespace: namespace,
+ prefix: prefix,
+ attrs: RefCell::new(vec!()),
+ attr_list: Cell::new(None),
+ class_list: Cell::new(None),
+ style_attribute: Traceable::new(RefCell::new(None)),
+ }
+ }
+
+ pub fn new(local_name: DOMString, namespace: Namespace, prefix: Option<DOMString>, document: &JSRef<Document>) -> Temporary<Element> {
+ let element = Element::new_inherited(ElementTypeId, local_name, namespace, prefix, document);
+ Node::reflect_node(box element, document, ElementBinding::Wrap)
+ }
+}
+
+pub trait RawLayoutElementHelpers {
+ unsafe fn get_attr_val_for_layout(&self, namespace: &Namespace, name: &str) -> Option<&'static str>;
+ unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &str) -> Option<Atom>;
+}
+
+impl RawLayoutElementHelpers for Element {
+ #[inline]
+ unsafe fn get_attr_val_for_layout(&self, namespace: &Namespace, name: &str)
+ -> Option<&'static str> {
+ // cast to point to T in RefCell<T> directly
+ let attrs: *const Vec<JS<Attr>> = mem::transmute(&self.attrs);
+ (*attrs).iter().find(|attr: & &JS<Attr>| {
+ let attr = attr.unsafe_get();
+ name == (*attr).local_name().as_slice() &&
+ (*attr).namespace == *namespace
+ }).map(|attr| {
+ let attr = attr.unsafe_get();
+ (*attr).value_ref_forever()
+ })
+ }
+
+ #[inline]
+ unsafe fn get_attr_atom_for_layout(&self, namespace: &Namespace, name: &str)
+ -> Option<Atom> {
+ // cast to point to T in RefCell<T> directly
+ let attrs: *const Vec<JS<Attr>> = mem::transmute(&self.attrs);
+ (*attrs).iter().find(|attr: & &JS<Attr>| {
+ let attr = attr.unsafe_get();
+ name == (*attr).local_name().as_slice() &&
+ (*attr).namespace == *namespace
+ }).and_then(|attr| {
+ let attr = attr.unsafe_get();
+ (*attr).value_atom_forever()
+ })
+ }
+}
+
+pub trait LayoutElementHelpers {
+ unsafe fn html_element_in_html_document_for_layout(&self) -> bool;
+}
+
+impl LayoutElementHelpers for JS<Element> {
+ unsafe fn html_element_in_html_document_for_layout(&self) -> bool {
+ if (*self.unsafe_get()).namespace != namespace::HTML {
+ return false
+ }
+ let node: JS<Node> = self.transmute_copy();
+ let owner_doc = node.owner_doc_for_layout().unsafe_get();
+ (*owner_doc).is_html_document
+ }
+}
+
+pub trait ElementHelpers {
+ fn html_element_in_html_document(&self) -> bool;
+ fn get_local_name<'a>(&'a self) -> &'a Atom;
+ fn get_namespace<'a>(&'a self) -> &'a Namespace;
+}
+
+impl<'a> ElementHelpers for JSRef<'a, Element> {
+ fn html_element_in_html_document(&self) -> bool {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ self.namespace == namespace::HTML && node.is_in_html_doc()
+ }
+
+ fn get_local_name<'a>(&'a self) -> &'a Atom {
+ &self.deref().local_name
+ }
+
+ fn get_namespace<'a>(&'a self) -> &'a Namespace {
+ &self.deref().namespace
+ }
+}
+
+pub trait AttributeHandlers {
+ fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<Temporary<Attr>>;
+ fn set_attribute_from_parser(&self, local_name: Atom,
+ value: DOMString, namespace: Namespace,
+ prefix: Option<DOMString>);
+ fn set_attribute(&self, name: &str, value: AttrValue);
+ fn do_set_attribute(&self, local_name: Atom, value: AttrValue,
+ name: Atom, namespace: Namespace,
+ prefix: Option<DOMString>, cb: |&JSRef<Attr>| -> bool);
+ fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom,
+ value: DOMString) -> AttrValue;
+
+ fn remove_attribute(&self, namespace: Namespace, name: &str);
+ fn notify_attribute_changed(&self, local_name: &Atom);
+ fn has_class(&self, name: &str) -> bool;
+
+ fn set_atomic_attribute(&self, name: &str, value: DOMString);
+
+ // http://www.whatwg.org/html/#reflecting-content-attributes-in-idl-attributes
+ fn has_attribute(&self, name: &str) -> bool;
+ fn set_bool_attribute(&self, name: &str, value: bool);
+ fn get_url_attribute(&self, name: &str) -> DOMString;
+ fn set_url_attribute(&self, name: &str, value: DOMString);
+ fn get_string_attribute(&self, name: &str) -> DOMString;
+ fn set_string_attribute(&self, name: &str, value: DOMString);
+ fn set_tokenlist_attribute(&self, name: &str, value: DOMString);
+ fn get_uint_attribute(&self, name: &str) -> u32;
+ fn set_uint_attribute(&self, name: &str, value: u32);
+}
+
+impl<'a> AttributeHandlers for JSRef<'a, Element> {
+ fn get_attribute(&self, namespace: Namespace, name: &str) -> Option<Temporary<Attr>> {
+ let element: &Element = self.deref();
+ let local_name = match self.html_element_in_html_document() {
+ true => Atom::from_slice(name.to_ascii_lower().as_slice()),
+ false => Atom::from_slice(name)
+ };
+ element.attrs.borrow().iter().map(|attr| attr.root()).find(|attr| {
+ *attr.local_name() == local_name && attr.namespace == namespace
+ }).map(|x| Temporary::from_rooted(&*x))
+ }
+
+ fn set_attribute_from_parser(&self, local_name: Atom,
+ value: DOMString, namespace: Namespace,
+ prefix: Option<DOMString>) {
+ let name = match prefix {
+ None => local_name.clone(),
+ Some(ref prefix) => {
+ let name = format!("{:s}:{:s}", *prefix, local_name.as_slice());
+ Atom::from_slice(name.as_slice())
+ },
+ };
+ let value = self.parse_attribute(&namespace, &local_name, value);
+ self.do_set_attribute(local_name, value, name, namespace, prefix, |_| false)
+ }
+
+ fn set_attribute(&self, name: &str, value: AttrValue) {
+ assert!(name == name.to_ascii_lower().as_slice());
+ assert!(!name.contains(":"));
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.wait_until_safe_to_modify_dom();
+
+ let name = Atom::from_slice(name);
+ self.do_set_attribute(name.clone(), value, name.clone(),
+ namespace::Null, None, |attr| *attr.local_name() == name);
+ }
+
+ fn do_set_attribute(&self, local_name: Atom, value: AttrValue,
+ name: Atom, namespace: Namespace,
+ prefix: Option<DOMString>, cb: |&JSRef<Attr>| -> bool) {
+ let idx = self.deref().attrs.borrow().iter()
+ .map(|attr| attr.root())
+ .position(|attr| cb(&*attr));
+ let (idx, set_type) = match idx {
+ Some(idx) => (idx, ReplacedAttr),
+ None => {
+ let window = window_from_node(self).root();
+ let attr = Attr::new(&*window, local_name, value.clone(),
+ name, namespace.clone(), prefix, self);
+ self.deref().attrs.borrow_mut().push_unrooted(&attr);
+ (self.deref().attrs.borrow().len() - 1, FirstSetAttr)
+ }
+ };
+
+ (*self.deref().attrs.borrow())[idx].root().set_value(set_type, value);
+ }
+
+ fn parse_attribute(&self, namespace: &Namespace, local_name: &Atom,
+ value: DOMString) -> AttrValue {
+ if *namespace == namespace::Null {
+ vtable_for(NodeCast::from_ref(self))
+ .parse_plain_attribute(local_name.as_slice(), value)
+ } else {
+ StringAttrValue(value)
+ }
+ }
+
+ fn remove_attribute(&self, namespace: Namespace, name: &str) {
+ let (_, local_name) = get_attribute_parts(name);
+ let local_name = Atom::from_slice(local_name);
+
+ let idx = self.deref().attrs.borrow().iter().map(|attr| attr.root()).position(|attr| {
+ *attr.local_name() == local_name
+ });
+
+ match idx {
+ None => (),
+ Some(idx) => {
+ {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.wait_until_safe_to_modify_dom();
+ }
+
+ if namespace == namespace::Null {
+ let removed_raw_value = (*self.deref().attrs.borrow())[idx].root().Value();
+ vtable_for(NodeCast::from_ref(self))
+ .before_remove_attr(&local_name,
+ removed_raw_value);
+ }
+
+ self.deref().attrs.borrow_mut().remove(idx);
+ }
+ };
+ }
+
+ fn notify_attribute_changed(&self, local_name: &Atom) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.is_in_doc() {
+ let damage = match local_name.as_slice() {
+ "style" | "id" | "class" => MatchSelectorsDocumentDamage,
+ _ => ContentChangedDocumentDamage
+ };
+ let document = node.owner_doc().root();
+ document.deref().damage_and_reflow(damage);
+ }
+ }
+
+ fn has_class(&self, name: &str) -> bool {
+ self.get_attribute(Null, "class").root().map(|attr| {
+ attr.deref().value().tokens().map(|mut tokens| {
+ tokens.any(|atom| atom.as_slice() == name)
+ }).unwrap_or(false)
+ }).unwrap_or(false)
+ }
+
+ fn set_atomic_attribute(&self, name: &str, value: DOMString) {
+ assert!(name == name.to_ascii_lower().as_slice());
+ let value = AttrValue::from_atomic(value);
+ self.set_attribute(name, value);
+ }
+
+ fn has_attribute(&self, name: &str) -> bool {
+ let name = match self.html_element_in_html_document() {
+ true => Atom::from_slice(name.to_ascii_lower().as_slice()),
+ false => Atom::from_slice(name)
+ };
+ self.deref().attrs.borrow().iter().map(|attr| attr.root()).any(|attr| {
+ *attr.local_name() == name && attr.namespace == Null
+ })
+ }
+
+ fn set_bool_attribute(&self, name: &str, value: bool) {
+ if self.has_attribute(name) == value { return; }
+ if value {
+ self.set_string_attribute(name, String::new());
+ } else {
+ self.remove_attribute(Null, name);
+ }
+ }
+
+ fn get_url_attribute(&self, name: &str) -> DOMString {
+ // XXX Resolve URL.
+ self.get_string_attribute(name)
+ }
+ fn set_url_attribute(&self, name: &str, value: DOMString) {
+ self.set_string_attribute(name, value);
+ }
+
+ fn get_string_attribute(&self, name: &str) -> DOMString {
+ match self.get_attribute(Null, name) {
+ Some(x) => {
+ let x = x.root();
+ x.deref().Value()
+ }
+ None => "".to_string()
+ }
+ }
+ fn set_string_attribute(&self, name: &str, value: DOMString) {
+ assert!(name == name.to_ascii_lower().as_slice());
+ self.set_attribute(name, StringAttrValue(value));
+ }
+
+ fn set_tokenlist_attribute(&self, name: &str, value: DOMString) {
+ assert!(name == name.to_ascii_lower().as_slice());
+ self.set_attribute(name, AttrValue::from_tokenlist(value));
+ }
+
+ fn get_uint_attribute(&self, name: &str) -> u32 {
+ assert!(name == name.to_ascii_lower().as_slice());
+ let attribute = self.get_attribute(Null, name).root();
+ match attribute {
+ Some(attribute) => {
+ match *attribute.deref().value() {
+ UIntAttrValue(_, value) => value,
+ _ => fail!("Expected a UIntAttrValue"),
+ }
+ }
+ None => 0,
+ }
+ }
+ fn set_uint_attribute(&self, name: &str, value: u32) {
+ assert!(name == name.to_ascii_lower().as_slice());
+ self.set_attribute(name, UIntAttrValue(value.to_string(), value));
+ }
+}
+
+impl Element {
+ pub fn is_void(&self) -> bool {
+ if self.namespace != namespace::HTML {
+ return false
+ }
+ match self.local_name.as_slice() {
+ /* List of void elements from
+ http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#html-fragment-serialization-algorithm */
+ "area" | "base" | "basefont" | "bgsound" | "br" | "col" | "embed" |
+ "frame" | "hr" | "img" | "input" | "keygen" | "link" | "menuitem" |
+ "meta" | "param" | "source" | "track" | "wbr" => true,
+ _ => false
+ }
+ }
+}
+
+impl<'a> ElementMethods for JSRef<'a, Element> {
+ // http://dom.spec.whatwg.org/#dom-element-namespaceuri
+ fn GetNamespaceURI(&self) -> Option<DOMString> {
+ match self.namespace {
+ Null => None,
+ ref ns => Some(ns.to_str().to_string())
+ }
+ }
+
+ fn LocalName(&self) -> DOMString {
+ self.local_name.as_slice().to_string()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-prefix
+ fn GetPrefix(&self) -> Option<DOMString> {
+ self.prefix.clone()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-tagname
+ fn TagName(&self) -> DOMString {
+ let qualified_name = match self.prefix {
+ Some(ref prefix) => format!("{}:{}", prefix, self.local_name).into_maybe_owned(),
+ None => self.local_name.as_slice().into_maybe_owned()
+ };
+ if self.html_element_in_html_document() {
+ qualified_name.as_slice().to_ascii_upper()
+ } else {
+ qualified_name.into_string()
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-id
+ fn Id(&self) -> DOMString {
+ self.get_string_attribute("id")
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-id
+ fn SetId(&self, id: DOMString) {
+ self.set_atomic_attribute("id", id);
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-classname
+ fn ClassName(&self) -> DOMString {
+ self.get_string_attribute("class")
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-classname
+ fn SetClassName(&self, class: DOMString) {
+ self.set_tokenlist_attribute("class", class);
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-classlist
+ fn ClassList(&self) -> Temporary<DOMTokenList> {
+ match self.class_list.get() {
+ Some(class_list) => Temporary::new(class_list),
+ None => {
+ let class_list = DOMTokenList::new(self, "class").root();
+ self.class_list.assign(Some(class_list.deref().clone()));
+ Temporary::from_rooted(&*class_list)
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-attributes
+ fn Attributes(&self) -> Temporary<NamedNodeMap> {
+ match self.attr_list.get() {
+ None => (),
+ Some(ref list) => return Temporary::new(list.clone()),
+ }
+
+ let doc = {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.owner_doc().root()
+ };
+ let window = doc.deref().window.root();
+ let list = NamedNodeMap::new(&*window, self);
+ self.attr_list.assign(Some(list));
+ Temporary::new(self.attr_list.get().get_ref().clone())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-getattribute
+ fn GetAttribute(&self, name: DOMString) -> Option<DOMString> {
+ let name = if self.html_element_in_html_document() {
+ name.as_slice().to_ascii_lower()
+ } else {
+ name
+ };
+ self.get_attribute(Null, name.as_slice()).root()
+ .map(|s| s.deref().Value())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-getattributens
+ fn GetAttributeNS(&self,
+ namespace: Option<DOMString>,
+ local_name: DOMString) -> Option<DOMString> {
+ let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace));
+ self.get_attribute(namespace, local_name.as_slice()).root()
+ .map(|attr| attr.deref().Value())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-setattribute
+ fn SetAttribute(&self,
+ name: DOMString,
+ value: DOMString) -> ErrorResult {
+ {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.wait_until_safe_to_modify_dom();
+ }
+
+ // Step 1.
+ match xml_name_type(name.as_slice()) {
+ InvalidXMLName => return Err(InvalidCharacter),
+ _ => {}
+ }
+
+ // Step 2.
+ let name = if self.html_element_in_html_document() {
+ name.as_slice().to_ascii_lower()
+ } else {
+ name
+ };
+
+ // Step 3-5.
+ let name = Atom::from_slice(name.as_slice());
+ let value = self.parse_attribute(&namespace::Null, &name, value);
+ self.do_set_attribute(name.clone(), value, name.clone(), namespace::Null, None, |attr| {
+ attr.deref().name.as_slice() == name.as_slice()
+ });
+ Ok(())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-setattributens
+ fn SetAttributeNS(&self,
+ namespace_url: Option<DOMString>,
+ name: DOMString,
+ value: DOMString) -> ErrorResult {
+ {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.wait_until_safe_to_modify_dom();
+ }
+
+ // Step 1.
+ let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace_url));
+
+ let name_type = xml_name_type(name.as_slice());
+ match name_type {
+ // Step 2.
+ InvalidXMLName => return Err(InvalidCharacter),
+ // Step 3.
+ Name => return Err(NamespaceError),
+ QName => {}
+ }
+
+ // Step 4.
+ let (prefix, local_name) = get_attribute_parts(name.as_slice());
+ match prefix {
+ Some(ref prefix_str) => {
+ // Step 5.
+ if namespace == namespace::Null {
+ return Err(NamespaceError);
+ }
+
+ // Step 6.
+ if "xml" == prefix_str.as_slice() && namespace != namespace::XML {
+ return Err(NamespaceError);
+ }
+
+ // Step 7b.
+ if "xmlns" == prefix_str.as_slice() && namespace != namespace::XMLNS {
+ return Err(NamespaceError);
+ }
+ },
+ None => {}
+ }
+
+ let name = Atom::from_slice(name.as_slice());
+ let local_name = Atom::from_slice(local_name);
+ let xmlns = Atom::from_slice("xmlns"); // TODO: Make this a static atom type
+
+ // Step 7a.
+ if xmlns == name && namespace != namespace::XMLNS {
+ return Err(NamespaceError);
+ }
+
+ // Step 8.
+ if namespace == namespace::XMLNS && xmlns != name && Some("xmlns") != prefix {
+ return Err(NamespaceError);
+ }
+
+ // Step 9.
+ let value = self.parse_attribute(&namespace, &local_name, value);
+ self.do_set_attribute(local_name.clone(), value, name,
+ namespace.clone(), prefix.map(|s| s.to_string()),
+ |attr| {
+ *attr.local_name() == local_name &&
+ attr.namespace == namespace
+ });
+ Ok(())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-removeattribute
+ fn RemoveAttribute(&self, name: DOMString) {
+ let name = if self.html_element_in_html_document() {
+ name.as_slice().to_ascii_lower()
+ } else {
+ name
+ };
+ self.remove_attribute(namespace::Null, name.as_slice())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-removeattributens
+ fn RemoveAttributeNS(&self,
+ namespace: Option<DOMString>,
+ localname: DOMString) {
+ let namespace = Namespace::from_str(null_str_as_empty_ref(&namespace));
+ self.remove_attribute(namespace, localname.as_slice())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-hasattribute
+ fn HasAttribute(&self,
+ name: DOMString) -> bool {
+ self.has_attribute(name.as_slice())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-hasattributens
+ fn HasAttributeNS(&self,
+ namespace: Option<DOMString>,
+ local_name: DOMString) -> bool {
+ self.GetAttributeNS(namespace, local_name).is_some()
+ }
+
+ fn GetElementsByTagName(&self, localname: DOMString) -> Temporary<HTMLCollection> {
+ let window = window_from_node(self).root();
+ HTMLCollection::by_tag_name(&*window, NodeCast::from_ref(self), localname)
+ }
+
+ fn GetElementsByTagNameNS(&self, maybe_ns: Option<DOMString>,
+ localname: DOMString) -> Temporary<HTMLCollection> {
+ let window = window_from_node(self).root();
+ HTMLCollection::by_tag_name_ns(&*window, NodeCast::from_ref(self), localname, maybe_ns)
+ }
+
+ fn GetElementsByClassName(&self, classes: DOMString) -> Temporary<HTMLCollection> {
+ let window = window_from_node(self).root();
+ HTMLCollection::by_class_name(&*window, NodeCast::from_ref(self), classes)
+ }
+
+ // http://dev.w3.org/csswg/cssom-view/#dom-element-getclientrects
+ fn GetClientRects(&self) -> Temporary<DOMRectList> {
+ let win = window_from_node(self).root();
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let rects = node.get_content_boxes();
+ let rects: Vec<Root<DOMRect>> = rects.iter().map(|r| {
+ DOMRect::new(
+ &*win,
+ r.origin.y,
+ r.origin.y + r.size.height,
+ r.origin.x,
+ r.origin.x + r.size.width).root()
+ }).collect();
+
+ DOMRectList::new(&*win, rects.iter().map(|rect| rect.deref().clone()).collect())
+ }
+
+ // http://dev.w3.org/csswg/cssom-view/#dom-element-getboundingclientrect
+ fn GetBoundingClientRect(&self) -> Temporary<DOMRect> {
+ let win = window_from_node(self).root();
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let rect = node.get_bounding_content_box();
+ DOMRect::new(
+ &*win,
+ rect.origin.y,
+ rect.origin.y + rect.size.height,
+ rect.origin.x,
+ rect.origin.x + rect.size.width)
+ }
+
+ fn GetInnerHTML(&self) -> Fallible<DOMString> {
+ //XXX TODO: XML case
+ Ok(serialize(&mut NodeIterator::new(NodeCast::from_ref(self), false, false)))
+ }
+
+ fn GetOuterHTML(&self) -> Fallible<DOMString> {
+ Ok(serialize(&mut NodeIterator::new(NodeCast::from_ref(self), true, false)))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-children
+ fn Children(&self) -> Temporary<HTMLCollection> {
+ let window = window_from_node(self).root();
+ HTMLCollection::children(&*window, NodeCast::from_ref(self))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselector
+ fn QuerySelector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector(selectors)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
+ fn QuerySelectorAll(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ root.query_selector_all(selectors)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-childnode-remove
+ fn Remove(&self) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.remove_self();
+ }
+
+ // http://dom.spec.whatwg.org/#dom-element-matches
+ fn Matches(&self, selectors: DOMString) -> Fallible<bool> {
+ match parse_selector_list_from_str(selectors.as_slice()) {
+ Err(()) => Err(Syntax),
+ Ok(ref selectors) => {
+ let root: &JSRef<Node> = NodeCast::from_ref(self);
+ Ok(matches(selectors, root))
+ }
+ }
+ }
+}
+
+pub fn get_attribute_parts<'a>(name: &'a str) -> (Option<&'a str>, &'a str) {
+ //FIXME: Throw for XML-invalid names
+ //FIXME: Throw for XMLNS-invalid names
+ let (prefix, local_name) = if name.contains(":") {
+ let mut parts = name.splitn(':', 1);
+ (Some(parts.next().unwrap()), parts.next().unwrap())
+ } else {
+ (None, name)
+ };
+
+ (prefix, local_name)
+}
+
+impl<'a> VirtualMethods for JSRef<'a, Element> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ Some(node as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ match name.as_slice() {
+ "style" => {
+ let doc = document_from_node(self).root();
+ let base_url = doc.deref().url().clone();
+ let style = Some(style::parse_style_attribute(value.as_slice(), &base_url));
+ *self.deref().style_attribute.deref().borrow_mut() = style;
+ }
+ "id" => {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.is_in_doc() && !value.is_empty() {
+ let doc = document_from_node(self).root();
+ doc.register_named_element(self, value.clone());
+ }
+ }
+ _ => ()
+ }
+
+ self.notify_attribute_changed(name);
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ match name.as_slice() {
+ "style" => {
+ *self.deref().style_attribute.deref().borrow_mut() = None;
+ }
+ "id" => {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.is_in_doc() && !value.is_empty() {
+ let doc = document_from_node(self).root();
+ doc.unregister_named_element(self, value);
+ }
+ }
+ _ => ()
+ }
+
+ self.notify_attribute_changed(name);
+ }
+
+ fn parse_plain_attribute(&self, name: &str, value: DOMString) -> AttrValue {
+ match name {
+ "id" => AttrValue::from_atomic(value),
+ "class" => AttrValue::from_tokenlist(value),
+ _ => self.super_type().unwrap().parse_plain_attribute(name, value),
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ if !tree_in_doc { return; }
+
+ match self.get_attribute(Null, "id").root() {
+ Some(attr) => {
+ let doc = document_from_node(self).root();
+ let value = attr.deref().Value();
+ if !value.is_empty() {
+ doc.deref().register_named_element(self, value);
+ }
+ }
+ _ => ()
+ }
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ if !tree_in_doc { return; }
+
+ match self.get_attribute(Null, "id").root() {
+ Some(attr) => {
+ let doc = document_from_node(self).root();
+ let value = attr.deref().Value();
+ if !value.is_empty() {
+ doc.deref().unregister_named_element(self, value);
+ }
+ }
+ _ => ()
+ }
+ }
+}
+
+impl<'a> style::TElement for JSRef<'a, Element> {
+ fn get_attr(&self, namespace: &Namespace, attr: &str) -> Option<&'static str> {
+ self.get_attribute(namespace.clone(), attr).root().map(|attr| {
+ unsafe { mem::transmute(attr.deref().value().as_slice()) }
+ })
+ }
+ fn get_link(&self) -> Option<&'static str> {
+ // FIXME: This is HTML only.
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match node.type_id() {
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/selectors.html#
+ // selector-link
+ ElementNodeTypeId(HTMLAnchorElementTypeId) |
+ ElementNodeTypeId(HTMLAreaElementTypeId) |
+ ElementNodeTypeId(HTMLLinkElementTypeId) => self.get_attr(&namespace::Null, "href"),
+ _ => None,
+ }
+ }
+ fn get_local_name<'a>(&'a self) -> &'a Atom {
+ (self as &ElementHelpers).get_local_name()
+ }
+ fn get_namespace<'a>(&'a self) -> &'a Namespace {
+ (self as &ElementHelpers).get_namespace()
+ }
+ fn get_hover_state(&self) -> bool {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.get_hover_state()
+ }
+ fn get_id<'a>(&self) -> Option<Atom> {
+ self.get_attribute(namespace::Null, "id").map(|attr| {
+ let attr = attr.root();
+ match *attr.value() {
+ AtomAttrValue(ref val) => val.clone(),
+ _ => fail!("`id` attribute should be AtomAttrValue"),
+ }
+ })
+ }
+ fn get_disabled_state(&self) -> bool {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.get_disabled_state()
+ }
+ fn get_enabled_state(&self) -> bool {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.get_enabled_state()
+ }
+}
diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs
new file mode 100644
index 00000000000..15709ad5bc1
--- /dev/null
+++ b/components/script/dom/event.rs
@@ -0,0 +1,174 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventBinding;
+use dom::bindings::codegen::Bindings::EventBinding::{EventConstants, EventMethods};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::eventtarget::EventTarget;
+use servo_util::str::DOMString;
+use std::cell::{Cell, RefCell};
+
+use time;
+
+#[deriving(Encodable)]
+pub enum EventPhase {
+ PhaseNone = EventConstants::NONE as int,
+ PhaseCapturing = EventConstants::CAPTURING_PHASE as int,
+ PhaseAtTarget = EventConstants::AT_TARGET as int,
+ PhaseBubbling = EventConstants::BUBBLING_PHASE as int,
+}
+
+#[deriving(PartialEq, Encodable)]
+pub enum EventTypeId {
+ CustomEventTypeId,
+ HTMLEventTypeId,
+ KeyEventTypeId,
+ MessageEventTypeId,
+ MouseEventTypeId,
+ ProgressEventTypeId,
+ UIEventTypeId
+}
+
+#[deriving(Encodable)]
+pub struct Event {
+ pub type_id: EventTypeId,
+ reflector_: Reflector,
+ pub current_target: Cell<Option<JS<EventTarget>>>,
+ pub target: Cell<Option<JS<EventTarget>>>,
+ type_: Traceable<RefCell<DOMString>>,
+ pub phase: Traceable<Cell<EventPhase>>,
+ pub canceled: Traceable<Cell<bool>>,
+ pub stop_propagation: Traceable<Cell<bool>>,
+ pub stop_immediate: Traceable<Cell<bool>>,
+ pub cancelable: Traceable<Cell<bool>>,
+ pub bubbles: Traceable<Cell<bool>>,
+ pub trusted: Traceable<Cell<bool>>,
+ pub dispatching: Traceable<Cell<bool>>,
+ pub initialized: Traceable<Cell<bool>>,
+ timestamp: u64,
+}
+
+impl Event {
+ pub fn new_inherited(type_id: EventTypeId) -> Event {
+ Event {
+ type_id: type_id,
+ reflector_: Reflector::new(),
+ current_target: Cell::new(None),
+ target: Cell::new(None),
+ phase: Traceable::new(Cell::new(PhaseNone)),
+ type_: Traceable::new(RefCell::new("".to_string())),
+ canceled: Traceable::new(Cell::new(false)),
+ cancelable: Traceable::new(Cell::new(true)),
+ bubbles: Traceable::new(Cell::new(false)),
+ trusted: Traceable::new(Cell::new(false)),
+ dispatching: Traceable::new(Cell::new(false)),
+ stop_propagation: Traceable::new(Cell::new(false)),
+ stop_immediate: Traceable::new(Cell::new(false)),
+ initialized: Traceable::new(Cell::new(false)),
+ timestamp: time::get_time().sec as u64,
+ }
+ }
+
+ pub fn new_uninitialized(global: &GlobalRef) -> Temporary<Event> {
+ reflect_dom_object(box Event::new_inherited(HTMLEventTypeId),
+ global,
+ EventBinding::Wrap)
+ }
+
+ pub fn new(global: &GlobalRef,
+ type_: DOMString,
+ can_bubble: bool,
+ cancelable: bool) -> Temporary<Event> {
+ let event = Event::new_uninitialized(global).root();
+ event.deref().InitEvent(type_, can_bubble, cancelable);
+ Temporary::from_rooted(&*event)
+ }
+
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &EventBinding::EventInit) -> Fallible<Temporary<Event>> {
+ Ok(Event::new(global, type_, init.bubbles, init.cancelable))
+ }
+}
+
+impl<'a> EventMethods for JSRef<'a, Event> {
+ fn EventPhase(&self) -> u16 {
+ self.phase.deref().get() as u16
+ }
+
+ fn Type(&self) -> DOMString {
+ self.type_.deref().borrow().clone()
+ }
+
+ fn GetTarget(&self) -> Option<Temporary<EventTarget>> {
+ self.target.get().as_ref().map(|target| Temporary::new(target.clone()))
+ }
+
+ fn GetCurrentTarget(&self) -> Option<Temporary<EventTarget>> {
+ self.current_target.get().as_ref().map(|target| Temporary::new(target.clone()))
+ }
+
+ fn DefaultPrevented(&self) -> bool {
+ self.canceled.deref().get()
+ }
+
+ fn PreventDefault(&self) {
+ if self.cancelable.deref().get() {
+ self.canceled.deref().set(true)
+ }
+ }
+
+ fn StopPropagation(&self) {
+ self.stop_propagation.deref().set(true);
+ }
+
+ fn StopImmediatePropagation(&self) {
+ self.stop_immediate.deref().set(true);
+ self.stop_propagation.deref().set(true);
+ }
+
+ fn Bubbles(&self) -> bool {
+ self.bubbles.deref().get()
+ }
+
+ fn Cancelable(&self) -> bool {
+ self.cancelable.deref().get()
+ }
+
+ fn TimeStamp(&self) -> u64 {
+ self.timestamp
+ }
+
+ fn InitEvent(&self,
+ type_: DOMString,
+ bubbles: bool,
+ cancelable: bool) {
+ self.initialized.deref().set(true);
+ if self.dispatching.deref().get() {
+ return;
+ }
+ self.stop_propagation.deref().set(false);
+ self.stop_immediate.deref().set(false);
+ self.canceled.deref().set(false);
+ self.trusted.deref().set(false);
+ self.target.set(None);
+ *self.type_.deref().borrow_mut() = type_;
+ self.bubbles.deref().set(bubbles);
+ self.cancelable.deref().set(cancelable);
+ }
+
+ fn IsTrusted(&self) -> bool {
+ self.trusted.deref().get()
+ }
+}
+
+impl Reflectable for Event {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/eventdispatcher.rs b/components/script/dom/eventdispatcher.rs
new file mode 100644
index 00000000000..f0648c13ce0
--- /dev/null
+++ b/components/script/dom/eventdispatcher.rs
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::callback::ReportExceptions;
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, NodeDerived};
+use dom::bindings::js::{JS, JSRef, OptionalSettable, OptionalRootable, Root};
+use dom::eventtarget::{Capturing, Bubbling, EventTarget};
+use dom::event::{Event, PhaseAtTarget, PhaseNone, PhaseBubbling, PhaseCapturing};
+use dom::node::{Node, NodeHelpers};
+use dom::virtualmethods::vtable_for;
+
+// See http://dom.spec.whatwg.org/#concept-event-dispatch for the full dispatch algorithm
+pub fn dispatch_event<'a, 'b>(target: &JSRef<'a, EventTarget>,
+ pseudo_target: Option<JSRef<'b, EventTarget>>,
+ event: &JSRef<Event>) -> bool {
+ assert!(!event.deref().dispatching.deref().get());
+
+ event.target.assign(Some(match pseudo_target {
+ Some(pseudo_target) => pseudo_target,
+ None => target.clone(),
+ }));
+ event.dispatching.deref().set(true);
+
+ let type_ = event.Type();
+
+ //TODO: no chain if not participating in a tree
+ let mut chain: Vec<Root<EventTarget>> = if target.deref().is_node() {
+ let target_node: &JSRef<Node> = NodeCast::to_ref(target).unwrap();
+ target_node.ancestors().map(|ancestor| {
+ let ancestor_target: &JSRef<EventTarget> = EventTargetCast::from_ref(&ancestor);
+ JS::from_rooted(ancestor_target).root()
+ }).collect()
+ } else {
+ vec!()
+ };
+
+ event.deref().phase.deref().set(PhaseCapturing);
+
+ //FIXME: The "callback this value" should be currentTarget
+
+ /* capturing */
+ for cur_target in chain.as_slice().iter().rev() {
+ let stopped = match cur_target.get_listeners_for(type_.as_slice(), Capturing) {
+ Some(listeners) => {
+ event.current_target.assign(Some(cur_target.deref().clone()));
+ for listener in listeners.iter() {
+ // Explicitly drop any exception on the floor.
+ let _ = listener.HandleEvent_(&**cur_target, event, ReportExceptions);
+
+ if event.deref().stop_immediate.deref().get() {
+ break;
+ }
+ }
+
+ event.deref().stop_propagation.deref().get()
+ }
+ None => false
+ };
+
+ if stopped {
+ break;
+ }
+ }
+
+ /* at target */
+ if !event.deref().stop_propagation.deref().get() {
+ event.phase.deref().set(PhaseAtTarget);
+ event.current_target.assign(Some(target.clone()));
+
+ let opt_listeners = target.deref().get_listeners(type_.as_slice());
+ for listeners in opt_listeners.iter() {
+ for listener in listeners.iter() {
+ // Explicitly drop any exception on the floor.
+ let _ = listener.HandleEvent_(target, event, ReportExceptions);
+
+ if event.deref().stop_immediate.deref().get() {
+ break;
+ }
+ }
+ }
+ }
+
+ /* bubbling */
+ if event.deref().bubbles.deref().get() && !event.deref().stop_propagation.deref().get() {
+ event.deref().phase.deref().set(PhaseBubbling);
+
+ for cur_target in chain.iter() {
+ let stopped = match cur_target.deref().get_listeners_for(type_.as_slice(), Bubbling) {
+ Some(listeners) => {
+ event.deref().current_target.assign(Some(cur_target.deref().clone()));
+ for listener in listeners.iter() {
+ // Explicitly drop any exception on the floor.
+ let _ = listener.HandleEvent_(&**cur_target, event, ReportExceptions);
+
+ if event.deref().stop_immediate.deref().get() {
+ break;
+ }
+ }
+
+ event.deref().stop_propagation.deref().get()
+ }
+ None => false
+ };
+ if stopped {
+ break;
+ }
+ }
+ }
+
+ /* default action */
+ let target = event.GetTarget().root();
+ match target {
+ Some(target) => {
+ let node: Option<&JSRef<Node>> = NodeCast::to_ref(&*target);
+ match node {
+ Some(node) => {
+ let vtable = vtable_for(node);
+ vtable.handle_event(event);
+ }
+ None => {}
+ }
+ }
+ None => {}
+ }
+
+ // Root ordering restrictions mean we need to unroot the chain entries
+ // in the same order they were rooted.
+ while chain.len() > 0 {
+ let _ = chain.pop();
+ }
+
+ event.dispatching.deref().set(false);
+ event.phase.deref().set(PhaseNone);
+ event.current_target.set(None);
+
+ !event.DefaultPrevented()
+}
diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs
new file mode 100644
index 00000000000..4f2cba18def
--- /dev/null
+++ b/components/script/dom/eventtarget.rs
@@ -0,0 +1,287 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::callback::CallbackContainer;
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::EventListenerBinding::EventListener;
+use dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods;
+use dom::bindings::error::{Fallible, InvalidState, report_pending_exception};
+use dom::bindings::js::JSRef;
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::event::Event;
+use dom::eventdispatcher::dispatch_event;
+use dom::node::NodeTypeId;
+use dom::workerglobalscope::WorkerGlobalScopeId;
+use dom::xmlhttprequest::XMLHttpRequestId;
+use dom::virtualmethods::VirtualMethods;
+use js::jsapi::{JS_CompileUCFunction, JS_GetFunctionObject, JS_CloneFunctionObject};
+use js::jsapi::{JSContext, JSObject};
+use servo_util::str::DOMString;
+use libc::{c_char, size_t};
+use std::cell::RefCell;
+use std::ptr;
+use url::Url;
+
+use std::collections::hashmap::HashMap;
+
+#[deriving(PartialEq,Encodable)]
+pub enum ListenerPhase {
+ Capturing,
+ Bubbling,
+}
+
+#[deriving(PartialEq,Encodable)]
+pub enum EventTargetTypeId {
+ NodeTargetTypeId(NodeTypeId),
+ WindowTypeId,
+ WorkerTypeId,
+ WorkerGlobalScopeTypeId(WorkerGlobalScopeId),
+ XMLHttpRequestTargetTypeId(XMLHttpRequestId)
+}
+
+#[deriving(PartialEq, Encodable)]
+pub enum EventListenerType {
+ Additive(EventListener),
+ Inline(EventListener),
+}
+
+impl EventListenerType {
+ fn get_listener(&self) -> EventListener {
+ match *self {
+ Additive(listener) | Inline(listener) => listener
+ }
+ }
+}
+
+#[deriving(PartialEq,Encodable)]
+pub struct EventListenerEntry {
+ pub phase: ListenerPhase,
+ pub listener: EventListenerType
+}
+
+#[deriving(Encodable)]
+pub struct EventTarget {
+ pub type_id: EventTargetTypeId,
+ reflector_: Reflector,
+ handlers: Traceable<RefCell<HashMap<DOMString, Vec<EventListenerEntry>>>>,
+}
+
+impl EventTarget {
+ pub fn new_inherited(type_id: EventTargetTypeId) -> EventTarget {
+ EventTarget {
+ type_id: type_id,
+ reflector_: Reflector::new(),
+ handlers: Traceable::new(RefCell::new(HashMap::new())),
+ }
+ }
+
+ pub fn get_listeners(&self, type_: &str) -> Option<Vec<EventListener>> {
+ self.handlers.deref().borrow().find_equiv(&type_).map(|listeners| {
+ listeners.iter().map(|entry| entry.listener.get_listener()).collect()
+ })
+ }
+
+ pub fn get_listeners_for(&self, type_: &str, desired_phase: ListenerPhase)
+ -> Option<Vec<EventListener>> {
+ self.handlers.deref().borrow().find_equiv(&type_).map(|listeners| {
+ let filtered = listeners.iter().filter(|entry| entry.phase == desired_phase);
+ filtered.map(|entry| entry.listener.get_listener()).collect()
+ })
+ }
+}
+
+pub trait EventTargetHelpers {
+ fn dispatch_event_with_target<'a>(&self,
+ target: Option<JSRef<'a, EventTarget>>,
+ event: &JSRef<Event>) -> Fallible<bool>;
+ fn set_inline_event_listener(&self,
+ ty: DOMString,
+ listener: Option<EventListener>);
+ fn get_inline_event_listener(&self, ty: DOMString) -> Option<EventListener>;
+ fn set_event_handler_uncompiled(&self,
+ cx: *mut JSContext,
+ url: Url,
+ scope: *mut JSObject,
+ ty: &str,
+ source: DOMString);
+ fn set_event_handler_common<T: CallbackContainer>(&self, ty: &str,
+ listener: Option<T>);
+ fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<T>;
+
+ fn has_handlers(&self) -> bool;
+}
+
+impl<'a> EventTargetHelpers for JSRef<'a, EventTarget> {
+ fn dispatch_event_with_target<'b>(&self,
+ target: Option<JSRef<'b, EventTarget>>,
+ event: &JSRef<Event>) -> Fallible<bool> {
+ if event.deref().dispatching.deref().get() || !event.deref().initialized.deref().get() {
+ return Err(InvalidState);
+ }
+ Ok(dispatch_event(self, target, event))
+ }
+
+ fn set_inline_event_listener(&self,
+ ty: DOMString,
+ listener: Option<EventListener>) {
+ let mut handlers = self.handlers.deref().borrow_mut();
+ let entries = handlers.find_or_insert_with(ty, |_| vec!());
+ let idx = entries.iter().position(|&entry| {
+ match entry.listener {
+ Inline(_) => true,
+ _ => false,
+ }
+ });
+
+ match idx {
+ Some(idx) => {
+ match listener {
+ Some(listener) => entries.get_mut(idx).listener = Inline(listener),
+ None => {
+ entries.remove(idx);
+ }
+ }
+ }
+ None => {
+ if listener.is_some() {
+ entries.push(EventListenerEntry {
+ phase: Bubbling,
+ listener: Inline(listener.unwrap()),
+ });
+ }
+ }
+ }
+ }
+
+ fn get_inline_event_listener(&self, ty: DOMString) -> Option<EventListener> {
+ let handlers = self.handlers.deref().borrow();
+ let entries = handlers.find(&ty);
+ entries.and_then(|entries| entries.iter().find(|entry| {
+ match entry.listener {
+ Inline(_) => true,
+ _ => false,
+ }
+ }).map(|entry| entry.listener.get_listener()))
+ }
+
+ fn set_event_handler_uncompiled(&self,
+ cx: *mut JSContext,
+ url: Url,
+ scope: *mut JSObject,
+ ty: &str,
+ source: DOMString) {
+ let url = url.serialize().to_c_str();
+ let name = ty.to_c_str();
+ let lineno = 0; //XXXjdm need to get a real number here
+
+ let nargs = 1; //XXXjdm not true for onerror
+ static arg_name: [c_char, ..6] =
+ ['e' as c_char, 'v' as c_char, 'e' as c_char, 'n' as c_char, 't' as c_char, 0];
+ static arg_names: [*const c_char, ..1] = [&arg_name as *const c_char];
+
+ let source: Vec<u16> = source.as_slice().utf16_units().collect();
+ let handler = unsafe {
+ JS_CompileUCFunction(cx,
+ ptr::mut_null(),
+ name.as_ptr(),
+ nargs,
+ &arg_names as *const *const i8 as *mut *const i8,
+ source.as_ptr(),
+ source.len() as size_t,
+ url.as_ptr(),
+ lineno)
+ };
+ if handler.is_null() {
+ report_pending_exception(cx, self.reflector().get_jsobject());
+ return;
+ }
+
+ let funobj = unsafe {
+ JS_CloneFunctionObject(cx, JS_GetFunctionObject(handler), scope)
+ };
+ assert!(funobj.is_not_null());
+ self.set_event_handler_common(ty, Some(EventHandlerNonNull::new(funobj)));
+ }
+
+ fn set_event_handler_common<T: CallbackContainer>(
+ &self, ty: &str, listener: Option<T>)
+ {
+ let event_listener = listener.map(|listener|
+ EventListener::new(listener.callback()));
+ self.set_inline_event_listener(ty.to_string(), event_listener);
+ }
+
+ fn get_event_handler_common<T: CallbackContainer>(&self, ty: &str) -> Option<T> {
+ let listener = self.get_inline_event_listener(ty.to_string());
+ listener.map(|listener| CallbackContainer::new(listener.parent.callback()))
+ }
+
+ fn has_handlers(&self) -> bool {
+ !self.handlers.deref().borrow().is_empty()
+ }
+}
+
+impl<'a> EventTargetMethods for JSRef<'a, EventTarget> {
+ fn AddEventListener(&self,
+ ty: DOMString,
+ listener: Option<EventListener>,
+ capture: bool) {
+ match listener {
+ Some(listener) => {
+ let mut handlers = self.handlers.deref().borrow_mut();
+ let entry = handlers.find_or_insert_with(ty, |_| vec!());
+ let phase = if capture { Capturing } else { Bubbling };
+ let new_entry = EventListenerEntry {
+ phase: phase,
+ listener: Additive(listener)
+ };
+ if entry.as_slice().position_elem(&new_entry).is_none() {
+ entry.push(new_entry);
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn RemoveEventListener(&self,
+ ty: DOMString,
+ listener: Option<EventListener>,
+ capture: bool) {
+ match listener {
+ Some(listener) => {
+ let mut handlers = self.handlers.deref().borrow_mut();
+ let mut entry = handlers.find_mut(&ty);
+ for entry in entry.mut_iter() {
+ let phase = if capture { Capturing } else { Bubbling };
+ let old_entry = EventListenerEntry {
+ phase: phase,
+ listener: Additive(listener)
+ };
+ let position = entry.as_slice().position_elem(&old_entry);
+ for &position in position.iter() {
+ entry.remove(position);
+ }
+ }
+ },
+ _ => (),
+ }
+ }
+
+ fn DispatchEvent(&self, event: &JSRef<Event>) -> Fallible<bool> {
+ self.dispatch_event_with_target(None, event)
+ }
+}
+
+impl Reflectable for EventTarget {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, EventTarget> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ None
+ }
+}
diff --git a/components/script/dom/file.rs b/components/script/dom/file.rs
new file mode 100644
index 00000000000..c4c07e03399
--- /dev/null
+++ b/components/script/dom/file.rs
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::FileBinding;
+use dom::bindings::codegen::Bindings::FileBinding::FileMethods;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::blob::{Blob, BlobType, FileTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct File {
+ pub blob: Blob,
+ pub name: DOMString,
+ pub type_: BlobType
+}
+
+impl File {
+ pub fn new_inherited(_file_bits: &JSRef<Blob>, name: DOMString) -> File {
+ File {
+ blob: Blob::new_inherited(),
+ name: name,
+ type_: FileTypeId
+ }
+ // XXXManishearth Once Blob is able to store data
+ // the relevant subfields of file_bits should be copied over
+ }
+
+ pub fn new(global: &GlobalRef, file_bits: &JSRef<Blob>, name: DOMString) -> Temporary<File> {
+ reflect_dom_object(box File::new_inherited(file_bits, name),
+ global,
+ FileBinding::Wrap)
+ }
+}
+
+impl FileMethods for File {
+ fn Name(&self) -> DOMString {
+ self.name.clone()
+ }
+}
+
+impl Reflectable for File {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.blob.reflector()
+ }
+}
diff --git a/components/script/dom/formdata.rs b/components/script/dom/formdata.rs
new file mode 100644
index 00000000000..0de17b83107
--- /dev/null
+++ b/components/script/dom/formdata.rs
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::FormDataBinding;
+use dom::bindings::codegen::Bindings::FormDataBinding::FormDataMethods;
+use dom::bindings::codegen::InheritTypes::FileCast;
+use dom::bindings::codegen::UnionTypes::FileOrString::{FileOrString, eFile, eString};
+use dom::bindings::error::{Fallible};
+use dom::bindings::global::{GlobalRef, GlobalField};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::blob::Blob;
+use dom::file::File;
+use dom::htmlformelement::HTMLFormElement;
+use servo_util::str::DOMString;
+use std::cell::RefCell;
+use std::collections::hashmap::HashMap;
+
+#[deriving(Encodable, Clone)]
+pub enum FormDatum {
+ StringData(DOMString),
+ FileData(JS<File>)
+}
+
+#[deriving(Encodable)]
+pub struct FormData {
+ data: Traceable<RefCell<HashMap<DOMString, Vec<FormDatum>>>>,
+ reflector_: Reflector,
+ global: GlobalField,
+ form: Option<JS<HTMLFormElement>>
+}
+
+impl FormData {
+ pub fn new_inherited(form: Option<JSRef<HTMLFormElement>>, global: &GlobalRef) -> FormData {
+ FormData {
+ data: Traceable::new(RefCell::new(HashMap::new())),
+ reflector_: Reflector::new(),
+ global: GlobalField::from_rooted(global),
+ form: form.map(|f| JS::from_rooted(&f)),
+ }
+ }
+
+ pub fn new(form: Option<JSRef<HTMLFormElement>>, global: &GlobalRef) -> Temporary<FormData> {
+ reflect_dom_object(box FormData::new_inherited(form, global),
+ global, FormDataBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef, form: Option<JSRef<HTMLFormElement>>) -> Fallible<Temporary<FormData>> {
+ Ok(FormData::new(form, global))
+ }
+}
+
+impl<'a> FormDataMethods for JSRef<'a, FormData> {
+ fn Append(&self, name: DOMString, value: &JSRef<Blob>, filename: Option<DOMString>) {
+ let file = FileData(JS::from_rooted(&self.get_file_from_blob(value, filename)));
+ self.data.deref().borrow_mut().insert_or_update_with(name.clone(), vec!(file.clone()),
+ |_k, v| {v.push(file.clone());});
+ }
+
+ fn Append_(&self, name: DOMString, value: DOMString) {
+ self.data.deref().borrow_mut().insert_or_update_with(name, vec!(StringData(value.clone())),
+ |_k, v| {v.push(StringData(value.clone()));});
+ }
+
+ fn Delete(&self, name: DOMString) {
+ self.data.deref().borrow_mut().remove(&name);
+ }
+
+ fn Get(&self, name: DOMString) -> Option<FileOrString> {
+ if self.data.deref().borrow().contains_key_equiv(&name) {
+ match self.data.deref().borrow().get(&name)[0].clone() {
+ StringData(ref s) => Some(eString(s.clone())),
+ FileData(ref f) => {
+ Some(eFile(f.clone()))
+ }
+ }
+ } else {
+ None
+ }
+ }
+
+ fn Has(&self, name: DOMString) -> bool {
+ self.data.deref().borrow().contains_key_equiv(&name)
+ }
+
+ fn Set(&self, name: DOMString, value: &JSRef<Blob>, filename: Option<DOMString>) {
+ let file = FileData(JS::from_rooted(&self.get_file_from_blob(value, filename)));
+ self.data.deref().borrow_mut().insert(name, vec!(file));
+ }
+
+ fn Set_(&self, name: DOMString, value: DOMString) {
+ self.data.deref().borrow_mut().insert(name, vec!(StringData(value)));
+ }
+}
+
+impl Reflectable for FormData {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+trait PrivateFormDataHelpers{
+ fn get_file_from_blob(&self, value: &JSRef<Blob>, filename: Option<DOMString>) -> Temporary<File>;
+}
+
+impl PrivateFormDataHelpers for FormData {
+ fn get_file_from_blob(&self, value: &JSRef<Blob>, filename: Option<DOMString>) -> Temporary<File> {
+ let global = self.global.root();
+ let f: Option<&JSRef<File>> = FileCast::to_ref(value);
+ let name = filename.unwrap_or(f.map(|inner| inner.name.clone()).unwrap_or("blob".to_string()));
+ File::new(&global.root_ref(), value, name)
+ }
+}
diff --git a/components/script/dom/htmlanchorelement.rs b/components/script/dom/htmlanchorelement.rs
new file mode 100644
index 00000000000..8c23abc940f
--- /dev/null
+++ b/components/script/dom/htmlanchorelement.rs
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding;
+use dom::bindings::codegen::Bindings::HTMLAnchorElementBinding::HTMLAnchorElementMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::HTMLAnchorElementDerived;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::{Document, DocumentHelpers};
+use dom::element::{Element, AttributeHandlers, HTMLAnchorElementTypeId};
+use dom::event::Event;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::namespace::Null;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLAnchorElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLAnchorElementDerived for EventTarget {
+ fn is_htmlanchorelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLAnchorElementTypeId))
+ }
+}
+
+impl HTMLAnchorElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLAnchorElement {
+ HTMLAnchorElement {
+ htmlelement: HTMLElement::new_inherited(HTMLAnchorElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLAnchorElement> {
+ let element = HTMLAnchorElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLAnchorElementBinding::Wrap)
+ }
+}
+
+trait PrivateHTMLAnchorElementHelpers {
+ fn handle_event_impl(&self, event: &JSRef<Event>);
+}
+
+impl<'a> PrivateHTMLAnchorElementHelpers for JSRef<'a, HTMLAnchorElement> {
+ fn handle_event_impl(&self, event: &JSRef<Event>) {
+ if "click" == event.Type().as_slice() && !event.DefaultPrevented() {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ let attr = element.get_attribute(Null, "href").root();
+ match attr {
+ Some(ref href) => {
+ let value = href.Value();
+ debug!("clicked on link to {:s}", value);
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let doc = node.owner_doc().root();
+ doc.load_anchor_href(value);
+ }
+ None => ()
+ }
+ }
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLAnchorElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(true),
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(false),
+ _ => ()
+ }
+ }
+
+ fn handle_event(&self, event: &JSRef<Event>) {
+ match self.super_type() {
+ Some(s) => {
+ s.handle_event(event);
+ }
+ None => {}
+ }
+ self.handle_event_impl(event);
+ }
+}
+
+impl Reflectable for HTMLAnchorElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
+
+impl<'a> HTMLAnchorElementMethods for JSRef<'a, HTMLAnchorElement> {
+ fn Text(&self) -> DOMString {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.GetTextContent().unwrap()
+ }
+
+ fn SetText(&self, value: DOMString) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.SetTextContent(Some(value))
+ }
+}
diff --git a/components/script/dom/htmlappletelement.rs b/components/script/dom/htmlappletelement.rs
new file mode 100644
index 00000000000..97a3757f02e
--- /dev/null
+++ b/components/script/dom/htmlappletelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLAppletElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLAppletElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLAppletElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLAppletElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLAppletElementDerived for EventTarget {
+ fn is_htmlappletelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLAppletElementTypeId))
+ }
+}
+
+impl HTMLAppletElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLAppletElement {
+ HTMLAppletElement {
+ htmlelement: HTMLElement::new_inherited(HTMLAppletElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLAppletElement> {
+ let element = HTMLAppletElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLAppletElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLAppletElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs
new file mode 100644
index 00000000000..3de80a21802
--- /dev/null
+++ b/components/script/dom/htmlareaelement.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLAreaElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLAreaElementDerived;
+use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLAreaElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLAreaElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLAreaElementDerived for EventTarget {
+ fn is_htmlareaelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLAreaElementTypeId))
+ }
+}
+
+impl HTMLAreaElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLAreaElement {
+ HTMLAreaElement {
+ htmlelement: HTMLElement::new_inherited(HTMLAreaElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLAreaElement> {
+ let element = HTMLAreaElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLAreaElementBinding::Wrap)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLAreaElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(true),
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(false),
+ _ => ()
+ }
+ }
+}
+
+impl Reflectable for HTMLAreaElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlaudioelement.rs b/components/script/dom/htmlaudioelement.rs
new file mode 100644
index 00000000000..626e2e99619
--- /dev/null
+++ b/components/script/dom/htmlaudioelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLAudioElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLAudioElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLAudioElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlmediaelement::HTMLMediaElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLAudioElement {
+ pub htmlmediaelement: HTMLMediaElement
+}
+
+impl HTMLAudioElementDerived for EventTarget {
+ fn is_htmlaudioelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLAudioElementTypeId))
+ }
+}
+
+impl HTMLAudioElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLAudioElement {
+ HTMLAudioElement {
+ htmlmediaelement: HTMLMediaElement::new_inherited(HTMLAudioElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLAudioElement> {
+ let element = HTMLAudioElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLAudioElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLAudioElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlmediaelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlbaseelement.rs b/components/script/dom/htmlbaseelement.rs
new file mode 100644
index 00000000000..fbd3d5cdfca
--- /dev/null
+++ b/components/script/dom/htmlbaseelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLBaseElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLBaseElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLBaseElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLBaseElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLBaseElementDerived for EventTarget {
+ fn is_htmlbaseelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLBaseElementTypeId))
+ }
+}
+
+impl HTMLBaseElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLBaseElement {
+ HTMLBaseElement {
+ htmlelement: HTMLElement::new_inherited(HTMLBaseElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLBaseElement> {
+ let element = HTMLBaseElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLBaseElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLBaseElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs
new file mode 100644
index 00000000000..b7bb35e618a
--- /dev/null
+++ b/components/script/dom/htmlbodyelement.rs
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::HTMLBodyElementBinding;
+use dom::bindings::codegen::Bindings::HTMLBodyElementBinding::HTMLBodyElementMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::codegen::InheritTypes::{HTMLBodyElementDerived, HTMLElementCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLBodyElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId, EventTargetHelpers};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLBodyElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLBodyElementDerived for EventTarget {
+ fn is_htmlbodyelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLBodyElementTypeId))
+ }
+}
+
+impl HTMLBodyElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLBodyElement {
+ HTMLBodyElement {
+ htmlelement: HTMLElement::new_inherited(HTMLBodyElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLBodyElement> {
+ let element = HTMLBodyElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLBodyElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLBodyElementMethods for JSRef<'a, HTMLBodyElement> {
+ fn GetOnunload(&self) -> Option<EventHandlerNonNull> {
+ let win = window_from_node(self).root();
+ win.deref().GetOnunload()
+ }
+
+ fn SetOnunload(&self, listener: Option<EventHandlerNonNull>) {
+ let win = window_from_node(self).root();
+ win.deref().SetOnunload(listener)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLBodyElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let element: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(element as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ if name.as_slice().starts_with("on") {
+ static forwarded_events: &'static [&'static str] =
+ &["onfocus", "onload", "onscroll", "onafterprint", "onbeforeprint",
+ "onbeforeunload", "onhashchange", "onlanguagechange", "onmessage",
+ "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate",
+ "onstorage", "onresize", "onunload", "onerror"];
+ let window = window_from_node(self).root();
+ let (cx, url, reflector) = (window.get_cx(),
+ window.get_url(),
+ window.reflector().get_jsobject());
+ let evtarget: &JSRef<EventTarget> =
+ if forwarded_events.iter().any(|&event| name.as_slice() == event) {
+ EventTargetCast::from_ref(&*window)
+ } else {
+ EventTargetCast::from_ref(self)
+ };
+ evtarget.set_event_handler_uncompiled(cx, url, reflector,
+ name.as_slice().slice_from(2),
+ value);
+ }
+ }
+}
+
+impl Reflectable for HTMLBodyElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlbrelement.rs b/components/script/dom/htmlbrelement.rs
new file mode 100644
index 00000000000..70c415ba24a
--- /dev/null
+++ b/components/script/dom/htmlbrelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLBRElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLBRElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLBRElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLBRElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLBRElementDerived for EventTarget {
+ fn is_htmlbrelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLBRElementTypeId))
+ }
+}
+
+impl HTMLBRElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLBRElement {
+ HTMLBRElement {
+ htmlelement: HTMLElement::new_inherited(HTMLBRElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLBRElement> {
+ let element = HTMLBRElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLBRElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLBRElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs
new file mode 100644
index 00000000000..0c8903603e1
--- /dev/null
+++ b/components/script/dom/htmlbuttonelement.rs
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLButtonElementBinding;
+use dom::bindings::codegen::Bindings::HTMLButtonElementBinding::HTMLButtonElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLButtonElementDerived, HTMLFieldSetElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLButtonElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node};
+use dom::validitystate::ValidityState;
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLButtonElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLButtonElementDerived for EventTarget {
+ fn is_htmlbuttonelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLButtonElementTypeId))
+ }
+}
+
+impl HTMLButtonElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLButtonElement {
+ HTMLButtonElement {
+ htmlelement: HTMLElement::new_inherited(HTMLButtonElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLButtonElement> {
+ let element = HTMLButtonElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLButtonElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLButtonElementMethods for JSRef<'a, HTMLButtonElement> {
+ fn Validity(&self) -> Temporary<ValidityState> {
+ let window = window_from_node(self).root();
+ ValidityState::new(&*window)
+ }
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLButtonElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ node.check_ancestors_disabled_state_for_form_control();
+ },
+ _ => ()
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.check_ancestors_disabled_state_for_form_control();
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) {
+ node.check_ancestors_disabled_state_for_form_control();
+ } else {
+ node.check_disabled_attribute();
+ }
+ }
+}
+
+impl Reflectable for HTMLButtonElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs
new file mode 100644
index 00000000000..28902265610
--- /dev/null
+++ b/components/script/dom/htmlcanvaselement.rs
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding;
+use dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElementMethods;
+use dom::bindings::codegen::InheritTypes::HTMLCanvasElementDerived;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::canvasrenderingcontext2d::CanvasRenderingContext2D;
+use dom::document::Document;
+use dom::element::{Element, HTMLCanvasElementTypeId, AttributeHandlers};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::{DOMString, parse_unsigned_integer};
+
+use geom::size::Size2D;
+
+use std::cell::Cell;
+
+static DefaultWidth: u32 = 300;
+static DefaultHeight: u32 = 150;
+
+#[deriving(Encodable)]
+pub struct HTMLCanvasElement {
+ pub htmlelement: HTMLElement,
+ context: Traceable<Cell<Option<JS<CanvasRenderingContext2D>>>>,
+ width: Traceable<Cell<u32>>,
+ height: Traceable<Cell<u32>>,
+}
+
+impl HTMLCanvasElementDerived for EventTarget {
+ fn is_htmlcanvaselement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLCanvasElementTypeId))
+ }
+}
+
+impl HTMLCanvasElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLCanvasElement {
+ HTMLCanvasElement {
+ htmlelement: HTMLElement::new_inherited(HTMLCanvasElementTypeId, localName, document),
+ context: Traceable::new(Cell::new(None)),
+ width: Traceable::new(Cell::new(DefaultWidth)),
+ height: Traceable::new(Cell::new(DefaultHeight)),
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLCanvasElement> {
+ let element = HTMLCanvasElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLCanvasElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLCanvasElementMethods for JSRef<'a, HTMLCanvasElement> {
+ fn Width(&self) -> u32 {
+ self.width.get()
+ }
+
+ fn SetWidth(&self, width: u32) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_uint_attribute("width", width)
+ }
+
+ fn Height(&self) -> u32 {
+ self.height.get()
+ }
+
+ fn SetHeight(&self, height: u32) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_uint_attribute("height", height)
+ }
+
+ fn GetContext(&self, id: DOMString) -> Option<Temporary<CanvasRenderingContext2D>> {
+ if id.as_slice() != "2d" {
+ return None;
+ }
+
+ if self.context.get().is_none() {
+ let window = window_from_node(self).root();
+ let (w, h) = (self.width.get() as i32, self.height.get() as i32);
+ let context = CanvasRenderingContext2D::new(&Window(*window), self, Size2D(w, h));
+ self.context.assign(Some(context));
+ }
+ self.context.get().map(|context| Temporary::new(context))
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLCanvasElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let element: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(element as &VirtualMethods)
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let recreate = match name.as_slice() {
+ "width" => {
+ self.width.set(DefaultWidth);
+ true
+ }
+ "height" => {
+ self.height.set(DefaultHeight);
+ true
+ }
+ _ => false,
+ };
+
+ if recreate {
+ let (w, h) = (self.width.get() as i32, self.height.get() as i32);
+ match self.context.get() {
+ Some(ref context) => context.root().recreate(Size2D(w, h)),
+ None => ()
+ }
+ }
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let recreate = match name.as_slice() {
+ "width" => {
+ self.width.set(parse_unsigned_integer(value.as_slice().chars()).unwrap_or(DefaultWidth));
+ true
+ }
+ "height" => {
+ self.height.set(parse_unsigned_integer(value.as_slice().chars()).unwrap_or(DefaultHeight));
+ true
+ }
+ _ => false,
+ };
+
+ if recreate {
+ let (w, h) = (self.width.get() as i32, self.height.get() as i32);
+ match self.context.get() {
+ Some(ref context) => context.root().recreate(Size2D(w, h)),
+ None => ()
+ }
+ }
+ }
+}
+
+impl Reflectable for HTMLCanvasElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlcollection.rs b/components/script/dom/htmlcollection.rs
new file mode 100644
index 00000000000..5bef6c56ff5
--- /dev/null
+++ b/components/script/dom/htmlcollection.rs
@@ -0,0 +1,257 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLCollectionBinding;
+use dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast};
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::element::{Element, AttributeHandlers, ElementHelpers};
+use dom::node::{Node, NodeHelpers};
+use dom::window::Window;
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::namespace::Namespace;
+use servo_util::str::{DOMString, split_html_space_chars};
+
+use serialize::{Encoder, Encodable};
+use std::ascii::StrAsciiExt;
+
+pub trait CollectionFilter {
+ fn filter(&self, elem: &JSRef<Element>, root: &JSRef<Node>) -> bool;
+}
+
+impl<S: Encoder<E>, E> Encodable<S, E> for Box<CollectionFilter> {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+#[deriving(Encodable)]
+pub enum CollectionTypeId {
+ Static(Vec<JS<Element>>),
+ Live(JS<Node>, Box<CollectionFilter>)
+}
+
+#[deriving(Encodable)]
+pub struct HTMLCollection {
+ collection: CollectionTypeId,
+ reflector_: Reflector,
+}
+
+impl HTMLCollection {
+ pub fn new_inherited(collection: CollectionTypeId) -> HTMLCollection {
+ HTMLCollection {
+ collection: collection,
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>, collection: CollectionTypeId) -> Temporary<HTMLCollection> {
+ reflect_dom_object(box HTMLCollection::new_inherited(collection),
+ &Window(*window), HTMLCollectionBinding::Wrap)
+ }
+}
+
+impl HTMLCollection {
+ pub fn create(window: &JSRef<Window>, root: &JSRef<Node>,
+ filter: Box<CollectionFilter>) -> Temporary<HTMLCollection> {
+ HTMLCollection::new(window, Live(JS::from_rooted(root), filter))
+ }
+
+ fn all_elements(window: &JSRef<Window>, root: &JSRef<Node>,
+ namespace_filter: Option<Namespace>) -> Temporary<HTMLCollection> {
+ struct AllElementFilter {
+ namespace_filter: Option<Namespace>
+ }
+ impl CollectionFilter for AllElementFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ match self.namespace_filter {
+ None => true,
+ Some(ref namespace) => elem.namespace == *namespace
+ }
+ }
+ }
+ let filter = AllElementFilter {namespace_filter: namespace_filter};
+ HTMLCollection::create(window, root, box filter)
+ }
+
+ pub fn by_tag_name(window: &JSRef<Window>, root: &JSRef<Node>, tag: DOMString)
+ -> Temporary<HTMLCollection> {
+ if tag.as_slice() == "*" {
+ return HTMLCollection::all_elements(window, root, None);
+ }
+
+ struct TagNameFilter {
+ tag: Atom,
+ ascii_lower_tag: Atom,
+ }
+ impl CollectionFilter for TagNameFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ if elem.html_element_in_html_document() {
+ elem.local_name == self.ascii_lower_tag
+ } else {
+ elem.local_name == self.tag
+ }
+ }
+ }
+ let filter = TagNameFilter {
+ tag: Atom::from_slice(tag.as_slice()),
+ ascii_lower_tag: Atom::from_slice(tag.as_slice().to_ascii_lower().as_slice()),
+ };
+ HTMLCollection::create(window, root, box filter)
+ }
+
+ pub fn by_tag_name_ns(window: &JSRef<Window>, root: &JSRef<Node>, tag: DOMString,
+ maybe_ns: Option<DOMString>) -> Temporary<HTMLCollection> {
+ let namespace_filter = match maybe_ns {
+ Some(namespace) => {
+ match namespace.as_slice() {
+ "*" => None,
+ ns => Some(Namespace::from_str(ns)),
+ }
+ },
+ None => Some(namespace::Null),
+ };
+
+ if tag.as_slice() == "*" {
+ return HTMLCollection::all_elements(window, root, namespace_filter);
+ }
+ struct TagNameNSFilter {
+ tag: Atom,
+ namespace_filter: Option<Namespace>
+ }
+ impl CollectionFilter for TagNameNSFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ let ns_match = match self.namespace_filter {
+ Some(ref namespace) => {
+ elem.deref().namespace == *namespace
+ },
+ None => true
+ };
+ ns_match && elem.deref().local_name == self.tag
+ }
+ }
+ let filter = TagNameNSFilter {
+ tag: Atom::from_slice(tag.as_slice()),
+ namespace_filter: namespace_filter
+ };
+ HTMLCollection::create(window, root, box filter)
+ }
+
+ pub fn by_class_name(window: &JSRef<Window>, root: &JSRef<Node>, classes: DOMString)
+ -> Temporary<HTMLCollection> {
+ struct ClassNameFilter {
+ classes: Vec<DOMString>
+ }
+ impl CollectionFilter for ClassNameFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ self.classes.iter().all(|class| elem.has_class(class.as_slice()))
+ }
+ }
+ let filter = ClassNameFilter {
+ classes: split_html_space_chars(classes.as_slice()).map(|class| class.to_string()).collect()
+ };
+ HTMLCollection::create(window, root, box filter)
+ }
+
+ pub fn children(window: &JSRef<Window>, root: &JSRef<Node>) -> Temporary<HTMLCollection> {
+ struct ElementChildFilter;
+ impl CollectionFilter for ElementChildFilter {
+ fn filter(&self, elem: &JSRef<Element>, root: &JSRef<Node>) -> bool {
+ root.is_parent_of(NodeCast::from_ref(elem))
+ }
+ }
+ HTMLCollection::create(window, root, box ElementChildFilter)
+ }
+}
+
+impl<'a> HTMLCollectionMethods for JSRef<'a, HTMLCollection> {
+ // http://dom.spec.whatwg.org/#dom-htmlcollection-length
+ fn Length(&self) -> u32 {
+ match self.collection {
+ Static(ref elems) => elems.len() as u32,
+ Live(ref root, ref filter) => {
+ let root = root.root();
+ root.deref().traverse_preorder()
+ .filter(|&child| {
+ let elem: Option<&JSRef<Element>> = ElementCast::to_ref(&child);
+ elem.map_or(false, |elem| filter.filter(elem, &*root))
+ }).count() as u32
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-htmlcollection-item
+ fn Item(&self, index: u32) -> Option<Temporary<Element>> {
+ match self.collection {
+ Static(ref elems) => elems
+ .as_slice()
+ .get(index as uint)
+ .map(|elem| Temporary::new(elem.clone())),
+ Live(ref root, ref filter) => {
+ let root = root.root();
+ root.deref().traverse_preorder()
+ .filter_map(|node| {
+ let elem: Option<&JSRef<Element>> = ElementCast::to_ref(&node);
+ elem.filtered(|&elem| filter.filter(elem, &*root))
+ .map(|elem| elem.clone())
+ })
+ .nth(index as uint)
+ .clone()
+ .map(|elem| Temporary::from_rooted(&elem))
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-htmlcollection-nameditem
+ fn NamedItem(&self, key: DOMString) -> Option<Temporary<Element>> {
+ // Step 1.
+ if key.is_empty() {
+ return None;
+ }
+
+ // Step 2.
+ match self.collection {
+ Static(ref elems) => elems.iter()
+ .map(|elem| elem.root())
+ .find(|elem| {
+ elem.get_string_attribute("name") == key ||
+ elem.get_string_attribute("id") == key })
+ .map(|maybe_elem| Temporary::from_rooted(&*maybe_elem)),
+ Live(ref root, ref filter) => {
+ let root = root.root();
+ root.deref().traverse_preorder()
+ .filter_map(|node| {
+ let elem: Option<&JSRef<Element>> = ElementCast::to_ref(&node);
+ elem.filtered(|&elem| filter.filter(elem, &*root))
+ .map(|elem| elem.clone())
+ })
+ .find(|elem| {
+ elem.get_string_attribute("name") == key ||
+ elem.get_string_attribute("id") == key })
+ .map(|maybe_elem| Temporary::from_rooted(&maybe_elem))
+ }
+ }
+ }
+
+ fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Temporary<Element>> {
+ let maybe_elem = self.Item(index);
+ *found = maybe_elem.is_some();
+ maybe_elem
+ }
+
+ fn NamedGetter(&self, name: DOMString, found: &mut bool) -> Option<Temporary<Element>> {
+ let maybe_elem = self.NamedItem(name);
+ *found = maybe_elem.is_some();
+ maybe_elem
+ }
+}
+
+impl Reflectable for HTMLCollection {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/htmldataelement.rs b/components/script/dom/htmldataelement.rs
new file mode 100644
index 00000000000..cf35507a824
--- /dev/null
+++ b/components/script/dom/htmldataelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLDataElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLDataElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLDataElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLDataElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLDataElementDerived for EventTarget {
+ fn is_htmldataelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLDataElementTypeId))
+ }
+}
+
+impl HTMLDataElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLDataElement {
+ HTMLDataElement {
+ htmlelement: HTMLElement::new_inherited(HTMLDataElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLDataElement> {
+ let element = HTMLDataElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLDataElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLDataElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmldatalistelement.rs b/components/script/dom/htmldatalistelement.rs
new file mode 100644
index 00000000000..f7ff70e9858
--- /dev/null
+++ b/components/script/dom/htmldatalistelement.rs
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLDataListElementBinding;
+use dom::bindings::codegen::Bindings::HTMLDataListElementBinding::HTMLDataListElementMethods;
+use dom::bindings::codegen::InheritTypes::{HTMLDataListElementDerived, HTMLOptionElementDerived};
+use dom::bindings::codegen::InheritTypes::NodeCast;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{Element, HTMLDataListElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlcollection::{HTMLCollection, CollectionFilter};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLDataListElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLDataListElementDerived for EventTarget {
+ fn is_htmldatalistelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLDataListElementTypeId))
+ }
+}
+
+impl HTMLDataListElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLDataListElement {
+ HTMLDataListElement {
+ htmlelement: HTMLElement::new_inherited(HTMLDataListElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLDataListElement> {
+ let element = HTMLDataListElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLDataListElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLDataListElementMethods for JSRef<'a, HTMLDataListElement> {
+ fn Options(&self) -> Temporary<HTMLCollection> {
+ struct HTMLDataListOptionsFilter;
+ impl CollectionFilter for HTMLDataListOptionsFilter {
+ fn filter(&self, elem: &JSRef<Element>, _root: &JSRef<Node>) -> bool {
+ elem.is_htmloptionelement()
+ }
+ }
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let filter = box HTMLDataListOptionsFilter;
+ let window = window_from_node(node).root();
+ HTMLCollection::create(&*window, node, filter)
+ }
+}
+
+impl Reflectable for HTMLDataListElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmldirectoryelement.rs b/components/script/dom/htmldirectoryelement.rs
new file mode 100644
index 00000000000..2539a389e19
--- /dev/null
+++ b/components/script/dom/htmldirectoryelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLDirectoryElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLDirectoryElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLDirectoryElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLDirectoryElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLDirectoryElementDerived for EventTarget {
+ fn is_htmldirectoryelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLDirectoryElementTypeId))
+ }
+}
+
+impl HTMLDirectoryElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLDirectoryElement {
+ HTMLDirectoryElement {
+ htmlelement: HTMLElement::new_inherited(HTMLDirectoryElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLDirectoryElement> {
+ let element = HTMLDirectoryElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLDirectoryElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLDirectoryElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmldivelement.rs b/components/script/dom/htmldivelement.rs
new file mode 100644
index 00000000000..01319b56f98
--- /dev/null
+++ b/components/script/dom/htmldivelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLDivElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLDivElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLDivElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLDivElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLDivElementDerived for EventTarget {
+ fn is_htmldivelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLDivElementTypeId))
+ }
+}
+
+impl HTMLDivElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLDivElement {
+ HTMLDivElement {
+ htmlelement: HTMLElement::new_inherited(HTMLDivElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLDivElement> {
+ let element = HTMLDivElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLDivElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLDivElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmldlistelement.rs b/components/script/dom/htmldlistelement.rs
new file mode 100644
index 00000000000..0af66bca2c5
--- /dev/null
+++ b/components/script/dom/htmldlistelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLDListElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLDListElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLDListElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLDListElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLDListElementDerived for EventTarget {
+ fn is_htmldlistelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLDListElementTypeId))
+ }
+}
+
+impl HTMLDListElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLDListElement {
+ HTMLDListElement {
+ htmlelement: HTMLElement::new_inherited(HTMLDListElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLDListElement> {
+ let element = HTMLDListElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLDListElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLDListElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs
new file mode 100644
index 00000000000..076ba5fddce
--- /dev/null
+++ b/components/script/dom/htmlelement.rs
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::HTMLElementBinding;
+use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFrameSetElementDerived};
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::codegen::InheritTypes::{HTMLElementDerived, HTMLBodyElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{Element, ElementTypeId, HTMLElementTypeId};
+use dom::eventtarget::{EventTarget, EventTargetHelpers, NodeTargetTypeId};
+use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLElement {
+ pub element: Element
+}
+
+impl HTMLElementDerived for EventTarget {
+ fn is_htmlelement(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(ElementNodeTypeId(ElementTypeId)) => false,
+ NodeTargetTypeId(ElementNodeTypeId(_)) => true,
+ _ => false
+ }
+ }
+}
+
+impl HTMLElement {
+ pub fn new_inherited(type_id: ElementTypeId, tag_name: DOMString, document: &JSRef<Document>) -> HTMLElement {
+ HTMLElement {
+ element: Element::new_inherited(type_id, tag_name, namespace::HTML, None, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLElement> {
+ let element = HTMLElement::new_inherited(HTMLElementTypeId, localName, document);
+ Node::reflect_node(box element, document, HTMLElementBinding::Wrap)
+ }
+}
+
+trait PrivateHTMLElementHelpers {
+ fn is_body_or_frameset(&self) -> bool;
+}
+
+impl<'a> PrivateHTMLElementHelpers for JSRef<'a, HTMLElement> {
+ fn is_body_or_frameset(&self) -> bool {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.is_htmlbodyelement() || eventtarget.is_htmlframesetelement()
+ }
+}
+
+impl<'a> HTMLElementMethods for JSRef<'a, HTMLElement> {
+ fn GetOnclick(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("click")
+ }
+
+ fn SetOnclick(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("click", listener)
+ }
+
+ fn GetOnload(&self) -> Option<EventHandlerNonNull> {
+ if self.is_body_or_frameset() {
+ let win = window_from_node(self).root();
+ win.deref().GetOnload()
+ } else {
+ None
+ }
+ }
+
+ fn SetOnload(&self, listener: Option<EventHandlerNonNull>) {
+ if self.is_body_or_frameset() {
+ let win = window_from_node(self).root();
+ win.deref().SetOnload(listener)
+ }
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ Some(element as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ if name.as_slice().starts_with("on") {
+ let window = window_from_node(self).root();
+ let (cx, url, reflector) = (window.get_cx(),
+ window.get_url(),
+ window.reflector().get_jsobject());
+ let evtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ evtarget.set_event_handler_uncompiled(cx, url, reflector,
+ name.as_slice().slice_from(2),
+ value);
+ }
+ }
+}
+
+impl Reflectable for HTMLElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.element.reflector()
+ }
+}
diff --git a/components/script/dom/htmlembedelement.rs b/components/script/dom/htmlembedelement.rs
new file mode 100644
index 00000000000..142e29ab9ef
--- /dev/null
+++ b/components/script/dom/htmlembedelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLEmbedElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLEmbedElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLEmbedElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLEmbedElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLEmbedElementDerived for EventTarget {
+ fn is_htmlembedelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLEmbedElementTypeId))
+ }
+}
+
+impl HTMLEmbedElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLEmbedElement {
+ HTMLEmbedElement {
+ htmlelement: HTMLElement::new_inherited(HTMLEmbedElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLEmbedElement> {
+ let element = HTMLEmbedElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLEmbedElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLEmbedElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs
new file mode 100644
index 00000000000..3bb30fbf7a7
--- /dev/null
+++ b/components/script/dom/htmlfieldsetelement.rs
@@ -0,0 +1,156 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding;
+use dom::bindings::codegen::Bindings::HTMLFieldSetElementBinding::HTMLFieldSetElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLFieldSetElementDerived, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLLegendElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLFieldSetElementTypeId, HTMLButtonElementTypeId};
+use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId, HTMLTextAreaElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlcollection::{HTMLCollection, CollectionFilter};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node};
+use dom::validitystate::ValidityState;
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::{DOMString, StaticStringVec};
+
+#[deriving(Encodable)]
+pub struct HTMLFieldSetElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLFieldSetElementDerived for EventTarget {
+ fn is_htmlfieldsetelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLFieldSetElementTypeId))
+ }
+}
+
+impl HTMLFieldSetElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLFieldSetElement {
+ HTMLFieldSetElement {
+ htmlelement: HTMLElement::new_inherited(HTMLFieldSetElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLFieldSetElement> {
+ let element = HTMLFieldSetElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLFieldSetElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLFieldSetElementMethods for JSRef<'a, HTMLFieldSetElement> {
+ // http://www.whatwg.org/html/#dom-fieldset-elements
+ fn Elements(&self) -> Temporary<HTMLCollection> {
+ struct ElementsFilter;
+ impl CollectionFilter for ElementsFilter {
+ fn filter(&self, elem: &JSRef<Element>, root: &JSRef<Node>) -> bool {
+ static tag_names: StaticStringVec = &["button", "fieldset", "input",
+ "keygen", "object", "output", "select", "textarea"];
+ let root: &JSRef<Element> = ElementCast::to_ref(root).unwrap();
+ elem != root && tag_names.iter().any(|&tag_name| tag_name == elem.deref().local_name.as_slice())
+ }
+ }
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let filter = box ElementsFilter;
+ let window = window_from_node(node).root();
+ HTMLCollection::create(&*window, node, filter)
+ }
+
+ fn Validity(&self) -> Temporary<ValidityState> {
+ let window = window_from_node(self).root();
+ ValidityState::new(&*window)
+ }
+
+ // http://www.whatwg.org/html/#dom-fieldset-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-fieldset-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLFieldSetElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ let maybe_legend = node.children().find(|node| node.is_htmllegendelement());
+ let filtered: Vec<JSRef<Node>> = node.children().filter(|child| {
+ maybe_legend.map_or(true, |legend| legend != *child)
+ }).collect();
+ for descendant in filtered.iter().flat_map(|child| child.traverse_preorder()) {
+ match descendant.type_id() {
+ ElementNodeTypeId(HTMLButtonElementTypeId) |
+ ElementNodeTypeId(HTMLInputElementTypeId) |
+ ElementNodeTypeId(HTMLSelectElementTypeId) |
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) => {
+ descendant.set_disabled_state(true);
+ descendant.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ let maybe_legend = node.children().find(|node| node.is_htmllegendelement());
+ let filtered: Vec<JSRef<Node>> = node.children().filter(|child| {
+ maybe_legend.map_or(true, |legend| legend != *child)
+ }).collect();
+ for descendant in filtered.iter().flat_map(|child| child.traverse_preorder()) {
+ match descendant.type_id() {
+ ElementNodeTypeId(HTMLButtonElementTypeId) |
+ ElementNodeTypeId(HTMLInputElementTypeId) |
+ ElementNodeTypeId(HTMLSelectElementTypeId) |
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) => {
+ descendant.check_disabled_attribute();
+ descendant.check_ancestors_disabled_state_for_form_control();
+ },
+ _ => ()
+ }
+ }
+ },
+ _ => ()
+ }
+ }
+}
+
+impl Reflectable for HTMLFieldSetElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlfontelement.rs b/components/script/dom/htmlfontelement.rs
new file mode 100644
index 00000000000..a26d83fb7d2
--- /dev/null
+++ b/components/script/dom/htmlfontelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLFontElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLFontElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLFontElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLFontElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLFontElementDerived for EventTarget {
+ fn is_htmlfontelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLFontElementTypeId))
+ }
+}
+
+impl HTMLFontElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLFontElement {
+ HTMLFontElement {
+ htmlelement: HTMLElement::new_inherited(HTMLFontElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLFontElement> {
+ let element = HTMLFontElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLFontElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLFontElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs
new file mode 100644
index 00000000000..e31179ed424
--- /dev/null
+++ b/components/script/dom/htmlformelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLFormElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLFormElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLFormElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLFormElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLFormElementDerived for EventTarget {
+ fn is_htmlformelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLFormElementTypeId))
+ }
+}
+
+impl HTMLFormElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLFormElement {
+ HTMLFormElement {
+ htmlelement: HTMLElement::new_inherited(HTMLFormElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLFormElement> {
+ let element = HTMLFormElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLFormElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLFormElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlframeelement.rs b/components/script/dom/htmlframeelement.rs
new file mode 100644
index 00000000000..dd362a3947a
--- /dev/null
+++ b/components/script/dom/htmlframeelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLFrameElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLFrameElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLFrameElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLFrameElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLFrameElementDerived for EventTarget {
+ fn is_htmlframeelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLFrameElementTypeId))
+ }
+}
+
+impl HTMLFrameElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLFrameElement {
+ HTMLFrameElement {
+ htmlelement: HTMLElement::new_inherited(HTMLFrameElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLFrameElement> {
+ let element = HTMLFrameElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLFrameElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLFrameElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlframesetelement.rs b/components/script/dom/htmlframesetelement.rs
new file mode 100644
index 00000000000..ad6168a0613
--- /dev/null
+++ b/components/script/dom/htmlframesetelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLFrameSetElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLFrameSetElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLFrameSetElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLFrameSetElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLFrameSetElementDerived for EventTarget {
+ fn is_htmlframesetelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLFrameSetElementTypeId))
+ }
+}
+
+impl HTMLFrameSetElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLFrameSetElement {
+ HTMLFrameSetElement {
+ htmlelement: HTMLElement::new_inherited(HTMLFrameSetElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLFrameSetElement> {
+ let element = HTMLFrameSetElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLFrameSetElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLFrameSetElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlheadelement.rs b/components/script/dom/htmlheadelement.rs
new file mode 100644
index 00000000000..f3738058179
--- /dev/null
+++ b/components/script/dom/htmlheadelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLHeadElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLHeadElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLHeadElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLHeadElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLHeadElementDerived for EventTarget {
+ fn is_htmlheadelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLHeadElementTypeId))
+ }
+}
+
+impl HTMLHeadElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLHeadElement {
+ HTMLHeadElement {
+ htmlelement: HTMLElement::new_inherited(HTMLHeadElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLHeadElement> {
+ let element = HTMLHeadElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLHeadElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLHeadElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlheadingelement.rs b/components/script/dom/htmlheadingelement.rs
new file mode 100644
index 00000000000..b869e9764e1
--- /dev/null
+++ b/components/script/dom/htmlheadingelement.rs
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLHeadingElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLHeadingElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLHeadingElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub enum HeadingLevel {
+ Heading1,
+ Heading2,
+ Heading3,
+ Heading4,
+ Heading5,
+ Heading6,
+}
+
+#[deriving(Encodable)]
+pub struct HTMLHeadingElement {
+ pub htmlelement: HTMLElement,
+ pub level: HeadingLevel,
+}
+
+impl HTMLHeadingElementDerived for EventTarget {
+ fn is_htmlheadingelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLHeadingElementTypeId))
+ }
+}
+
+impl HTMLHeadingElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>, level: HeadingLevel) -> HTMLHeadingElement {
+ HTMLHeadingElement {
+ htmlelement: HTMLElement::new_inherited(HTMLHeadingElementTypeId, localName, document),
+ level: level,
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>, level: HeadingLevel) -> Temporary<HTMLHeadingElement> {
+ let element = HTMLHeadingElement::new_inherited(localName, document, level);
+ Node::reflect_node(box element, document, HTMLHeadingElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLHeadingElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlhrelement.rs b/components/script/dom/htmlhrelement.rs
new file mode 100644
index 00000000000..18a92df2679
--- /dev/null
+++ b/components/script/dom/htmlhrelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLHRElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLHRElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLHRElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLHRElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLHRElementDerived for EventTarget {
+ fn is_htmlhrelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLHRElementTypeId))
+ }
+}
+
+impl HTMLHRElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLHRElement {
+ HTMLHRElement {
+ htmlelement: HTMLElement::new_inherited(HTMLHRElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLHRElement> {
+ let element = HTMLHRElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLHRElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLHRElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlhtmlelement.rs b/components/script/dom/htmlhtmlelement.rs
new file mode 100644
index 00000000000..117cdf78257
--- /dev/null
+++ b/components/script/dom/htmlhtmlelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLHtmlElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLHtmlElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLHtmlElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLHtmlElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLHtmlElementDerived for EventTarget {
+ fn is_htmlhtmlelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLHtmlElementTypeId))
+ }
+}
+
+impl HTMLHtmlElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLHtmlElement {
+ HTMLHtmlElement {
+ htmlelement: HTMLElement::new_inherited(HTMLHtmlElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLHtmlElement> {
+ let element = HTMLHtmlElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLHtmlElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLHtmlElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs
new file mode 100644
index 00000000000..b2d2c05f728
--- /dev/null
+++ b/components/script/dom/htmliframeelement.rs
@@ -0,0 +1,225 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding;
+use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods;
+use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
+use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLIFrameElementDerived};
+use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{HTMLIFrameElementTypeId, Element};
+use dom::element::AttributeHandlers;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+use dom::window::Window;
+use page::IterablePage;
+
+use servo_msg::constellation_msg::{PipelineId, SubpageId};
+use servo_msg::constellation_msg::{IFrameSandboxed, IFrameUnsandboxed};
+use servo_msg::constellation_msg::{ConstellationChan, LoadIframeUrlMsg};
+use servo_util::atom::Atom;
+use servo_util::namespace::Null;
+use servo_util::str::DOMString;
+
+use std::ascii::StrAsciiExt;
+use std::cell::Cell;
+use url::{Url, UrlParser};
+
+enum SandboxAllowance {
+ AllowNothing = 0x00,
+ AllowSameOrigin = 0x01,
+ AllowTopNavigation = 0x02,
+ AllowForms = 0x04,
+ AllowScripts = 0x08,
+ AllowPointerLock = 0x10,
+ AllowPopups = 0x20
+}
+
+#[deriving(Encodable)]
+pub struct HTMLIFrameElement {
+ pub htmlelement: HTMLElement,
+ pub size: Traceable<Cell<Option<IFrameSize>>>,
+ pub sandbox: Traceable<Cell<Option<u8>>>,
+}
+
+impl HTMLIFrameElementDerived for EventTarget {
+ fn is_htmliframeelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLIFrameElementTypeId))
+ }
+}
+
+#[deriving(Encodable)]
+pub struct IFrameSize {
+ pub pipeline_id: PipelineId,
+ pub subpage_id: SubpageId,
+}
+
+pub trait HTMLIFrameElementHelpers {
+ fn is_sandboxed(&self) -> bool;
+ fn get_url(&self) -> Option<Url>;
+ /// http://www.whatwg.org/html/#process-the-iframe-attributes
+ fn process_the_iframe_attributes(&self);
+}
+
+impl<'a> HTMLIFrameElementHelpers for JSRef<'a, HTMLIFrameElement> {
+ fn is_sandboxed(&self) -> bool {
+ self.sandbox.deref().get().is_some()
+ }
+
+ fn get_url(&self) -> Option<Url> {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_attribute(Null, "src").root().and_then(|src| {
+ let window = window_from_node(self).root();
+ UrlParser::new().base_url(&window.deref().page().get_url())
+ .parse(src.deref().value().as_slice()).ok()
+ })
+ }
+
+ fn process_the_iframe_attributes(&self) {
+ match self.get_url() {
+ Some(url) => {
+ let sandboxed = if self.is_sandboxed() {
+ IFrameSandboxed
+ } else {
+ IFrameUnsandboxed
+ };
+
+ // Subpage Id
+ let window = window_from_node(self).root();
+ let page = window.deref().page();
+ let subpage_id = page.get_next_subpage_id();
+
+ self.deref().size.deref().set(Some(IFrameSize {
+ pipeline_id: page.id,
+ subpage_id: subpage_id,
+ }));
+
+ let ConstellationChan(ref chan) = *page.constellation_chan.deref();
+ chan.send(LoadIframeUrlMsg(url, page.id, subpage_id, sandboxed));
+ }
+ _ => ()
+ }
+ }
+}
+
+impl HTMLIFrameElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLIFrameElement {
+ HTMLIFrameElement {
+ htmlelement: HTMLElement::new_inherited(HTMLIFrameElementTypeId, localName, document),
+ size: Traceable::new(Cell::new(None)),
+ sandbox: Traceable::new(Cell::new(None)),
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLIFrameElement> {
+ let element = HTMLIFrameElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLIFrameElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLIFrameElementMethods for JSRef<'a, HTMLIFrameElement> {
+ fn Src(&self) -> DOMString {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_string_attribute("src")
+ }
+
+ fn SetSrc(&self, src: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_url_attribute("src", src)
+ }
+
+ fn Sandbox(&self) -> DOMString {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_string_attribute("sandbox")
+ }
+
+ fn SetSandbox(&self, sandbox: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("sandbox", sandbox);
+ }
+
+ fn GetContentWindow(&self) -> Option<Temporary<Window>> {
+ self.size.deref().get().and_then(|size| {
+ let window = window_from_node(self).root();
+ let children = &*window.deref().page.children.deref().borrow();
+ let child = children.iter().find(|child| {
+ child.subpage_id.unwrap() == size.subpage_id
+ });
+ child.and_then(|page| {
+ page.frame.deref().borrow().as_ref().map(|frame| {
+ Temporary::new(frame.window.clone())
+ })
+ })
+ })
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLIFrameElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ if "sandbox" == name.as_slice() {
+ let mut modes = AllowNothing as u8;
+ for word in value.as_slice().split(' ') {
+ modes |= match word.to_ascii_lower().as_slice() {
+ "allow-same-origin" => AllowSameOrigin,
+ "allow-forms" => AllowForms,
+ "allow-pointer-lock" => AllowPointerLock,
+ "allow-popups" => AllowPopups,
+ "allow-scripts" => AllowScripts,
+ "allow-top-navigation" => AllowTopNavigation,
+ _ => AllowNothing
+ } as u8;
+ }
+ self.deref().sandbox.deref().set(Some(modes));
+ }
+
+ if "src" == name.as_slice() {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.is_in_doc() {
+ self.process_the_iframe_attributes()
+ }
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ if "sandbox" == name.as_slice() {
+ self.deref().sandbox.deref().set(None);
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ if tree_in_doc {
+ self.process_the_iframe_attributes();
+ }
+ }
+}
+
+impl Reflectable for HTMLIFrameElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs
new file mode 100644
index 00000000000..33d7b7dfd31
--- /dev/null
+++ b/components/script/dom/htmlimageelement.rs
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::attr::AttrValue;
+use dom::bindings::codegen::Bindings::HTMLImageElementBinding;
+use dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
+use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast, HTMLElementCast, HTMLImageElementDerived};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::trace::Untraceable;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{Element, HTMLImageElementTypeId};
+use dom::element::AttributeHandlers;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+use servo_net::image_cache_task;
+use servo_util::atom::Atom;
+use servo_util::geometry::to_px;
+use servo_util::str::DOMString;
+
+use url::{Url, UrlParser};
+
+use std::cell::RefCell;
+
+#[deriving(Encodable)]
+pub struct HTMLImageElement {
+ pub htmlelement: HTMLElement,
+ image: Untraceable<RefCell<Option<Url>>>,
+}
+
+impl HTMLImageElementDerived for EventTarget {
+ fn is_htmlimageelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLImageElementTypeId))
+ }
+}
+
+trait PrivateHTMLImageElementHelpers {
+ fn update_image(&self, value: Option<(DOMString, &Url)>);
+}
+
+impl<'a> PrivateHTMLImageElementHelpers for JSRef<'a, HTMLImageElement> {
+ /// Makes the local `image` member match the status of the `src` attribute and starts
+ /// prefetching the image. This method must be called after `src` is changed.
+ fn update_image(&self, value: Option<(DOMString, &Url)>) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let document = node.owner_doc().root();
+ let window = document.deref().window.root();
+ let image_cache = &window.image_cache_task;
+ match value {
+ None => {
+ *self.image.deref().borrow_mut() = None;
+ }
+ Some((src, base_url)) => {
+ let img_url = UrlParser::new().base_url(base_url).parse(src.as_slice());
+ // FIXME: handle URL parse errors more gracefully.
+ let img_url = img_url.unwrap();
+ *self.image.deref().borrow_mut() = Some(img_url.clone());
+
+ // inform the image cache to load this, but don't store a
+ // handle.
+ //
+ // TODO (Issue #84): don't prefetch if we are within a
+ // <noscript> tag.
+ image_cache.send(image_cache_task::Prefetch(img_url));
+ }
+ }
+ }
+}
+
+impl HTMLImageElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLImageElement {
+ HTMLImageElement {
+ htmlelement: HTMLElement::new_inherited(HTMLImageElementTypeId, localName, document),
+ image: Untraceable::new(RefCell::new(None)),
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLImageElement> {
+ let element = HTMLImageElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLImageElementBinding::Wrap)
+ }
+}
+
+pub trait LayoutHTMLImageElementHelpers {
+ unsafe fn image(&self) -> Option<Url>;
+}
+
+impl LayoutHTMLImageElementHelpers for JS<HTMLImageElement> {
+ unsafe fn image(&self) -> Option<Url> {
+ (*self.unsafe_get()).image.borrow().clone()
+ }
+}
+
+impl<'a> HTMLImageElementMethods for JSRef<'a, HTMLImageElement> {
+ make_getter!(Alt)
+
+ fn SetAlt(&self, alt: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("alt", alt)
+ }
+
+ make_getter!(Src)
+
+ fn SetSrc(&self, src: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_url_attribute("src", src)
+ }
+
+ make_getter!(UseMap)
+
+ fn SetUseMap(&self, use_map: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("usemap", use_map)
+ }
+
+ make_bool_getter!(IsMap)
+
+ fn SetIsMap(&self, is_map: bool) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("ismap", is_map.to_string())
+ }
+
+ fn Width(&self) -> u32 {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let rect = node.get_bounding_content_box();
+ to_px(rect.size.width) as u32
+ }
+
+ fn SetWidth(&self, width: u32) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_uint_attribute("width", width)
+ }
+
+ fn Height(&self) -> u32 {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let rect = node.get_bounding_content_box();
+ to_px(rect.size.height) as u32
+ }
+
+ fn SetHeight(&self, height: u32) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_uint_attribute("height", height)
+ }
+
+ make_getter!(Name)
+
+ fn SetName(&self, name: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("name", name)
+ }
+
+ make_getter!(Align)
+
+ fn SetAlign(&self, align: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("align", align)
+ }
+
+ make_uint_getter!(Hspace)
+
+ fn SetHspace(&self, hspace: u32) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_uint_attribute("hspace", hspace)
+ }
+
+ make_uint_getter!(Vspace)
+
+ fn SetVspace(&self, vspace: u32) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_uint_attribute("vspace", vspace)
+ }
+
+ make_getter!(LongDesc)
+
+ fn SetLongDesc(&self, longdesc: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("longdesc", longdesc)
+ }
+
+ make_getter!(Border)
+
+ fn SetBorder(&self, border: DOMString) {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.set_string_attribute("border", border)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLImageElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ if "src" == name.as_slice() {
+ let window = window_from_node(self).root();
+ let url = window.deref().get_url();
+ self.update_image(Some((value, &url)));
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ if "src" == name.as_slice() {
+ self.update_image(None);
+ }
+ }
+
+ fn parse_plain_attribute(&self, name: &str, value: DOMString) -> AttrValue {
+ match name {
+ "width" | "height" | "hspace" | "vspace" => AttrValue::from_u32(value, 0),
+ _ => self.super_type().unwrap().parse_plain_attribute(name, value),
+ }
+ }
+}
+
+impl Reflectable for HTMLImageElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
new file mode 100644
index 00000000000..38d63acc7da
--- /dev/null
+++ b/components/script/dom/htmlinputelement.rs
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLInputElementBinding;
+use dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLInputElementDerived, HTMLFieldSetElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLInputElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLInputElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLInputElementDerived for EventTarget {
+ fn is_htmlinputelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLInputElementTypeId))
+ }
+}
+
+impl HTMLInputElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLInputElement {
+ HTMLInputElement {
+ htmlelement: HTMLElement::new_inherited(HTMLInputElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLInputElement> {
+ let element = HTMLInputElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLInputElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLInputElementMethods for JSRef<'a, HTMLInputElement> {
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLInputElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ node.check_ancestors_disabled_state_for_form_control();
+ },
+ _ => ()
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.check_ancestors_disabled_state_for_form_control();
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) {
+ node.check_ancestors_disabled_state_for_form_control();
+ } else {
+ node.check_disabled_attribute();
+ }
+ }
+}
+
+impl Reflectable for HTMLInputElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs
new file mode 100644
index 00000000000..54349aa5bf5
--- /dev/null
+++ b/components/script/dom/htmllabelelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLLabelElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLLabelElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLLabelElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLLabelElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLLabelElementDerived for EventTarget {
+ fn is_htmllabelelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLLabelElementTypeId))
+ }
+}
+
+impl HTMLLabelElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLLabelElement {
+ HTMLLabelElement {
+ htmlelement: HTMLElement::new_inherited(HTMLLabelElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLLabelElement> {
+ let element = HTMLLabelElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLLabelElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLLabelElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs
new file mode 100644
index 00000000000..168f94bc27e
--- /dev/null
+++ b/components/script/dom/htmllegendelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLLegendElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLLegendElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLLegendElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLLegendElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLLegendElementDerived for EventTarget {
+ fn is_htmllegendelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLLegendElementTypeId))
+ }
+}
+
+impl HTMLLegendElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLLegendElement {
+ HTMLLegendElement {
+ htmlelement: HTMLElement::new_inherited(HTMLLegendElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLLegendElement> {
+ let element = HTMLLegendElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLLegendElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLLegendElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmllielement.rs b/components/script/dom/htmllielement.rs
new file mode 100644
index 00000000000..5d15d405d94
--- /dev/null
+++ b/components/script/dom/htmllielement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLLIElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLLIElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLLIElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLLIElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLLIElementDerived for EventTarget {
+ fn is_htmllielement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLLIElementTypeId))
+ }
+}
+
+impl HTMLLIElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLLIElement {
+ HTMLLIElement {
+ htmlelement: HTMLElement::new_inherited(HTMLLIElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLLIElement> {
+ let element = HTMLLIElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLLIElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLLIElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs
new file mode 100644
index 00000000000..fae89c1c520
--- /dev/null
+++ b/components/script/dom/htmllinkelement.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLLinkElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLLinkElementDerived;
+use dom::bindings::codegen::InheritTypes::{HTMLElementCast, NodeCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLLinkElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLLinkElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLLinkElementDerived for EventTarget {
+ fn is_htmllinkelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLLinkElementTypeId))
+ }
+}
+
+impl HTMLLinkElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLLinkElement {
+ HTMLLinkElement {
+ htmlelement: HTMLElement::new_inherited(HTMLLinkElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLLinkElement> {
+ let element = HTMLLinkElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLLinkElementBinding::Wrap)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLLinkElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(true),
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "href" => node.set_enabled_state(false),
+ _ => ()
+ }
+ }
+}
+
+impl Reflectable for HTMLLinkElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlmapelement.rs b/components/script/dom/htmlmapelement.rs
new file mode 100644
index 00000000000..21e9a04364f
--- /dev/null
+++ b/components/script/dom/htmlmapelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLMapElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLMapElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLMapElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLMapElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLMapElementDerived for EventTarget {
+ fn is_htmlmapelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLMapElementTypeId))
+ }
+}
+
+impl HTMLMapElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLMapElement {
+ HTMLMapElement {
+ htmlelement: HTMLElement::new_inherited(HTMLMapElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLMapElement> {
+ let element = HTMLMapElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLMapElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLMapElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
new file mode 100644
index 00000000000..b9e1ad7782d
--- /dev/null
+++ b/components/script/dom/htmlmediaelement.rs
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::js::{JSRef};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::bindings::codegen::InheritTypes::HTMLMediaElementDerived;
+use dom::document::Document;
+use dom::element::{ElementTypeId, HTMLAudioElementTypeId, HTMLVideoElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::ElementNodeTypeId;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLMediaElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLMediaElementDerived for EventTarget {
+ fn is_htmlmediaelement(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(ElementNodeTypeId(HTMLVideoElementTypeId)) |
+ NodeTargetTypeId(ElementNodeTypeId(HTMLAudioElementTypeId)) => true,
+ _ => false
+ }
+ }
+}
+
+impl HTMLMediaElement {
+ pub fn new_inherited(type_id: ElementTypeId, tag_name: DOMString, document: &JSRef<Document>) -> HTMLMediaElement {
+ HTMLMediaElement {
+ htmlelement: HTMLElement::new_inherited(type_id, tag_name, document)
+ }
+ }
+}
+
+impl Reflectable for HTMLMediaElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs
new file mode 100644
index 00000000000..224d400a216
--- /dev/null
+++ b/components/script/dom/htmlmetaelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLMetaElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLMetaElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLMetaElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLMetaElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLMetaElementDerived for EventTarget {
+ fn is_htmlmetaelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLMetaElementTypeId))
+ }
+}
+
+impl HTMLMetaElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLMetaElement {
+ HTMLMetaElement {
+ htmlelement: HTMLElement::new_inherited(HTMLMetaElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLMetaElement> {
+ let element = HTMLMetaElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLMetaElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLMetaElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlmeterelement.rs b/components/script/dom/htmlmeterelement.rs
new file mode 100644
index 00000000000..a0eeff8cb92
--- /dev/null
+++ b/components/script/dom/htmlmeterelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLMeterElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLMeterElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLMeterElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLMeterElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLMeterElementDerived for EventTarget {
+ fn is_htmlmeterelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLMeterElementTypeId))
+ }
+}
+
+impl HTMLMeterElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLMeterElement {
+ HTMLMeterElement {
+ htmlelement: HTMLElement::new_inherited(HTMLMeterElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLMeterElement> {
+ let element = HTMLMeterElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLMeterElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLMeterElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlmodelement.rs b/components/script/dom/htmlmodelement.rs
new file mode 100644
index 00000000000..6ea001f5185
--- /dev/null
+++ b/components/script/dom/htmlmodelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLModElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLModElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLModElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLModElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLModElementDerived for EventTarget {
+ fn is_htmlmodelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLModElementTypeId))
+ }
+}
+
+impl HTMLModElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLModElement {
+ HTMLModElement {
+ htmlelement: HTMLElement::new_inherited(HTMLModElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLModElement> {
+ let element = HTMLModElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLModElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLModElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlobjectelement.rs b/components/script/dom/htmlobjectelement.rs
new file mode 100644
index 00000000000..94c87719563
--- /dev/null
+++ b/components/script/dom/htmlobjectelement.rs
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::HTMLObjectElementBinding;
+use dom::bindings::codegen::Bindings::HTMLObjectElementBinding::HTMLObjectElementMethods;
+use dom::bindings::codegen::InheritTypes::HTMLObjectElementDerived;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{Element, HTMLObjectElementTypeId};
+use dom::element::AttributeHandlers;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, NodeHelpers, window_from_node};
+use dom::validitystate::ValidityState;
+use dom::virtualmethods::VirtualMethods;
+
+use servo_net::image_cache_task;
+use servo_net::image_cache_task::ImageCacheTask;
+use servo_util::atom::Atom;
+use servo_util::namespace::Null;
+use servo_util::str::DOMString;
+
+use url::Url;
+
+#[deriving(Encodable)]
+pub struct HTMLObjectElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLObjectElementDerived for EventTarget {
+ fn is_htmlobjectelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLObjectElementTypeId))
+ }
+}
+
+impl HTMLObjectElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLObjectElement {
+ HTMLObjectElement {
+ htmlelement: HTMLElement::new_inherited(HTMLObjectElementTypeId, localName, document),
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLObjectElement> {
+ let element = HTMLObjectElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLObjectElementBinding::Wrap)
+ }
+}
+
+trait ProcessDataURL {
+ fn process_data_url(&self, image_cache: ImageCacheTask);
+}
+
+impl<'a> ProcessDataURL for JSRef<'a, HTMLObjectElement> {
+ // Makes the local `data` member match the status of the `data` attribute and starts
+ /// prefetching the image. This method must be called after `data` is changed.
+ fn process_data_url(&self, image_cache: ImageCacheTask) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+
+ // TODO: support other values
+ match (elem.get_attribute(Null, "type").map(|x| x.root().Value()),
+ elem.get_attribute(Null, "data").map(|x| x.root().Value())) {
+ (None, Some(uri)) => {
+ if is_image_data(uri.as_slice()) {
+ let data_url = Url::parse(uri.as_slice()).unwrap();
+ // Issue #84
+ image_cache.send(image_cache_task::Prefetch(data_url));
+ }
+ }
+ _ => { }
+ }
+ }
+}
+
+pub fn is_image_data(uri: &str) -> bool {
+ static types: &'static [&'static str] = &["data:image/png", "data:image/gif", "data:image/jpeg"];
+ types.iter().any(|&type_| uri.starts_with(type_))
+}
+
+impl<'a> HTMLObjectElementMethods for JSRef<'a, HTMLObjectElement> {
+ fn Validity(&self) -> Temporary<ValidityState> {
+ let window = window_from_node(self).root();
+ ValidityState::new(&*window)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLObjectElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value),
+ _ => (),
+ }
+
+ if "data" == name.as_slice() {
+ let window = window_from_node(self).root();
+ self.process_data_url(window.deref().image_cache_task.clone());
+ }
+ }
+}
+
+impl Reflectable for HTMLObjectElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlolistelement.rs b/components/script/dom/htmlolistelement.rs
new file mode 100644
index 00000000000..11637fe3bd5
--- /dev/null
+++ b/components/script/dom/htmlolistelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLOListElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLOListElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLOListElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLOListElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLOListElementDerived for EventTarget {
+ fn is_htmlolistelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLOListElementTypeId))
+ }
+}
+
+impl HTMLOListElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLOListElement {
+ HTMLOListElement {
+ htmlelement: HTMLElement::new_inherited(HTMLOListElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLOListElement> {
+ let element = HTMLOListElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLOListElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLOListElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmloptgroupelement.rs b/components/script/dom/htmloptgroupelement.rs
new file mode 100644
index 00000000000..6e90cb45fe5
--- /dev/null
+++ b/components/script/dom/htmloptgroupelement.rs
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding;
+use dom::bindings::codegen::Bindings::HTMLOptGroupElementBinding::HTMLOptGroupElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLOptGroupElementDerived, HTMLOptionElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLOptGroupElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLOptGroupElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLOptGroupElementDerived for EventTarget {
+ fn is_htmloptgroupelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLOptGroupElementTypeId))
+ }
+}
+
+impl HTMLOptGroupElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLOptGroupElement {
+ HTMLOptGroupElement {
+ htmlelement: HTMLElement::new_inherited(HTMLOptGroupElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLOptGroupElement> {
+ let element = HTMLOptGroupElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLOptGroupElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLOptGroupElementMethods for JSRef<'a, HTMLOptGroupElement> {
+ // http://www.whatwg.org/html#dom-optgroup-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html#dom-optgroup-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLOptGroupElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ for child in node.children().filter(|child| child.is_htmloptionelement()) {
+ child.set_disabled_state(true);
+ child.set_enabled_state(false);
+ }
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ for child in node.children().filter(|child| child.is_htmloptionelement()) {
+ child.check_disabled_attribute();
+ }
+ },
+ _ => ()
+ }
+ }
+}
+
+impl Reflectable for HTMLOptGroupElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs
new file mode 100644
index 00000000000..d066784b285
--- /dev/null
+++ b/components/script/dom/htmloptionelement.rs
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLOptionElementBinding;
+use dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::HTMLOptionElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLOptionElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLOptionElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLOptionElementDerived for EventTarget {
+ fn is_htmloptionelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLOptionElementTypeId))
+ }
+}
+
+impl HTMLOptionElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLOptionElement {
+ HTMLOptionElement {
+ htmlelement: HTMLElement::new_inherited(HTMLOptionElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLOptionElement> {
+ let element = HTMLOptionElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLOptionElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLOptionElementMethods for JSRef<'a, HTMLOptionElement> {
+ // http://www.whatwg.org/html/#dom-option-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-option-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLOptionElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ node.check_parent_disabled_state_for_option();
+ },
+ _ => ()
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.check_parent_disabled_state_for_option();
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.parent_node().is_some() {
+ node.check_parent_disabled_state_for_option();
+ } else {
+ node.check_disabled_attribute();
+ }
+ }
+}
+
+impl Reflectable for HTMLOptionElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmloutputelement.rs b/components/script/dom/htmloutputelement.rs
new file mode 100644
index 00000000000..19926cfe4fc
--- /dev/null
+++ b/components/script/dom/htmloutputelement.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLOutputElementBinding;
+use dom::bindings::codegen::Bindings::HTMLOutputElementBinding::HTMLOutputElementMethods;
+use dom::bindings::codegen::InheritTypes::HTMLOutputElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLOutputElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId, window_from_node};
+use dom::validitystate::ValidityState;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLOutputElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLOutputElementDerived for EventTarget {
+ fn is_htmloutputelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLOutputElementTypeId))
+ }
+}
+
+impl HTMLOutputElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLOutputElement {
+ HTMLOutputElement {
+ htmlelement: HTMLElement::new_inherited(HTMLOutputElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLOutputElement> {
+ let element = HTMLOutputElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLOutputElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLOutputElementMethods for JSRef<'a, HTMLOutputElement> {
+ fn Validity(&self) -> Temporary<ValidityState> {
+ let window = window_from_node(self).root();
+ ValidityState::new(&*window)
+ }
+}
+
+impl Reflectable for HTMLOutputElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlparagraphelement.rs b/components/script/dom/htmlparagraphelement.rs
new file mode 100644
index 00000000000..fe4dd4317cf
--- /dev/null
+++ b/components/script/dom/htmlparagraphelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLParagraphElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLParagraphElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLParagraphElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLParagraphElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLParagraphElementDerived for EventTarget {
+ fn is_htmlparagraphelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLParagraphElementTypeId))
+ }
+}
+
+impl HTMLParagraphElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLParagraphElement {
+ HTMLParagraphElement {
+ htmlelement: HTMLElement::new_inherited(HTMLParagraphElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLParagraphElement> {
+ let element = HTMLParagraphElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLParagraphElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLParagraphElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlparamelement.rs b/components/script/dom/htmlparamelement.rs
new file mode 100644
index 00000000000..0f181bd1b32
--- /dev/null
+++ b/components/script/dom/htmlparamelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLParamElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLParamElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLParamElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLParamElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLParamElementDerived for EventTarget {
+ fn is_htmlparamelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLParamElementTypeId))
+ }
+}
+
+impl HTMLParamElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLParamElement {
+ HTMLParamElement {
+ htmlelement: HTMLElement::new_inherited(HTMLParamElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLParamElement> {
+ let element = HTMLParamElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLParamElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLParamElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlpreelement.rs b/components/script/dom/htmlpreelement.rs
new file mode 100644
index 00000000000..25f9c75bc15
--- /dev/null
+++ b/components/script/dom/htmlpreelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLPreElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLPreElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLPreElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLPreElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLPreElementDerived for EventTarget {
+ fn is_htmlpreelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLPreElementTypeId))
+ }
+}
+
+impl HTMLPreElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLPreElement {
+ HTMLPreElement {
+ htmlelement: HTMLElement::new_inherited(HTMLPreElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLPreElement> {
+ let element = HTMLPreElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLPreElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLPreElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlprogresselement.rs b/components/script/dom/htmlprogresselement.rs
new file mode 100644
index 00000000000..74dcab1730f
--- /dev/null
+++ b/components/script/dom/htmlprogresselement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLProgressElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLProgressElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLProgressElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLProgressElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLProgressElementDerived for EventTarget {
+ fn is_htmlprogresselement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLProgressElementTypeId))
+ }
+}
+
+impl HTMLProgressElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLProgressElement {
+ HTMLProgressElement {
+ htmlelement: HTMLElement::new_inherited(HTMLProgressElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLProgressElement> {
+ let element = HTMLProgressElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLProgressElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLProgressElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlquoteelement.rs b/components/script/dom/htmlquoteelement.rs
new file mode 100644
index 00000000000..488a82c394d
--- /dev/null
+++ b/components/script/dom/htmlquoteelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLQuoteElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLQuoteElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLQuoteElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLQuoteElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLQuoteElementDerived for EventTarget {
+ fn is_htmlquoteelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLQuoteElementTypeId))
+ }
+}
+
+impl HTMLQuoteElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLQuoteElement {
+ HTMLQuoteElement {
+ htmlelement: HTMLElement::new_inherited(HTMLQuoteElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLQuoteElement> {
+ let element = HTMLQuoteElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLQuoteElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLQuoteElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs
new file mode 100644
index 00000000000..3c189791b94
--- /dev/null
+++ b/components/script/dom/htmlscriptelement.rs
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::HTMLScriptElementBinding;
+use dom::bindings::codegen::Bindings::HTMLScriptElementBinding::HTMLScriptElementMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::HTMLScriptElementDerived;
+use dom::bindings::codegen::InheritTypes::{ElementCast, NodeCast};
+use dom::bindings::js::{JSRef, Temporary, OptionalRootable};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{HTMLScriptElementTypeId, Element, AttributeHandlers};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+
+use servo_util::namespace::Null;
+use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS, StaticStringVec};
+
+#[deriving(Encodable)]
+pub struct HTMLScriptElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLScriptElementDerived for EventTarget {
+ fn is_htmlscriptelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLScriptElementTypeId))
+ }
+}
+
+impl HTMLScriptElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLScriptElement {
+ HTMLScriptElement {
+ htmlelement: HTMLElement::new_inherited(HTMLScriptElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLScriptElement> {
+ let element = HTMLScriptElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLScriptElementBinding::Wrap)
+ }
+}
+
+pub trait HTMLScriptElementHelpers {
+ /// Prepare a script (<http://www.whatwg.org/html/#prepare-a-script>),
+ /// steps 6 and 7.
+ fn is_javascript(&self) -> bool;
+}
+
+/// Supported script types as defined by
+/// <http://whatwg.org/html/#support-the-scripting-language>.
+static SCRIPT_JS_MIMES: StaticStringVec = &[
+ "application/ecmascript",
+ "application/javascript",
+ "application/x-ecmascript",
+ "application/x-javascript",
+ "text/ecmascript",
+ "text/javascript",
+ "text/javascript1.0",
+ "text/javascript1.1",
+ "text/javascript1.2",
+ "text/javascript1.3",
+ "text/javascript1.4",
+ "text/javascript1.5",
+ "text/jscript",
+ "text/livescript",
+ "text/x-ecmascript",
+ "text/x-javascript",
+];
+
+impl<'a> HTMLScriptElementHelpers for JSRef<'a, HTMLScriptElement> {
+ fn is_javascript(&self) -> bool {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ match element.get_attribute(Null, "type").root().map(|s| s.Value()) {
+ Some(ref s) if s.is_empty() => {
+ // type attr exists, but empty means js
+ debug!("script type empty, inferring js");
+ true
+ },
+ Some(ref s) => {
+ debug!("script type={:s}", *s);
+ SCRIPT_JS_MIMES.contains(&s.as_slice().trim_chars(HTML_SPACE_CHARACTERS))
+ },
+ None => {
+ debug!("no script type");
+ match element.get_attribute(Null, "language").root().map(|s| s.Value()) {
+ Some(ref s) if s.is_empty() => {
+ debug!("script language empty, inferring js");
+ true
+ },
+ Some(ref s) => {
+ debug!("script language={:s}", *s);
+ SCRIPT_JS_MIMES.contains(&"text/".to_string().append(s.as_slice()).as_slice())
+ },
+ None => {
+ debug!("no script type or language, inferring js");
+ true
+ }
+ }
+ }
+ }
+ }
+}
+
+impl<'a> HTMLScriptElementMethods for JSRef<'a, HTMLScriptElement> {
+ fn Src(&self) -> DOMString {
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_url_attribute("src")
+ }
+
+ // http://www.whatwg.org/html/#dom-script-text
+ fn Text(&self) -> DOMString {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ Node::collect_text_contents(node.children())
+ }
+
+ // http://www.whatwg.org/html/#dom-script-text
+ fn SetText(&self, value: DOMString) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.SetTextContent(Some(value))
+ }
+}
+
+impl Reflectable for HTMLScriptElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs
new file mode 100644
index 00000000000..b8b5303cffb
--- /dev/null
+++ b/components/script/dom/htmlselectelement.rs
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLSelectElementBinding;
+use dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLSelectElementDerived, HTMLFieldSetElementDerived};
+use dom::bindings::codegen::UnionTypes::HTMLElementOrLong::HTMLElementOrLong;
+use dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement::HTMLOptionElementOrHTMLOptGroupElement;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLSelectElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId, window_from_node};
+use dom::validitystate::ValidityState;
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLSelectElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLSelectElementDerived for EventTarget {
+ fn is_htmlselectelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLSelectElementTypeId))
+ }
+}
+
+impl HTMLSelectElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLSelectElement {
+ HTMLSelectElement {
+ htmlelement: HTMLElement::new_inherited(HTMLSelectElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLSelectElement> {
+ let element = HTMLSelectElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLSelectElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLSelectElementMethods for JSRef<'a, HTMLSelectElement> {
+ fn Validity(&self) -> Temporary<ValidityState> {
+ let window = window_from_node(self).root();
+ ValidityState::new(&*window)
+ }
+
+ // Note: this function currently only exists for test_union.html.
+ fn Add(&self, _element: HTMLOptionElementOrHTMLOptGroupElement, _before: Option<HTMLElementOrLong>) {
+ }
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLSelectElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ node.check_ancestors_disabled_state_for_form_control();
+ },
+ _ => ()
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.check_ancestors_disabled_state_for_form_control();
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) {
+ node.check_ancestors_disabled_state_for_form_control();
+ } else {
+ node.check_disabled_attribute();
+ }
+ }
+}
+
+impl Reflectable for HTMLSelectElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlserializer.rs b/components/script/dom/htmlserializer.rs
new file mode 100644
index 00000000000..cb9e1769255
--- /dev/null
+++ b/components/script/dom/htmlserializer.rs
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::attr::Attr;
+use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, CommentCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{DocumentTypeCast, CharacterDataCast};
+use dom::bindings::codegen::InheritTypes::ProcessingInstructionCast;
+use dom::bindings::js::JSRef;
+use dom::characterdata::CharacterData;
+use dom::comment::Comment;
+use dom::documenttype::DocumentType;
+use dom::element::Element;
+use dom::node::{Node, NodeIterator};
+use dom::node::{DoctypeNodeTypeId, DocumentFragmentNodeTypeId, CommentNodeTypeId};
+use dom::node::{DocumentNodeTypeId, ElementNodeTypeId, ProcessingInstructionNodeTypeId};
+use dom::node::{TextNodeTypeId, NodeHelpers};
+use dom::processinginstruction::ProcessingInstruction;
+use dom::text::Text;
+
+use servo_util::atom::Atom;
+use servo_util::namespace;
+
+pub fn serialize(iterator: &mut NodeIterator) -> String {
+ let mut html = String::new();
+ let mut open_elements: Vec<String> = vec!();
+ let depth = iterator.depth;
+ for node in *iterator {
+ while open_elements.len() > depth {
+ html.push_str("</");
+ html.push_str(open_elements.pop().unwrap().as_slice());
+ html.push_str(">");
+ }
+ match node.type_id() {
+ ElementNodeTypeId(..) => {
+ let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
+ serialize_elem(elem, &mut open_elements, &mut html)
+ }
+ CommentNodeTypeId => {
+ let comment: &JSRef<Comment> = CommentCast::to_ref(&node).unwrap();
+ serialize_comment(comment, &mut html)
+ }
+ TextNodeTypeId => {
+ let text: &JSRef<Text> = TextCast::to_ref(&node).unwrap();
+ serialize_text(text, &mut html)
+ }
+ DoctypeNodeTypeId => {
+ let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(&node).unwrap();
+ serialize_doctype(doctype, &mut html)
+ }
+ ProcessingInstructionNodeTypeId => {
+ let processing_instruction: &JSRef<ProcessingInstruction> =
+ ProcessingInstructionCast::to_ref(&node).unwrap();
+ serialize_processing_instruction(processing_instruction, &mut html)
+ }
+ DocumentFragmentNodeTypeId => {}
+ DocumentNodeTypeId => {
+ fail!("It shouldn't be possible to serialize a document node")
+ }
+ }
+ }
+ while open_elements.len() > 0 {
+ html.push_str("</");
+ html.push_str(open_elements.pop().unwrap().as_slice());
+ html.push_str(">");
+ }
+ html
+}
+
+fn serialize_comment(comment: &JSRef<Comment>, html: &mut String) {
+ html.push_str("<!--");
+ html.push_str(comment.deref().characterdata.data.deref().borrow().as_slice());
+ html.push_str("-->");
+}
+
+fn serialize_text(text: &JSRef<Text>, html: &mut String) {
+ let text_node: &JSRef<Node> = NodeCast::from_ref(text);
+ match text_node.parent_node().map(|node| node.root()) {
+ Some(ref parent) if parent.is_element() => {
+ let elem: &JSRef<Element> = ElementCast::to_ref(&**parent).unwrap();
+ match elem.deref().local_name.as_slice() {
+ "style" | "script" | "xmp" | "iframe" |
+ "noembed" | "noframes" | "plaintext" |
+ "noscript" if elem.deref().namespace == namespace::HTML
+ => html.push_str(text.deref().characterdata.data.deref().borrow().as_slice()),
+ _ => escape(text.deref().characterdata.data.deref().borrow().as_slice(), false, html)
+ }
+ }
+ _ => escape(text.deref().characterdata.data.deref().borrow().as_slice(), false, html)
+ }
+}
+
+fn serialize_processing_instruction(processing_instruction: &JSRef<ProcessingInstruction>,
+ html: &mut String) {
+ html.push_str("<?");
+ html.push_str(processing_instruction.deref().target.as_slice());
+ html.push_char(' ');
+ html.push_str(processing_instruction.deref().characterdata.data.deref().borrow().as_slice());
+ html.push_str("?>");
+}
+
+fn serialize_doctype(doctype: &JSRef<DocumentType>, html: &mut String) {
+ html.push_str("<!DOCTYPE");
+ html.push_str(doctype.deref().name.as_slice());
+ html.push_char('>');
+}
+
+fn serialize_elem(elem: &JSRef<Element>, open_elements: &mut Vec<String>, html: &mut String) {
+ html.push_char('<');
+ html.push_str(elem.deref().local_name.as_slice());
+ for attr in elem.deref().attrs.borrow().iter() {
+ let attr = attr.root();
+ serialize_attr(&*attr, html);
+ };
+ html.push_char('>');
+
+ match elem.deref().local_name.as_slice() {
+ "pre" | "listing" | "textarea" if elem.deref().namespace == namespace::HTML => {
+ let node: &JSRef<Node> = NodeCast::from_ref(elem);
+ match node.first_child().map(|child| child.root()) {
+ Some(ref child) if child.is_text() => {
+ let text: &JSRef<CharacterData> = CharacterDataCast::to_ref(&**child).unwrap();
+ if text.deref().data.deref().borrow().len() > 0 && text.deref().data.deref().borrow().as_slice().char_at(0) == '\n' {
+ html.push_char('\x0A');
+ }
+ },
+ _ => {}
+ }
+ },
+ _ => {}
+ }
+
+ if !elem.deref().is_void() {
+ open_elements.push(elem.deref().local_name.as_slice().to_string());
+ }
+}
+
+fn serialize_attr(attr: &JSRef<Attr>, html: &mut String) {
+ html.push_char(' ');
+ if attr.deref().namespace == namespace::XML {
+ html.push_str("xml:");
+ html.push_str(attr.local_name().as_slice());
+ } else if attr.deref().namespace == namespace::XMLNS &&
+ *attr.local_name() == Atom::from_slice("xmlns") {
+ html.push_str("xmlns");
+ } else if attr.deref().namespace == namespace::XMLNS {
+ html.push_str("xmlns:");
+ html.push_str(attr.local_name().as_slice());
+ } else if attr.deref().namespace == namespace::XLink {
+ html.push_str("xlink:");
+ html.push_str(attr.local_name().as_slice());
+ } else {
+ html.push_str(attr.deref().name.as_slice());
+ };
+ html.push_str("=\"");
+ escape(attr.deref().value().as_slice(), true, html);
+ html.push_char('"');
+}
+
+fn escape(string: &str, attr_mode: bool, html: &mut String) {
+ for c in string.chars() {
+ match c {
+ '&' => html.push_str("&amp;"),
+ '\xA0' => html.push_str("&nbsp;"),
+ '"' if attr_mode => html.push_str("&quot;"),
+ '<' if !attr_mode => html.push_str("&lt;"),
+ '>' if !attr_mode => html.push_str("&gt;"),
+ c => html.push_char(c),
+ }
+ }
+}
diff --git a/components/script/dom/htmlsourceelement.rs b/components/script/dom/htmlsourceelement.rs
new file mode 100644
index 00000000000..cb27b8c75d2
--- /dev/null
+++ b/components/script/dom/htmlsourceelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLSourceElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLSourceElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLSourceElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLSourceElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLSourceElementDerived for EventTarget {
+ fn is_htmlsourceelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLSourceElementTypeId))
+ }
+}
+
+impl HTMLSourceElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLSourceElement {
+ HTMLSourceElement {
+ htmlelement: HTMLElement::new_inherited(HTMLSourceElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLSourceElement> {
+ let element = HTMLSourceElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLSourceElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLSourceElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlspanelement.rs b/components/script/dom/htmlspanelement.rs
new file mode 100644
index 00000000000..9b98b8fa28d
--- /dev/null
+++ b/components/script/dom/htmlspanelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLSpanElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLSpanElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLSpanElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLSpanElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLSpanElementDerived for EventTarget {
+ fn is_htmlspanelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLSpanElementTypeId))
+ }
+}
+
+impl HTMLSpanElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLSpanElement {
+ HTMLSpanElement {
+ htmlelement: HTMLElement::new_inherited(HTMLSpanElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLSpanElement> {
+ let element = HTMLSpanElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLSpanElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLSpanElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs
new file mode 100644
index 00000000000..d32219ea8f9
--- /dev/null
+++ b/components/script/dom/htmlstyleelement.rs
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLStyleElementBinding;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::{HTMLElementCast, HTMLStyleElementDerived, NodeCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLStyleElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId, window_from_node};
+use dom::virtualmethods::VirtualMethods;
+use html::cssparse::parse_inline_css;
+use layout_interface::{AddStylesheetMsg, LayoutChan};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLStyleElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLStyleElementDerived for EventTarget {
+ fn is_htmlstyleelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLStyleElementTypeId))
+ }
+}
+
+impl HTMLStyleElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLStyleElement {
+ HTMLStyleElement {
+ htmlelement: HTMLElement::new_inherited(HTMLStyleElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLStyleElement> {
+ let element = HTMLStyleElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLStyleElementBinding::Wrap)
+ }
+}
+
+pub trait StyleElementHelpers {
+ fn parse_own_css(&self);
+}
+
+impl<'a> StyleElementHelpers for JSRef<'a, HTMLStyleElement> {
+ fn parse_own_css(&self) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ assert!(node.is_in_doc());
+
+ let win = window_from_node(node).root();
+ let url = win.deref().page().get_url();
+
+ let data = node.GetTextContent().expect("Element.textContent must be a string");
+ let sheet = parse_inline_css(url, data);
+ let LayoutChan(ref layout_chan) = *win.deref().page().layout_chan;
+ layout_chan.send(AddStylesheetMsg(sheet));
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLStyleElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn child_inserted(&self, child: &JSRef<Node>) {
+ match self.super_type() {
+ Some(ref s) => s.child_inserted(child),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.is_in_doc() {
+ self.parse_own_css();
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => ()
+ }
+
+ if tree_in_doc {
+ self.parse_own_css();
+ }
+ }
+}
+
+impl Reflectable for HTMLStyleElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltablecaptionelement.rs b/components/script/dom/htmltablecaptionelement.rs
new file mode 100644
index 00000000000..92c45b49400
--- /dev/null
+++ b/components/script/dom/htmltablecaptionelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableCaptionElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableCaptionElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableCaptionElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableCaptionElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLTableCaptionElementDerived for EventTarget {
+ fn is_htmltablecaptionelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableCaptionElementTypeId))
+ }
+}
+
+impl HTMLTableCaptionElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableCaptionElement {
+ HTMLTableCaptionElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTableCaptionElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableCaptionElement> {
+ let element = HTMLTableCaptionElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableCaptionElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableCaptionElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs
new file mode 100644
index 00000000000..116768e23af
--- /dev/null
+++ b/components/script/dom/htmltablecellelement.rs
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::InheritTypes::HTMLTableCellElementDerived;
+use dom::bindings::js::JSRef;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{ElementTypeId, HTMLTableDataCellElementTypeId, HTMLTableHeaderCellElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::ElementNodeTypeId;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableCellElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTableCellElementDerived for EventTarget {
+ fn is_htmltablecellelement(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(ElementNodeTypeId(HTMLTableDataCellElementTypeId)) |
+ NodeTargetTypeId(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId)) => true,
+ _ => false
+ }
+ }
+}
+
+impl HTMLTableCellElement {
+ pub fn new_inherited(type_id: ElementTypeId, tag_name: DOMString, document: &JSRef<Document>) -> HTMLTableCellElement {
+ HTMLTableCellElement {
+ htmlelement: HTMLElement::new_inherited(type_id, tag_name, document)
+ }
+ }
+}
+
+impl Reflectable for HTMLTableCellElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs
new file mode 100644
index 00000000000..48d6164e500
--- /dev/null
+++ b/components/script/dom/htmltablecolelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableColElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableColElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableColElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableColElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTableColElementDerived for EventTarget {
+ fn is_htmltablecolelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableColElementTypeId))
+ }
+}
+
+impl HTMLTableColElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableColElement {
+ HTMLTableColElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTableColElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableColElement> {
+ let element = HTMLTableColElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableColElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableColElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltabledatacellelement.rs b/components/script/dom/htmltabledatacellelement.rs
new file mode 100644
index 00000000000..07027b5d294
--- /dev/null
+++ b/components/script/dom/htmltabledatacellelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableDataCellElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableDataCellElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableDataCellElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmltablecellelement::HTMLTableCellElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableDataCellElement {
+ pub htmltablecellelement: HTMLTableCellElement,
+}
+
+impl HTMLTableDataCellElementDerived for EventTarget {
+ fn is_htmltabledatacellelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableDataCellElementTypeId))
+ }
+}
+
+impl HTMLTableDataCellElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableDataCellElement {
+ HTMLTableDataCellElement {
+ htmltablecellelement: HTMLTableCellElement::new_inherited(HTMLTableDataCellElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableDataCellElement> {
+ let element = HTMLTableDataCellElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableDataCellElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableDataCellElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmltablecellelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltableelement.rs b/components/script/dom/htmltableelement.rs
new file mode 100644
index 00000000000..f33b03fb778
--- /dev/null
+++ b/components/script/dom/htmltableelement.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableElementBinding;
+use dom::bindings::codegen::Bindings::HTMLTableElementBinding::HTMLTableElementMethods;
+use dom::bindings::codegen::InheritTypes::{HTMLTableElementDerived, NodeCast, HTMLTableCaptionElementCast};
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableCaptionElementTypeId;
+use dom::element::HTMLTableElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::htmltablecaptionelement::HTMLTableCaptionElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTableElementDerived for EventTarget {
+ fn is_htmltableelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableElementTypeId))
+ }
+}
+
+impl HTMLTableElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableElement {
+ HTMLTableElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTableElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableElement> {
+ let element = HTMLTableElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
+
+impl<'a> HTMLTableElementMethods for JSRef<'a, HTMLTableElement> {
+
+ // http://www.whatwg.org/html/#dom-table-caption
+ fn GetCaption(&self) -> Option<Temporary<HTMLTableCaptionElement>> {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.children().find(|child| {
+ child.type_id() == ElementNodeTypeId(HTMLTableCaptionElementTypeId)
+ }).map(|node| {
+ Temporary::from_rooted(HTMLTableCaptionElementCast::to_ref(&node).unwrap())
+ })
+ }
+
+ // http://www.whatwg.org/html/#dom-table-caption
+ fn SetCaption(&self, new_caption: Option<JSRef<HTMLTableCaptionElement>>) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let old_caption = self.GetCaption();
+
+ match old_caption {
+ Some(htmlelem) => {
+ let htmlelem_jsref = &*htmlelem.root();
+ let old_caption_node: &JSRef<Node> = NodeCast::from_ref(htmlelem_jsref);
+ assert!(node.RemoveChild(old_caption_node).is_ok());
+ }
+ None => ()
+ }
+
+ new_caption.map(|caption| {
+ let new_caption_node: &JSRef<Node> = NodeCast::from_ref(&caption);
+ assert!(node.AppendChild(new_caption_node).is_ok());
+ });
+ }
+}
diff --git a/components/script/dom/htmltableheadercellelement.rs b/components/script/dom/htmltableheadercellelement.rs
new file mode 100644
index 00000000000..2ad288951da
--- /dev/null
+++ b/components/script/dom/htmltableheadercellelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableHeaderCellElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableHeaderCellElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableHeaderCellElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmltablecellelement::HTMLTableCellElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableHeaderCellElement {
+ pub htmltablecellelement: HTMLTableCellElement,
+}
+
+impl HTMLTableHeaderCellElementDerived for EventTarget {
+ fn is_htmltableheadercellelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableHeaderCellElementTypeId))
+ }
+}
+
+impl HTMLTableHeaderCellElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableHeaderCellElement {
+ HTMLTableHeaderCellElement {
+ htmltablecellelement: HTMLTableCellElement::new_inherited(HTMLTableHeaderCellElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableHeaderCellElement> {
+ let element = HTMLTableHeaderCellElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableHeaderCellElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableHeaderCellElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmltablecellelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltablerowelement.rs b/components/script/dom/htmltablerowelement.rs
new file mode 100644
index 00000000000..de8978c75cd
--- /dev/null
+++ b/components/script/dom/htmltablerowelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableRowElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableRowElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableRowElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableRowElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTableRowElementDerived for EventTarget {
+ fn is_htmltablerowelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableRowElementTypeId))
+ }
+}
+
+impl HTMLTableRowElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableRowElement {
+ HTMLTableRowElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTableRowElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableRowElement> {
+ let element = HTMLTableRowElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableRowElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableRowElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltablesectionelement.rs b/components/script/dom/htmltablesectionelement.rs
new file mode 100644
index 00000000000..1dc372862fc
--- /dev/null
+++ b/components/script/dom/htmltablesectionelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTableSectionElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTableSectionElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTableSectionElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTableSectionElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTableSectionElementDerived for EventTarget {
+ fn is_htmltablesectionelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTableSectionElementTypeId))
+ }
+}
+
+impl HTMLTableSectionElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTableSectionElement {
+ HTMLTableSectionElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTableSectionElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTableSectionElement> {
+ let element = HTMLTableSectionElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTableSectionElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTableSectionElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltemplateelement.rs b/components/script/dom/htmltemplateelement.rs
new file mode 100644
index 00000000000..12be7665169
--- /dev/null
+++ b/components/script/dom/htmltemplateelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTemplateElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTemplateElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTemplateElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTemplateElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTemplateElementDerived for EventTarget {
+ fn is_htmltemplateelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTemplateElementTypeId))
+ }
+}
+
+impl HTMLTemplateElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTemplateElement {
+ HTMLTemplateElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTemplateElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTemplateElement> {
+ let element = HTMLTemplateElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTemplateElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTemplateElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs
new file mode 100644
index 00000000000..5385fefab7e
--- /dev/null
+++ b/components/script/dom/htmltextareaelement.rs
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding;
+use dom::bindings::codegen::Bindings::HTMLTextAreaElementBinding::HTMLTextAreaElementMethods;
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLElementCast, NodeCast};
+use dom::bindings::codegen::InheritTypes::{HTMLTextAreaElementDerived, HTMLFieldSetElementDerived};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::{AttributeHandlers, Element, HTMLTextAreaElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{DisabledStateHelpers, Node, NodeHelpers, ElementNodeTypeId};
+use dom::virtualmethods::VirtualMethods;
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTextAreaElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTextAreaElementDerived for EventTarget {
+ fn is_htmltextareaelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTextAreaElementTypeId))
+ }
+}
+
+impl HTMLTextAreaElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTextAreaElement {
+ HTMLTextAreaElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTextAreaElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTextAreaElement> {
+ let element = HTMLTextAreaElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTextAreaElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLTextAreaElementMethods for JSRef<'a, HTMLTextAreaElement> {
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ make_bool_getter!(Disabled)
+
+ // http://www.whatwg.org/html/#dom-fe-disabled
+ fn SetDisabled(&self, disabled: bool) {
+ let elem: &JSRef<Element> = ElementCast::from_ref(self);
+ elem.set_bool_attribute("disabled", disabled)
+ }
+}
+
+impl<'a> VirtualMethods for JSRef<'a, HTMLTextAreaElement> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let htmlelement: &JSRef<HTMLElement> = HTMLElementCast::from_ref(self);
+ Some(htmlelement as &VirtualMethods)
+ }
+
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value.clone()),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(true);
+ node.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ match name.as_slice() {
+ "disabled" => {
+ node.set_disabled_state(false);
+ node.set_enabled_state(true);
+ node.check_ancestors_disabled_state_for_form_control();
+ },
+ _ => ()
+ }
+ }
+
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.check_ancestors_disabled_state_for_form_control();
+ }
+
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ if node.ancestors().any(|ancestor| ancestor.is_htmlfieldsetelement()) {
+ node.check_ancestors_disabled_state_for_form_control();
+ } else {
+ node.check_disabled_attribute();
+ }
+ }
+}
+
+impl Reflectable for HTMLTextAreaElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltimeelement.rs b/components/script/dom/htmltimeelement.rs
new file mode 100644
index 00000000000..8eeb695d4c6
--- /dev/null
+++ b/components/script/dom/htmltimeelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTimeElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTimeElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTimeElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTimeElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLTimeElementDerived for EventTarget {
+ fn is_htmltimeelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTimeElementTypeId))
+ }
+}
+
+impl HTMLTimeElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTimeElement {
+ HTMLTimeElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTimeElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTimeElement> {
+ let element = HTMLTimeElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTimeElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTimeElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltitleelement.rs b/components/script/dom/htmltitleelement.rs
new file mode 100644
index 00000000000..550a531aa76
--- /dev/null
+++ b/components/script/dom/htmltitleelement.rs
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTitleElementBinding;
+use dom::bindings::codegen::Bindings::HTMLTitleElementBinding::HTMLTitleElementMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::{HTMLTitleElementDerived, NodeCast, TextCast};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTitleElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+use dom::text::Text;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTitleElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTitleElementDerived for EventTarget {
+ fn is_htmltitleelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTitleElementTypeId))
+ }
+}
+
+impl HTMLTitleElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTitleElement {
+ HTMLTitleElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTitleElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTitleElement> {
+ let element = HTMLTitleElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTitleElementBinding::Wrap)
+ }
+}
+
+impl<'a> HTMLTitleElementMethods for JSRef<'a, HTMLTitleElement> {
+ // http://www.whatwg.org/html/#dom-title-text
+ fn Text(&self) -> DOMString {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ let mut content = String::new();
+ for child in node.children() {
+ let text: Option<&JSRef<Text>> = TextCast::to_ref(&child);
+ match text {
+ Some(text) => content.push_str(text.characterdata.data.borrow().as_slice()),
+ None => (),
+ }
+ }
+ content
+ }
+
+ // http://www.whatwg.org/html/#dom-title-text
+ fn SetText(&self, value: DOMString) {
+ let node: &JSRef<Node> = NodeCast::from_ref(self);
+ node.SetTextContent(Some(value))
+ }
+}
+
+impl Reflectable for HTMLTitleElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmltrackelement.rs b/components/script/dom/htmltrackelement.rs
new file mode 100644
index 00000000000..5d22571db67
--- /dev/null
+++ b/components/script/dom/htmltrackelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLTrackElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLTrackElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLTrackElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLTrackElement {
+ pub htmlelement: HTMLElement,
+}
+
+impl HTMLTrackElementDerived for EventTarget {
+ fn is_htmltrackelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLTrackElementTypeId))
+ }
+}
+
+impl HTMLTrackElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLTrackElement {
+ HTMLTrackElement {
+ htmlelement: HTMLElement::new_inherited(HTMLTrackElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLTrackElement> {
+ let element = HTMLTrackElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLTrackElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLTrackElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlulistelement.rs b/components/script/dom/htmlulistelement.rs
new file mode 100644
index 00000000000..228152cc663
--- /dev/null
+++ b/components/script/dom/htmlulistelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLUListElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLUListElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLUListElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLUListElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLUListElementDerived for EventTarget {
+ fn is_htmlulistelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLUListElementTypeId))
+ }
+}
+
+impl HTMLUListElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLUListElement {
+ HTMLUListElement {
+ htmlelement: HTMLElement::new_inherited(HTMLUListElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLUListElement> {
+ let element = HTMLUListElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLUListElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLUListElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlunknownelement.rs b/components/script/dom/htmlunknownelement.rs
new file mode 100644
index 00000000000..2956b0c459a
--- /dev/null
+++ b/components/script/dom/htmlunknownelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLUnknownElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLUnknownElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLUnknownElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLUnknownElement {
+ pub htmlelement: HTMLElement
+}
+
+impl HTMLUnknownElementDerived for EventTarget {
+ fn is_htmlunknownelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLUnknownElementTypeId))
+ }
+}
+
+impl HTMLUnknownElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLUnknownElement {
+ HTMLUnknownElement {
+ htmlelement: HTMLElement::new_inherited(HTMLUnknownElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLUnknownElement> {
+ let element = HTMLUnknownElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLUnknownElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLUnknownElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlelement.reflector()
+ }
+}
diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs
new file mode 100644
index 00000000000..365b9a38f20
--- /dev/null
+++ b/components/script/dom/htmlvideoelement.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::HTMLVideoElementBinding;
+use dom::bindings::codegen::InheritTypes::HTMLVideoElementDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::document::Document;
+use dom::element::HTMLVideoElementTypeId;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::htmlmediaelement::HTMLMediaElement;
+use dom::node::{Node, ElementNodeTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct HTMLVideoElement {
+ pub htmlmediaelement: HTMLMediaElement
+}
+
+impl HTMLVideoElementDerived for EventTarget {
+ fn is_htmlvideoelement(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ElementNodeTypeId(HTMLVideoElementTypeId))
+ }
+}
+
+impl HTMLVideoElement {
+ pub fn new_inherited(localName: DOMString, document: &JSRef<Document>) -> HTMLVideoElement {
+ HTMLVideoElement {
+ htmlmediaelement: HTMLMediaElement::new_inherited(HTMLVideoElementTypeId, localName, document)
+ }
+ }
+
+ pub fn new(localName: DOMString, document: &JSRef<Document>) -> Temporary<HTMLVideoElement> {
+ let element = HTMLVideoElement::new_inherited(localName, document);
+ Node::reflect_node(box element, document, HTMLVideoElementBinding::Wrap)
+ }
+}
+
+impl Reflectable for HTMLVideoElement {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.htmlmediaelement.reflector()
+ }
+}
diff --git a/components/script/dom/location.rs b/components/script/dom/location.rs
new file mode 100644
index 00000000000..e310c52d49e
--- /dev/null
+++ b/components/script/dom/location.rs
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::LocationBinding;
+use dom::bindings::codegen::Bindings::LocationBinding::LocationMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+use page::Page;
+
+use servo_util::str::DOMString;
+
+use std::rc::Rc;
+
+#[deriving(Encodable)]
+pub struct Location {
+ reflector_: Reflector, //XXXjdm cycle: window->Location->window
+ page: Rc<Page>,
+}
+
+impl Location {
+ pub fn new_inherited(page: Rc<Page>) -> Location {
+ Location {
+ reflector_: Reflector::new(),
+ page: page
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>, page: Rc<Page>) -> Temporary<Location> {
+ reflect_dom_object(box Location::new_inherited(page),
+ &Window(*window),
+ LocationBinding::Wrap)
+ }
+}
+
+impl<'a> LocationMethods for JSRef<'a, Location> {
+ fn Href(&self) -> DOMString {
+ self.page.get_url().serialize()
+ }
+
+ fn Search(&self) -> DOMString {
+ match self.page.get_url().query {
+ None => "".to_string(),
+ Some(ref query) if query.as_slice() == "" => "".to_string(),
+ Some(ref query) => "?".to_string().append(query.as_slice())
+ }
+ }
+
+ fn Hash(&self) -> DOMString {
+ match self.page.get_url().fragment {
+ None => "".to_string(),
+ Some(ref hash) if hash.as_slice() == "" => "".to_string(),
+ Some(ref hash) => "#".to_string().append(hash.as_slice())
+ }
+ }
+}
+
+impl Reflectable for Location {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs
new file mode 100644
index 00000000000..6cfca77593d
--- /dev/null
+++ b/components/script/dom/macros.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![macro_escape]
+
+#[macro_export]
+macro_rules! make_getter(
+ ( $attr:ident ) => (
+ fn $attr(&self) -> DOMString {
+ use dom::element::{Element, AttributeHandlers};
+ use dom::bindings::codegen::InheritTypes::ElementCast;
+ use std::ascii::StrAsciiExt;
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_string_attribute(stringify!($attr).to_ascii_lower().as_slice())
+ }
+ );
+)
+
+#[macro_export]
+macro_rules! make_bool_getter(
+ ( $attr:ident ) => (
+ fn $attr(&self) -> bool {
+ use dom::element::{Element, AttributeHandlers};
+ use dom::bindings::codegen::InheritTypes::ElementCast;
+ use std::ascii::StrAsciiExt;
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.has_attribute(stringify!($attr).to_ascii_lower().as_slice())
+ }
+ );
+)
+
+#[macro_export]
+macro_rules! make_uint_getter(
+ ( $attr:ident ) => (
+ fn $attr(&self) -> u32 {
+ use dom::element::{Element, AttributeHandlers};
+ use dom::bindings::codegen::InheritTypes::ElementCast;
+ use std::ascii::StrAsciiExt;
+ let element: &JSRef<Element> = ElementCast::from_ref(self);
+ element.get_uint_attribute(stringify!($attr).to_ascii_lower().as_slice())
+ }
+ );
+)
diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs
new file mode 100644
index 00000000000..13c8cf52dfd
--- /dev/null
+++ b/components/script/dom/messageevent.rs
@@ -0,0 +1,99 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::MessageEventBinding;
+use dom::bindings::codegen::Bindings::MessageEventBinding::MessageEventMethods;
+use dom::bindings::codegen::InheritTypes::{EventCast, MessageEventDerived};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::event::{Event, MessageEventTypeId};
+use dom::eventtarget::{EventTarget, EventTargetHelpers};
+
+use servo_util::str::DOMString;
+
+use js::jsapi::JSContext;
+use js::jsval::JSVal;
+
+#[deriving(Encodable)]
+pub struct MessageEvent {
+ event: Event,
+ data: Traceable<JSVal>,
+ origin: DOMString,
+ lastEventId: DOMString,
+}
+
+impl MessageEventDerived for Event {
+ fn is_messageevent(&self) -> bool {
+ self.type_id == MessageEventTypeId
+ }
+}
+
+impl MessageEvent {
+ pub fn new_inherited(data: JSVal, origin: DOMString, lastEventId: DOMString)
+ -> MessageEvent {
+ MessageEvent {
+ event: Event::new_inherited(MessageEventTypeId),
+ data: Traceable::new(data),
+ origin: origin,
+ lastEventId: lastEventId,
+ }
+ }
+
+ pub fn new(global: &GlobalRef, type_: DOMString,
+ bubbles: bool, cancelable: bool,
+ data: JSVal, origin: DOMString, lastEventId: DOMString)
+ -> Temporary<MessageEvent> {
+ let ev = reflect_dom_object(box MessageEvent::new_inherited(data, origin, lastEventId),
+ global,
+ MessageEventBinding::Wrap).root();
+ let event: &JSRef<Event> = EventCast::from_ref(&*ev);
+ event.InitEvent(type_, bubbles, cancelable);
+ Temporary::from_rooted(&*ev)
+ }
+
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &MessageEventBinding::MessageEventInit)
+ -> Fallible<Temporary<MessageEvent>> {
+ let ev = MessageEvent::new(global, type_, init.parent.bubbles, init.parent.cancelable,
+ init.data, init.origin.clone(), init.lastEventId.clone());
+ Ok(ev)
+ }
+}
+
+impl MessageEvent {
+ pub fn dispatch_jsval(target: &JSRef<EventTarget>,
+ scope: &GlobalRef,
+ message: JSVal) {
+ let messageevent = MessageEvent::new(
+ scope, "message".to_string(), false, false, message,
+ "".to_string(), "".to_string()).root();
+ let event: &JSRef<Event> = EventCast::from_ref(&*messageevent);
+ target.dispatch_event_with_target(None, &*event).unwrap();
+ }
+}
+
+impl<'a> MessageEventMethods for JSRef<'a, MessageEvent> {
+ fn Data(&self, _cx: *mut JSContext) -> JSVal {
+ *self.data
+ }
+
+ fn Origin(&self) -> DOMString {
+ self.origin.clone()
+ }
+
+ fn LastEventId(&self) -> DOMString {
+ self.lastEventId.clone()
+ }
+}
+
+impl Reflectable for MessageEvent {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.event.reflector()
+ }
+}
diff --git a/components/script/dom/mouseevent.rs b/components/script/dom/mouseevent.rs
new file mode 100644
index 00000000000..aa750b501ba
--- /dev/null
+++ b/components/script/dom/mouseevent.rs
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::MouseEventBinding;
+use dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
+use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
+use dom::bindings::codegen::InheritTypes::{UIEventCast, MouseEventDerived};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, OptionalSettable};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::event::{Event, MouseEventTypeId};
+use dom::eventtarget::EventTarget;
+use dom::uievent::UIEvent;
+use dom::window::Window;
+use servo_util::str::DOMString;
+use std::cell::Cell;
+
+#[deriving(Encodable)]
+pub struct MouseEvent {
+ pub mouseevent: UIEvent,
+ pub screen_x: Traceable<Cell<i32>>,
+ pub screen_y: Traceable<Cell<i32>>,
+ pub client_x: Traceable<Cell<i32>>,
+ pub client_y: Traceable<Cell<i32>>,
+ pub ctrl_key: Traceable<Cell<bool>>,
+ pub shift_key: Traceable<Cell<bool>>,
+ pub alt_key: Traceable<Cell<bool>>,
+ pub meta_key: Traceable<Cell<bool>>,
+ pub button: Traceable<Cell<i16>>,
+ pub related_target: Cell<Option<JS<EventTarget>>>
+}
+
+impl MouseEventDerived for Event {
+ fn is_mouseevent(&self) -> bool {
+ self.type_id == MouseEventTypeId
+ }
+}
+
+impl MouseEvent {
+ pub fn new_inherited() -> MouseEvent {
+ MouseEvent {
+ mouseevent: UIEvent::new_inherited(MouseEventTypeId),
+ screen_x: Traceable::new(Cell::new(0)),
+ screen_y: Traceable::new(Cell::new(0)),
+ client_x: Traceable::new(Cell::new(0)),
+ client_y: Traceable::new(Cell::new(0)),
+ ctrl_key: Traceable::new(Cell::new(false)),
+ shift_key: Traceable::new(Cell::new(false)),
+ alt_key: Traceable::new(Cell::new(false)),
+ meta_key: Traceable::new(Cell::new(false)),
+ button: Traceable::new(Cell::new(0)),
+ related_target: Cell::new(None)
+ }
+ }
+
+ pub fn new_uninitialized(window: &JSRef<Window>) -> Temporary<MouseEvent> {
+ reflect_dom_object(box MouseEvent::new_inherited(),
+ &Window(*window),
+ MouseEventBinding::Wrap)
+ }
+
+ pub fn new(window: &JSRef<Window>,
+ type_: DOMString,
+ canBubble: bool,
+ cancelable: bool,
+ view: Option<JSRef<Window>>,
+ detail: i32,
+ screenX: i32,
+ screenY: i32,
+ clientX: i32,
+ clientY: i32,
+ ctrlKey: bool,
+ altKey: bool,
+ shiftKey: bool,
+ metaKey: bool,
+ button: i16,
+ relatedTarget: Option<JSRef<EventTarget>>) -> Temporary<MouseEvent> {
+ let ev = MouseEvent::new_uninitialized(window).root();
+ ev.deref().InitMouseEvent(type_, canBubble, cancelable, view, detail,
+ screenX, screenY, clientX, clientY,
+ ctrlKey, altKey, shiftKey, metaKey,
+ button, relatedTarget);
+ Temporary::from_rooted(&*ev)
+ }
+
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &MouseEventBinding::MouseEventInit) -> Fallible<Temporary<MouseEvent>> {
+ let event = MouseEvent::new(global.as_window(), type_,
+ init.parent.parent.bubbles,
+ init.parent.parent.cancelable,
+ init.parent.view.root_ref(),
+ init.parent.detail,
+ init.screenX, init.screenY,
+ init.clientX, init.clientY, init.ctrlKey,
+ init.altKey, init.shiftKey, init.metaKey,
+ init.button, init.relatedTarget.root_ref());
+ Ok(event)
+ }
+}
+
+impl<'a> MouseEventMethods for JSRef<'a, MouseEvent> {
+ fn ScreenX(&self) -> i32 {
+ self.screen_x.deref().get()
+ }
+
+ fn ScreenY(&self) -> i32 {
+ self.screen_y.deref().get()
+ }
+
+ fn ClientX(&self) -> i32 {
+ self.client_x.deref().get()
+ }
+
+ fn ClientY(&self) -> i32 {
+ self.client_y.deref().get()
+ }
+
+ fn CtrlKey(&self) -> bool {
+ self.ctrl_key.deref().get()
+ }
+
+ fn ShiftKey(&self) -> bool {
+ self.shift_key.deref().get()
+ }
+
+ fn AltKey(&self) -> bool {
+ self.alt_key.deref().get()
+ }
+
+ fn MetaKey(&self) -> bool {
+ self.meta_key.deref().get()
+ }
+
+ fn Button(&self) -> i16 {
+ self.button.deref().get()
+ }
+
+ fn GetRelatedTarget(&self) -> Option<Temporary<EventTarget>> {
+ self.related_target.get().clone().map(|target| Temporary::new(target))
+ }
+
+ fn InitMouseEvent(&self,
+ typeArg: DOMString,
+ canBubbleArg: bool,
+ cancelableArg: bool,
+ viewArg: Option<JSRef<Window>>,
+ detailArg: i32,
+ screenXArg: i32,
+ screenYArg: i32,
+ clientXArg: i32,
+ clientYArg: i32,
+ ctrlKeyArg: bool,
+ altKeyArg: bool,
+ shiftKeyArg: bool,
+ metaKeyArg: bool,
+ buttonArg: i16,
+ relatedTargetArg: Option<JSRef<EventTarget>>) {
+ let uievent: &JSRef<UIEvent> = UIEventCast::from_ref(self);
+ uievent.InitUIEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg);
+ self.screen_x.deref().set(screenXArg);
+ self.screen_y.deref().set(screenYArg);
+ self.client_x.deref().set(clientXArg);
+ self.client_y.deref().set(clientYArg);
+ self.ctrl_key.deref().set(ctrlKeyArg);
+ self.alt_key.deref().set(altKeyArg);
+ self.shift_key.deref().set(shiftKeyArg);
+ self.meta_key.deref().set(metaKeyArg);
+ self.button.deref().set(buttonArg);
+ self.related_target.assign(relatedTargetArg);
+ }
+}
+
+
+impl Reflectable for MouseEvent {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.mouseevent.reflector()
+ }
+}
diff --git a/components/script/dom/namednodemap.rs b/components/script/dom/namednodemap.rs
new file mode 100644
index 00000000000..d60160133c9
--- /dev/null
+++ b/components/script/dom/namednodemap.rs
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::attr::Attr;
+use dom::bindings::codegen::Bindings::NamedNodeMapBinding;
+use dom::bindings::codegen::Bindings::NamedNodeMapBinding::NamedNodeMapMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::element::Element;
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub struct NamedNodeMap {
+ reflector_: Reflector,
+ owner: JS<Element>,
+}
+
+impl NamedNodeMap {
+ pub fn new_inherited(elem: &JSRef<Element>) -> NamedNodeMap {
+ NamedNodeMap {
+ reflector_: Reflector::new(),
+ owner: JS::from_rooted(elem),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>, elem: &JSRef<Element>) -> Temporary<NamedNodeMap> {
+ reflect_dom_object(box NamedNodeMap::new_inherited(elem),
+ &Window(*window), NamedNodeMapBinding::Wrap)
+ }
+}
+
+impl<'a> NamedNodeMapMethods for JSRef<'a, NamedNodeMap> {
+ fn Length(&self) -> u32 {
+ self.owner.root().attrs.borrow().len() as u32
+ }
+
+ fn Item(&self, index: u32) -> Option<Temporary<Attr>> {
+ self.owner.root().attrs.borrow().as_slice().get(index as uint).map(|x| Temporary::new(x.clone()))
+ }
+
+ fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Temporary<Attr>> {
+ let item = self.Item(index);
+ *found = item.is_some();
+ item
+ }
+}
+
+impl Reflectable for NamedNodeMap {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs
new file mode 100644
index 00000000000..d1d7596aa6e
--- /dev/null
+++ b/components/script/dom/navigator.rs
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::NavigatorBinding;
+use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct Navigator {
+ pub reflector_: Reflector //XXXjdm cycle: window->navigator->window
+}
+
+impl Navigator {
+ pub fn new_inherited() -> Navigator {
+ Navigator {
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<Navigator> {
+ reflect_dom_object(box Navigator::new_inherited(),
+ &Window(*window),
+ NavigatorBinding::Wrap)
+ }
+}
+
+impl<'a> NavigatorMethods for JSRef<'a, Navigator> {
+ fn Product(&self) -> DOMString {
+ "Gecko".to_string()
+ }
+
+ fn TaintEnabled(&self) -> bool {
+ false
+ }
+
+ fn AppName(&self) -> DOMString {
+ "Netscape".to_string() // Like Gecko/Webkit
+ }
+
+ fn AppCodeName(&self) -> DOMString {
+ "Mozilla".to_string()
+ }
+
+ fn Platform(&self) -> DOMString {
+ "".to_string()
+ }
+}
+
+impl Reflectable for Navigator {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
new file mode 100644
index 00000000000..96ee5f62ba2
--- /dev/null
+++ b/components/script/dom/node.rs
@@ -0,0 +1,2085 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The core DOM types. Defines the basic DOM hierarchy as well as all the HTML elements.
+
+use dom::attr::Attr;
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods;
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods};
+use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
+use dom::bindings::codegen::InheritTypes::{CommentCast, DocumentCast, DocumentTypeCast};
+use dom::bindings::codegen::InheritTypes::{ElementCast, TextCast, NodeCast, ElementDerived};
+use dom::bindings::codegen::InheritTypes::{CharacterDataCast, NodeBase, NodeDerived};
+use dom::bindings::codegen::InheritTypes::{ProcessingInstructionCast, EventTargetCast};
+use dom::bindings::codegen::InheritTypes::{HTMLLegendElementDerived, HTMLFieldSetElementDerived};
+use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementDerived;
+use dom::bindings::error::{Fallible, NotFound, HierarchyRequest, Syntax};
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, Root, OptionalUnrootable};
+use dom::bindings::js::{OptionalSettable, TemporaryPushable, OptionalRootedRootable};
+use dom::bindings::js::{ResultRootable, OptionalRootable};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::characterdata::CharacterData;
+use dom::comment::Comment;
+use dom::document::{Document, DocumentHelpers, HTMLDocument, NonHTMLDocument};
+use dom::documentfragment::DocumentFragment;
+use dom::documenttype::DocumentType;
+use dom::element::{AttributeHandlers, Element, ElementTypeId};
+use dom::element::{HTMLAnchorElementTypeId, HTMLButtonElementTypeId, ElementHelpers};
+use dom::element::{HTMLInputElementTypeId, HTMLSelectElementTypeId};
+use dom::element::{HTMLTextAreaElementTypeId, HTMLOptGroupElementTypeId};
+use dom::element::{HTMLOptionElementTypeId, HTMLFieldSetElementTypeId};
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::nodelist::{NodeList};
+use dom::processinginstruction::ProcessingInstruction;
+use dom::text::Text;
+use dom::virtualmethods::{VirtualMethods, vtable_for};
+use dom::window::Window;
+use geom::rect::Rect;
+use html::hubbub_html_parser::build_element_from_tag;
+use layout_interface::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC,
+ LayoutChan, ReapLayoutDataMsg, TrustedNodeAddress, UntrustedNodeAddress};
+use servo_util::geometry::Au;
+use servo_util::str::{DOMString, null_str_as_empty};
+use style::{parse_selector_list_from_str, matches};
+
+use js::jsapi::{JSContext, JSObject, JSRuntime};
+use js::jsfriendapi;
+use libc;
+use libc::uintptr_t;
+use std::cell::{Cell, RefCell, Ref, RefMut};
+use std::iter::{Map, Filter};
+use std::mem;
+use style;
+use style::ComputedValues;
+use sync::Arc;
+
+use serialize::{Encoder, Encodable};
+
+//
+// The basic Node structure
+//
+
+/// An HTML node.
+#[deriving(Encodable)]
+pub struct Node {
+ /// The JavaScript reflector for this node.
+ pub eventtarget: EventTarget,
+
+ /// The type of node that this is.
+ type_id: NodeTypeId,
+
+ /// The parent of this node.
+ parent_node: Cell<Option<JS<Node>>>,
+
+ /// The first child of this node.
+ first_child: Cell<Option<JS<Node>>>,
+
+ /// The last child of this node.
+ last_child: Cell<Option<JS<Node>>>,
+
+ /// The next sibling of this node.
+ next_sibling: Cell<Option<JS<Node>>>,
+
+ /// The previous sibling of this node.
+ prev_sibling: Cell<Option<JS<Node>>>,
+
+ /// The document that this node belongs to.
+ owner_doc: Cell<Option<JS<Document>>>,
+
+ /// The live list of children return by .childNodes.
+ child_list: Cell<Option<JS<NodeList>>>,
+
+ /// A bitfield of flags for node items.
+ flags: Traceable<RefCell<NodeFlags>>,
+
+ /// Layout information. Only the layout task may touch this data.
+ ///
+ /// Must be sent back to the layout task to be destroyed when this
+ /// node is finalized.
+ pub layout_data: LayoutDataRef,
+}
+
+impl<S: Encoder<E>, E> Encodable<S, E> for LayoutDataRef {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+impl NodeDerived for EventTarget {
+ fn is_node(&self) -> bool {
+ match self.type_id {
+ NodeTargetTypeId(_) => true,
+ _ => false
+ }
+ }
+}
+
+bitflags! {
+ #[doc = "Flags for node items."]
+ #[deriving(Encodable)]
+ flags NodeFlags: u8 {
+ #[doc = "Specifies whether this node is in a document."]
+ static IsInDoc = 0x01,
+ #[doc = "Specifies whether this node is in hover state."]
+ static InHoverState = 0x02,
+ #[doc = "Specifies whether this node is in disabled state."]
+ static InDisabledState = 0x04,
+ #[doc = "Specifies whether this node is in enabled state."]
+ static InEnabledState = 0x08
+ }
+}
+
+impl NodeFlags {
+ pub fn new(type_id: NodeTypeId) -> NodeFlags {
+ match type_id {
+ DocumentNodeTypeId => IsInDoc,
+ // The following elements are enabled by default.
+ ElementNodeTypeId(HTMLButtonElementTypeId) |
+ ElementNodeTypeId(HTMLInputElementTypeId) |
+ ElementNodeTypeId(HTMLSelectElementTypeId) |
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) |
+ ElementNodeTypeId(HTMLOptGroupElementTypeId) |
+ ElementNodeTypeId(HTMLOptionElementTypeId) |
+ //ElementNodeTypeId(HTMLMenuItemElementTypeId) |
+ ElementNodeTypeId(HTMLFieldSetElementTypeId) => InEnabledState,
+ _ => NodeFlags::empty(),
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for Node {
+ fn drop(&mut self) {
+ unsafe {
+ self.reap_layout_data();
+ }
+ }
+}
+
+/// suppress observers flag
+/// http://dom.spec.whatwg.org/#concept-node-insert
+/// http://dom.spec.whatwg.org/#concept-node-remove
+enum SuppressObserver {
+ Suppressed,
+ Unsuppressed
+}
+
+/// Layout data that is shared between the script and layout tasks.
+pub struct SharedLayoutData {
+ /// The results of CSS styling for this node.
+ pub style: Option<Arc<ComputedValues>>,
+}
+
+/// Encapsulates the abstract layout data.
+pub struct LayoutData {
+ chan: Option<LayoutChan>,
+ _shared_data: SharedLayoutData,
+ _data: *const (),
+}
+
+pub struct LayoutDataRef {
+ pub data_cell: RefCell<Option<LayoutData>>,
+}
+
+impl LayoutDataRef {
+ pub fn new() -> LayoutDataRef {
+ LayoutDataRef {
+ data_cell: RefCell::new(None),
+ }
+ }
+
+ /// Returns true if there is layout data present.
+ #[inline]
+ pub fn is_present(&self) -> bool {
+ self.data_cell.borrow().is_some()
+ }
+
+ /// Take the chan out of the layout data if it is present.
+ pub fn take_chan(&self) -> Option<LayoutChan> {
+ let mut layout_data = self.data_cell.borrow_mut();
+ match *layout_data {
+ None => None,
+ Some(..) => Some(layout_data.get_mut_ref().chan.take_unwrap()),
+ }
+ }
+
+ /// Borrows the layout data immutably, *asserting that there are no mutators*. Bad things will
+ /// happen if you try to mutate the layout data while this is held. This is the only thread-
+ /// safe layout data accessor.
+ #[inline]
+ pub unsafe fn borrow_unchecked(&self) -> *const Option<LayoutData> {
+ mem::transmute(&self.data_cell)
+ }
+
+ /// Borrows the layout data immutably. This function is *not* thread-safe.
+ #[inline]
+ pub fn borrow<'a>(&'a self) -> Ref<'a,Option<LayoutData>> {
+ self.data_cell.borrow()
+ }
+
+ /// Borrows the layout data mutably. This function is *not* thread-safe.
+ ///
+ /// FIXME(pcwalton): We should really put this behind a `MutLayoutView` phantom type, to
+ /// prevent CSS selector matching from mutably accessing nodes it's not supposed to and racing
+ /// on it. This has already resulted in one bug!
+ #[inline]
+ pub fn borrow_mut<'a>(&'a self) -> RefMut<'a,Option<LayoutData>> {
+ self.data_cell.borrow_mut()
+ }
+}
+
+/// The different types of nodes.
+#[deriving(PartialEq,Encodable)]
+pub enum NodeTypeId {
+ DoctypeNodeTypeId,
+ DocumentFragmentNodeTypeId,
+ CommentNodeTypeId,
+ DocumentNodeTypeId,
+ ElementNodeTypeId(ElementTypeId),
+ TextNodeTypeId,
+ ProcessingInstructionNodeTypeId,
+}
+
+trait PrivateNodeHelpers {
+ fn node_inserted(&self);
+ fn node_removed(&self, parent_in_doc: bool);
+ fn add_child(&self, new_child: &JSRef<Node>, before: Option<JSRef<Node>>);
+ fn remove_child(&self, child: &JSRef<Node>);
+}
+
+impl<'a> PrivateNodeHelpers for JSRef<'a, Node> {
+ // http://dom.spec.whatwg.org/#node-is-inserted
+ fn node_inserted(&self) {
+ assert!(self.parent_node().is_some());
+ let document = document_from_node(self).root();
+ let is_in_doc = self.is_in_doc();
+
+ for node in self.traverse_preorder() {
+ vtable_for(&node).bind_to_tree(is_in_doc);
+ }
+
+ let parent = self.parent_node().root();
+ parent.map(|parent| vtable_for(&*parent).child_inserted(self));
+
+ document.deref().content_changed();
+ }
+
+ // http://dom.spec.whatwg.org/#node-is-removed
+ fn node_removed(&self, parent_in_doc: bool) {
+ assert!(self.parent_node().is_none());
+ let document = document_from_node(self).root();
+
+ for node in self.traverse_preorder() {
+ vtable_for(&node).unbind_from_tree(parent_in_doc);
+ }
+
+ document.deref().content_changed();
+ }
+
+ //
+ // Pointer stitching
+ //
+
+ /// Adds a new child to the end of this node's list of children.
+ ///
+ /// Fails unless `new_child` is disconnected from the tree.
+ fn add_child(&self, new_child: &JSRef<Node>, before: Option<JSRef<Node>>) {
+ let doc = self.owner_doc().root();
+ doc.deref().wait_until_safe_to_modify_dom();
+
+ assert!(new_child.parent_node().is_none());
+ assert!(new_child.prev_sibling().is_none());
+ assert!(new_child.next_sibling().is_none());
+ match before {
+ Some(ref before) => {
+ assert!(before.parent_node().root().root_ref() == Some(*self));
+ match before.prev_sibling().root() {
+ None => {
+ assert!(Some(*before) == self.first_child().root().root_ref());
+ self.first_child.assign(Some(*new_child));
+ },
+ Some(ref prev_sibling) => {
+ prev_sibling.next_sibling.assign(Some(*new_child));
+ new_child.prev_sibling.assign(Some(**prev_sibling));
+ },
+ }
+ before.prev_sibling.assign(Some(*new_child));
+ new_child.next_sibling.assign(Some(*before));
+ },
+ None => {
+ match self.last_child().root() {
+ None => self.first_child.assign(Some(*new_child)),
+ Some(ref last_child) => {
+ assert!(last_child.next_sibling().is_none());
+ last_child.next_sibling.assign(Some(*new_child));
+ new_child.prev_sibling.assign(Some(**last_child));
+ }
+ }
+
+ self.last_child.assign(Some(*new_child));
+ },
+ }
+
+ new_child.parent_node.assign(Some(*self));
+ }
+
+ /// Removes the given child from this node's list of children.
+ ///
+ /// Fails unless `child` is a child of this node.
+ fn remove_child(&self, child: &JSRef<Node>) {
+ let doc = self.owner_doc().root();
+ doc.deref().wait_until_safe_to_modify_dom();
+
+ assert!(child.parent_node().root().root_ref() == Some(*self));
+
+ match child.prev_sibling.get().root() {
+ None => {
+ self.first_child.assign(child.next_sibling.get());
+ }
+ Some(ref prev_sibling) => {
+ prev_sibling.next_sibling.assign(child.next_sibling.get());
+ }
+ }
+
+ match child.next_sibling.get().root() {
+ None => {
+ self.last_child.assign(child.prev_sibling.get());
+ }
+ Some(ref next_sibling) => {
+ next_sibling.prev_sibling.assign(child.prev_sibling.get());
+ }
+ }
+
+ child.prev_sibling.set(None);
+ child.next_sibling.set(None);
+ child.parent_node.set(None);
+ }
+}
+
+pub trait NodeHelpers<'m, 'n> {
+ fn ancestors(&self) -> AncestorIterator<'n>;
+ fn children(&self) -> AbstractNodeChildrenIterator<'n>;
+ fn child_elements(&self) -> ChildElementIterator<'m, 'n>;
+ fn following_siblings(&self) -> AbstractNodeChildrenIterator<'n>;
+ fn is_in_doc(&self) -> bool;
+ fn is_inclusive_ancestor_of(&self, parent: &JSRef<Node>) -> bool;
+ fn is_parent_of(&self, child: &JSRef<Node>) -> bool;
+
+ fn type_id(&self) -> NodeTypeId;
+
+ fn parent_node(&self) -> Option<Temporary<Node>>;
+ fn first_child(&self) -> Option<Temporary<Node>>;
+ fn last_child(&self) -> Option<Temporary<Node>>;
+ fn prev_sibling(&self) -> Option<Temporary<Node>>;
+ fn next_sibling(&self) -> Option<Temporary<Node>>;
+
+ fn owner_doc(&self) -> Temporary<Document>;
+ fn set_owner_doc(&self, document: &JSRef<Document>);
+ fn is_in_html_doc(&self) -> bool;
+
+ fn wait_until_safe_to_modify_dom(&self);
+
+ fn is_element(&self) -> bool;
+ fn is_document(&self) -> bool;
+ fn is_doctype(&self) -> bool;
+ fn is_text(&self) -> bool;
+ fn is_anchor_element(&self) -> bool;
+
+ fn get_hover_state(&self) -> bool;
+ fn set_hover_state(&self, state: bool);
+
+ fn get_disabled_state(&self) -> bool;
+ fn set_disabled_state(&self, state: bool);
+
+ fn get_enabled_state(&self) -> bool;
+ fn set_enabled_state(&self, state: bool);
+
+ fn dump(&self);
+ fn dump_indent(&self, indent: uint);
+ fn debug_str(&self) -> String;
+
+ fn traverse_preorder(&self) -> TreeIterator<'n>;
+ fn sequential_traverse_postorder(&self) -> TreeIterator<'n>;
+ fn inclusively_following_siblings(&self) -> AbstractNodeChildrenIterator<'n>;
+
+ fn to_trusted_node_address(&self) -> TrustedNodeAddress;
+
+ fn get_bounding_content_box(&self) -> Rect<Au>;
+ fn get_content_boxes(&self) -> Vec<Rect<Au>>;
+
+ fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>>;
+ fn query_selector_all(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>>;
+
+ fn remove_self(&self);
+}
+
+impl<'m, 'n> NodeHelpers<'m, 'n> for JSRef<'n, Node> {
+ /// Dumps the subtree rooted at this node, for debugging.
+ fn dump(&self) {
+ self.dump_indent(0);
+ }
+
+ /// Dumps the node tree, for debugging, with indentation.
+ fn dump_indent(&self, indent: uint) {
+ let mut s = String::new();
+ for _ in range(0, indent) {
+ s.push_str(" ");
+ }
+
+ s.push_str(self.debug_str().as_slice());
+ debug!("{:s}", s);
+
+ // FIXME: this should have a pure version?
+ for kid in self.children() {
+ kid.dump_indent(indent + 1u)
+ }
+ }
+
+ /// Returns a string that describes this node.
+ fn debug_str(&self) -> String {
+ format!("{:?}", self.type_id)
+ }
+
+ fn is_in_doc(&self) -> bool {
+ self.deref().flags.deref().borrow().contains(IsInDoc)
+ }
+
+ /// Returns the type ID of this node. Fails if this node is borrowed mutably.
+ fn type_id(&self) -> NodeTypeId {
+ self.deref().type_id
+ }
+
+ fn parent_node(&self) -> Option<Temporary<Node>> {
+ self.deref().parent_node.get().map(|node| Temporary::new(node))
+ }
+
+ fn first_child(&self) -> Option<Temporary<Node>> {
+ self.deref().first_child.get().map(|node| Temporary::new(node))
+ }
+
+ fn last_child(&self) -> Option<Temporary<Node>> {
+ self.deref().last_child.get().map(|node| Temporary::new(node))
+ }
+
+ /// Returns the previous sibling of this node. Fails if this node is borrowed mutably.
+ fn prev_sibling(&self) -> Option<Temporary<Node>> {
+ self.deref().prev_sibling.get().map(|node| Temporary::new(node))
+ }
+
+ /// Returns the next sibling of this node. Fails if this node is borrowed mutably.
+ fn next_sibling(&self) -> Option<Temporary<Node>> {
+ self.deref().next_sibling.get().map(|node| Temporary::new(node))
+ }
+
+ #[inline]
+ fn is_element(&self) -> bool {
+ match self.type_id {
+ ElementNodeTypeId(..) => true,
+ _ => false
+ }
+ }
+
+ #[inline]
+ fn is_document(&self) -> bool {
+ self.type_id == DocumentNodeTypeId
+ }
+
+ #[inline]
+ fn is_anchor_element(&self) -> bool {
+ self.type_id == ElementNodeTypeId(HTMLAnchorElementTypeId)
+ }
+
+ #[inline]
+ fn is_doctype(&self) -> bool {
+ self.type_id == DoctypeNodeTypeId
+ }
+
+ #[inline]
+ fn is_text(&self) -> bool {
+ self.type_id == TextNodeTypeId
+ }
+
+ fn get_hover_state(&self) -> bool {
+ self.flags.deref().borrow().contains(InHoverState)
+ }
+
+ fn set_hover_state(&self, state: bool) {
+ if state {
+ self.flags.deref().borrow_mut().insert(InHoverState);
+ } else {
+ self.flags.deref().borrow_mut().remove(InHoverState);
+ }
+ }
+
+ fn get_disabled_state(&self) -> bool {
+ self.flags.deref().borrow().contains(InDisabledState)
+ }
+
+ fn set_disabled_state(&self, state: bool) {
+ if state {
+ self.flags.deref().borrow_mut().insert(InDisabledState);
+ } else {
+ self.flags.deref().borrow_mut().remove(InDisabledState);
+ }
+ }
+
+ fn get_enabled_state(&self) -> bool {
+ self.flags.deref().borrow().contains(InEnabledState)
+ }
+
+ fn set_enabled_state(&self, state: bool) {
+ if state {
+ self.flags.deref().borrow_mut().insert(InEnabledState);
+ } else {
+ self.flags.deref().borrow_mut().remove(InEnabledState);
+ }
+ }
+
+ /// Iterates over this node and all its descendants, in preorder.
+ fn traverse_preorder(&self) -> TreeIterator<'n> {
+ let mut nodes = vec!();
+ gather_abstract_nodes(self, &mut nodes, false);
+ TreeIterator::new(nodes)
+ }
+
+ /// Iterates over this node and all its descendants, in postorder.
+ fn sequential_traverse_postorder(&self) -> TreeIterator<'n> {
+ let mut nodes = vec!();
+ gather_abstract_nodes(self, &mut nodes, true);
+ TreeIterator::new(nodes)
+ }
+
+ fn inclusively_following_siblings(&self) -> AbstractNodeChildrenIterator<'n> {
+ AbstractNodeChildrenIterator {
+ current_node: Some(self.clone()),
+ }
+ }
+
+ fn is_inclusive_ancestor_of(&self, parent: &JSRef<Node>) -> bool {
+ self == parent || parent.ancestors().any(|ancestor| &ancestor == self)
+ }
+
+ fn following_siblings(&self) -> AbstractNodeChildrenIterator<'n> {
+ AbstractNodeChildrenIterator {
+ current_node: self.next_sibling().root().map(|next| next.deref().clone()),
+ }
+ }
+
+ fn is_parent_of(&self, child: &JSRef<Node>) -> bool {
+ match child.parent_node() {
+ Some(ref parent) if *parent == Temporary::from_rooted(self) => true,
+ _ => false
+ }
+ }
+
+ fn to_trusted_node_address(&self) -> TrustedNodeAddress {
+ TrustedNodeAddress(self.deref() as *const Node as *const libc::c_void)
+ }
+
+ fn get_bounding_content_box(&self) -> Rect<Au> {
+ let window = window_from_node(self).root();
+ let page = window.deref().page();
+ let addr = self.to_trusted_node_address();
+
+ let ContentBoxResponse(rect) = page.layout_rpc.content_box(addr);
+ rect
+ }
+
+ fn get_content_boxes(&self) -> Vec<Rect<Au>> {
+ let window = window_from_node(self).root();
+ let page = window.deref().page();
+ let addr = self.to_trusted_node_address();
+ let ContentBoxesResponse(rects) = page.layout_rpc.content_boxes(addr);
+ rects
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselector
+ fn query_selector(&self, selectors: DOMString) -> Fallible<Option<Temporary<Element>>> {
+ // Step 1.
+ match parse_selector_list_from_str(selectors.as_slice()) {
+ // Step 2.
+ Err(()) => return Err(Syntax),
+ // Step 3.
+ Ok(ref selectors) => {
+ let root = self.ancestors().last().unwrap_or(self.clone());
+ for node in root.traverse_preorder() {
+ if node.is_element() && matches(selectors, &node) {
+ let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
+ return Ok(Some(Temporary::from_rooted(elem)));
+ }
+ }
+ }
+ }
+ Ok(None)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-parentnode-queryselectorall
+ fn query_selector_all(&self, selectors: DOMString) -> Fallible<Temporary<NodeList>> {
+ // Step 1.
+ let nodes;
+ let root = self.ancestors().last().unwrap_or(self.clone());
+ match parse_selector_list_from_str(selectors.as_slice()) {
+ // Step 2.
+ Err(()) => return Err(Syntax),
+ // Step 3.
+ Ok(ref selectors) => {
+ nodes = root.traverse_preorder().filter(
+ |node| node.is_element() && matches(selectors, node)).collect()
+ }
+ }
+ let window = window_from_node(self).root();
+ Ok(NodeList::new_simple_list(&window.root_ref(), nodes))
+ }
+
+ fn ancestors(&self) -> AncestorIterator<'n> {
+ AncestorIterator {
+ current: self.parent_node.get().map(|node| (*node.root()).clone()),
+ }
+ }
+
+ fn owner_doc(&self) -> Temporary<Document> {
+ Temporary::new(self.owner_doc.get().get_ref().clone())
+ }
+
+ fn set_owner_doc(&self, document: &JSRef<Document>) {
+ self.owner_doc.assign(Some(document.clone()));
+ }
+
+ fn is_in_html_doc(&self) -> bool {
+ self.owner_doc().root().is_html_document
+ }
+
+ fn children(&self) -> AbstractNodeChildrenIterator<'n> {
+ AbstractNodeChildrenIterator {
+ current_node: self.first_child.get().map(|node| (*node.root()).clone()),
+ }
+ }
+
+ fn child_elements(&self) -> ChildElementIterator<'m, 'n> {
+ self.children()
+ .filter(|node| {
+ node.is_element()
+ })
+ .map(|node| {
+ let elem: &JSRef<Element> = ElementCast::to_ref(&node).unwrap();
+ elem.clone()
+ })
+ }
+
+ fn wait_until_safe_to_modify_dom(&self) {
+ let document = self.owner_doc().root();
+ document.deref().wait_until_safe_to_modify_dom();
+ }
+
+ fn remove_self(&self) {
+ match self.parent_node().root() {
+ Some(ref parent) => parent.remove_child(self),
+ None => ()
+ }
+ }
+}
+
+/// If the given untrusted node address represents a valid DOM node in the given runtime,
+/// returns it.
+pub fn from_untrusted_node_address(runtime: *mut JSRuntime, candidate: UntrustedNodeAddress)
+ -> Temporary<Node> {
+ unsafe {
+ let candidate: uintptr_t = mem::transmute(candidate);
+ let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime,
+ candidate);
+ if object.is_null() {
+ fail!("Attempted to create a `JS<Node>` from an invalid pointer!")
+ }
+ let boxed_node: *const Node = utils::unwrap(object);
+ Temporary::new(JS::from_raw(boxed_node))
+ }
+}
+
+pub trait LayoutNodeHelpers {
+ unsafe fn type_id_for_layout(&self) -> NodeTypeId;
+
+ unsafe fn parent_node_ref(&self) -> Option<JS<Node>>;
+ unsafe fn first_child_ref(&self) -> Option<JS<Node>>;
+ unsafe fn last_child_ref(&self) -> Option<JS<Node>>;
+ unsafe fn prev_sibling_ref(&self) -> Option<JS<Node>>;
+ unsafe fn next_sibling_ref(&self) -> Option<JS<Node>>;
+
+ unsafe fn owner_doc_for_layout(&self) -> JS<Document>;
+
+ unsafe fn is_element_for_layout(&self) -> bool;
+}
+
+impl LayoutNodeHelpers for JS<Node> {
+ #[inline]
+ unsafe fn type_id_for_layout(&self) -> NodeTypeId {
+ (*self.unsafe_get()).type_id
+ }
+
+ #[inline]
+ unsafe fn is_element_for_layout(&self) -> bool {
+ (*self.unsafe_get()).is_element()
+ }
+
+ #[inline]
+ unsafe fn parent_node_ref(&self) -> Option<JS<Node>> {
+ (*self.unsafe_get()).parent_node.get()
+ }
+
+ #[inline]
+ unsafe fn first_child_ref(&self) -> Option<JS<Node>> {
+ (*self.unsafe_get()).first_child.get()
+ }
+
+ #[inline]
+ unsafe fn last_child_ref(&self) -> Option<JS<Node>> {
+ (*self.unsafe_get()).last_child.get()
+ }
+
+ #[inline]
+ unsafe fn prev_sibling_ref(&self) -> Option<JS<Node>> {
+ (*self.unsafe_get()).prev_sibling.get()
+ }
+
+ #[inline]
+ unsafe fn next_sibling_ref(&self) -> Option<JS<Node>> {
+ (*self.unsafe_get()).next_sibling.get()
+ }
+
+ #[inline]
+ unsafe fn owner_doc_for_layout(&self) -> JS<Document> {
+ (*self.unsafe_get()).owner_doc.get().unwrap()
+ }
+}
+
+pub trait RawLayoutNodeHelpers {
+ unsafe fn get_hover_state_for_layout(&self) -> bool;
+ unsafe fn get_disabled_state_for_layout(&self) -> bool;
+ unsafe fn get_enabled_state_for_layout(&self) -> bool;
+ fn type_id_for_layout(&self) -> NodeTypeId;
+}
+
+impl RawLayoutNodeHelpers for Node {
+ unsafe fn get_hover_state_for_layout(&self) -> bool {
+ (*self.unsafe_get_flags()).contains(InHoverState)
+ }
+ unsafe fn get_disabled_state_for_layout(&self) -> bool {
+ (*self.unsafe_get_flags()).contains(InDisabledState)
+ }
+ unsafe fn get_enabled_state_for_layout(&self) -> bool {
+ (*self.unsafe_get_flags()).contains(InEnabledState)
+ }
+
+ fn type_id_for_layout(&self) -> NodeTypeId {
+ self.type_id
+ }
+}
+
+
+//
+// Iteration and traversal
+//
+
+pub type ChildElementIterator<'a, 'b> = Map<'a, JSRef<'b, Node>,
+ JSRef<'b, Element>,
+ Filter<'a, JSRef<'b, Node>, AbstractNodeChildrenIterator<'b>>>;
+
+pub struct AbstractNodeChildrenIterator<'a> {
+ current_node: Option<JSRef<'a, Node>>,
+}
+
+impl<'a> Iterator<JSRef<'a, Node>> for AbstractNodeChildrenIterator<'a> {
+ fn next(&mut self) -> Option<JSRef<'a, Node>> {
+ let node = self.current_node.clone();
+ self.current_node = node.clone().and_then(|node| {
+ node.next_sibling().map(|node| (*node.root()).clone())
+ });
+ node
+ }
+}
+
+pub struct AncestorIterator<'a> {
+ current: Option<JSRef<'a, Node>>,
+}
+
+impl<'a> Iterator<JSRef<'a, Node>> for AncestorIterator<'a> {
+ fn next(&mut self) -> Option<JSRef<'a, Node>> {
+ if self.current.is_none() {
+ return None;
+ }
+
+ // FIXME: Do we need two clones here?
+ let x = self.current.get_ref().clone();
+ self.current = x.parent_node().map(|node| (*node.root()).clone());
+ Some(x)
+ }
+}
+
+// FIXME: Do this without precomputing a vector of refs.
+// Easy for preorder; harder for postorder.
+pub struct TreeIterator<'a> {
+ nodes: Vec<JSRef<'a, Node>>,
+ index: uint,
+}
+
+impl<'a> TreeIterator<'a> {
+ fn new(nodes: Vec<JSRef<'a, Node>>) -> TreeIterator<'a> {
+ TreeIterator {
+ nodes: nodes,
+ index: 0,
+ }
+ }
+}
+
+impl<'a> Iterator<JSRef<'a, Node>> for TreeIterator<'a> {
+ fn next(&mut self) -> Option<JSRef<'a, Node>> {
+ if self.index >= self.nodes.len() {
+ None
+ } else {
+ let v = self.nodes[self.index];
+ let v = v.clone();
+ self.index += 1;
+ Some(v)
+ }
+ }
+}
+
+pub struct NodeIterator {
+ pub start_node: JS<Node>,
+ pub current_node: Option<JS<Node>>,
+ pub depth: uint,
+ include_start: bool,
+ include_descendants_of_void: bool
+}
+
+impl NodeIterator {
+ pub fn new<'a>(start_node: &JSRef<'a, Node>,
+ include_start: bool,
+ include_descendants_of_void: bool) -> NodeIterator {
+ NodeIterator {
+ start_node: JS::from_rooted(start_node),
+ current_node: None,
+ depth: 0,
+ include_start: include_start,
+ include_descendants_of_void: include_descendants_of_void
+ }
+ }
+
+ fn next_child<'b>(&self, node: &JSRef<'b, Node>) -> Option<JSRef<'b, Node>> {
+ if !self.include_descendants_of_void && node.is_element() {
+ let elem: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ if elem.deref().is_void() {
+ None
+ } else {
+ node.first_child().map(|child| (*child.root()).clone())
+ }
+ } else {
+ node.first_child().map(|child| (*child.root()).clone())
+ }
+ }
+}
+
+impl<'a> Iterator<JSRef<'a, Node>> for NodeIterator {
+ fn next(&mut self) -> Option<JSRef<'a, Node>> {
+ self.current_node = match self.current_node.as_ref().map(|node| node.root()) {
+ None => {
+ if self.include_start {
+ Some(self.start_node)
+ } else {
+ self.next_child(&*self.start_node.root())
+ .map(|child| JS::from_rooted(&child))
+ }
+ },
+ Some(node) => {
+ match self.next_child(&*node) {
+ Some(child) => {
+ self.depth += 1;
+ Some(JS::from_rooted(&child))
+ },
+ None if JS::from_rooted(&*node) == self.start_node => None,
+ None => {
+ match node.deref().next_sibling().root() {
+ Some(sibling) => Some(JS::from_rooted(&*sibling)),
+ None => {
+ let mut candidate = node.deref().clone();
+ while candidate.next_sibling().is_none() {
+ candidate = (*candidate.parent_node()
+ .expect("Got to root without reaching start node")
+ .root()).clone();
+ self.depth -= 1;
+ if JS::from_rooted(&candidate) == self.start_node {
+ break;
+ }
+ }
+ if JS::from_rooted(&candidate) != self.start_node {
+ candidate.next_sibling().map(|node| JS::from_rooted(node.root().deref()))
+ } else {
+ None
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ self.current_node.map(|node| (*node.root()).clone())
+ }
+}
+
+fn gather_abstract_nodes<'a>(cur: &JSRef<'a, Node>, refs: &mut Vec<JSRef<'a, Node>>, postorder: bool) {
+ if !postorder {
+ refs.push(cur.clone());
+ }
+ for kid in cur.children() {
+ gather_abstract_nodes(&kid, refs, postorder)
+ }
+ if postorder {
+ refs.push(cur.clone());
+ }
+}
+
+/// Specifies whether children must be recursively cloned or not.
+#[deriving(PartialEq)]
+pub enum CloneChildrenFlag {
+ CloneChildren,
+ DoNotCloneChildren
+}
+
+fn as_uintptr<T>(t: &T) -> uintptr_t { t as *const T as uintptr_t }
+
+impl Node {
+ pub fn reflect_node<N: Reflectable+NodeBase>
+ (node: Box<N>,
+ document: &JSRef<Document>,
+ wrap_fn: extern "Rust" fn(*mut JSContext, &GlobalRef, Box<N>) -> Temporary<N>)
+ -> Temporary<N> {
+ let window = document.window.root();
+ reflect_dom_object(node, &Window(*window), wrap_fn)
+ }
+
+ pub fn new_inherited(type_id: NodeTypeId, doc: &JSRef<Document>) -> Node {
+ Node::new_(type_id, Some(doc.clone()))
+ }
+
+ pub fn new_without_doc(type_id: NodeTypeId) -> Node {
+ Node::new_(type_id, None)
+ }
+
+ fn new_(type_id: NodeTypeId, doc: Option<JSRef<Document>>) -> Node {
+ Node {
+ eventtarget: EventTarget::new_inherited(NodeTargetTypeId(type_id)),
+ type_id: type_id,
+
+ parent_node: Cell::new(None),
+ first_child: Cell::new(None),
+ last_child: Cell::new(None),
+ next_sibling: Cell::new(None),
+ prev_sibling: Cell::new(None),
+ owner_doc: Cell::new(doc.unrooted()),
+ child_list: Cell::new(None),
+
+ flags: Traceable::new(RefCell::new(NodeFlags::new(type_id))),
+
+ layout_data: LayoutDataRef::new(),
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-adopt
+ pub fn adopt(node: &JSRef<Node>, document: &JSRef<Document>) {
+ // Step 1.
+ match node.parent_node().root() {
+ Some(parent) => {
+ Node::remove(node, &*parent, Unsuppressed);
+ }
+ None => (),
+ }
+
+ // Step 2.
+ let node_doc = document_from_node(node).root();
+ if &*node_doc != document {
+ for descendant in node.traverse_preorder() {
+ descendant.set_owner_doc(document);
+ }
+ }
+
+ // Step 3.
+ // If node is an element, it is _affected by a base URL change_.
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-pre-insert
+ fn pre_insert(node: &JSRef<Node>, parent: &JSRef<Node>, child: Option<JSRef<Node>>)
+ -> Fallible<Temporary<Node>> {
+ // Step 1.
+ match parent.type_id() {
+ DocumentNodeTypeId |
+ DocumentFragmentNodeTypeId |
+ ElementNodeTypeId(..) => (),
+ _ => return Err(HierarchyRequest)
+ }
+
+ // Step 2.
+ if node.is_inclusive_ancestor_of(parent) {
+ return Err(HierarchyRequest);
+ }
+
+ // Step 3.
+ match child {
+ Some(ref child) if !parent.is_parent_of(child) => return Err(NotFound),
+ _ => ()
+ }
+
+ // Step 4-5.
+ match node.type_id() {
+ TextNodeTypeId => {
+ match node.parent_node().root() {
+ Some(ref parent) if parent.is_document() => return Err(HierarchyRequest),
+ _ => ()
+ }
+ }
+ DoctypeNodeTypeId => {
+ match node.parent_node().root() {
+ Some(ref parent) if !parent.is_document() => return Err(HierarchyRequest),
+ _ => ()
+ }
+ }
+ DocumentFragmentNodeTypeId |
+ ElementNodeTypeId(_) |
+ ProcessingInstructionNodeTypeId |
+ CommentNodeTypeId => (),
+ DocumentNodeTypeId => return Err(HierarchyRequest)
+ }
+
+ // Step 6.
+ match parent.type_id() {
+ DocumentNodeTypeId => {
+ match node.type_id() {
+ // Step 6.1
+ DocumentFragmentNodeTypeId => {
+ // Step 6.1.1(b)
+ if node.children().any(|c| c.is_text()) {
+ return Err(HierarchyRequest);
+ }
+ match node.child_elements().count() {
+ 0 => (),
+ // Step 6.1.2
+ 1 => {
+ // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218
+ // will be fixed
+ if parent.child_elements().count() > 0 {
+ return Err(HierarchyRequest);
+ }
+ match child {
+ Some(ref child) => {
+ if child.inclusively_following_siblings()
+ .any(|child| child.is_doctype()) {
+ return Err(HierarchyRequest)
+ }
+ }
+ _ => (),
+ }
+ },
+ // Step 6.1.1(a)
+ _ => return Err(HierarchyRequest),
+ }
+ },
+ // Step 6.2
+ ElementNodeTypeId(_) => {
+ // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218
+ // will be fixed
+ if parent.child_elements().count() > 0 {
+ return Err(HierarchyRequest);
+ }
+ match child {
+ Some(ref child) => {
+ if child.inclusively_following_siblings()
+ .any(|child| child.is_doctype()) {
+ return Err(HierarchyRequest)
+ }
+ }
+ _ => (),
+ }
+ },
+ // Step 6.3
+ DoctypeNodeTypeId => {
+ if parent.children().any(|c| c.is_doctype()) {
+ return Err(HierarchyRequest);
+ }
+ match child {
+ Some(ref child) => {
+ if parent.children()
+ .take_while(|c| c != child)
+ .any(|c| c.is_element()) {
+ return Err(HierarchyRequest);
+ }
+ },
+ None => {
+ // FIXME: change to empty() when https://github.com/mozilla/rust/issues/11218
+ // will be fixed
+ if parent.child_elements().count() > 0 {
+ return Err(HierarchyRequest);
+ }
+ },
+ }
+ },
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId |
+ CommentNodeTypeId => (),
+ DocumentNodeTypeId => unreachable!(),
+ }
+ },
+ _ => (),
+ }
+
+ // Step 7-8.
+ let referenceChild = match child {
+ Some(ref child) if child == node => node.next_sibling().map(|node| (*node.root()).clone()),
+ _ => child
+ };
+
+ // Step 9.
+ let document = document_from_node(parent).root();
+ Node::adopt(node, &*document);
+
+ // Step 10.
+ Node::insert(node, parent, referenceChild, Unsuppressed);
+
+ // Step 11.
+ return Ok(Temporary::from_rooted(node))
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-insert
+ fn insert(node: &JSRef<Node>,
+ parent: &JSRef<Node>,
+ child: Option<JSRef<Node>>,
+ suppress_observers: SuppressObserver) {
+ // XXX assert owner_doc
+ // Step 1-3: ranges.
+ // Step 4.
+ let mut nodes = match node.type_id() {
+ DocumentFragmentNodeTypeId => node.children().collect(),
+ _ => vec!(node.clone()),
+ };
+
+ // Step 5: DocumentFragment, mutation records.
+ // Step 6: DocumentFragment.
+ match node.type_id() {
+ DocumentFragmentNodeTypeId => {
+ for c in node.children() {
+ Node::remove(&c, node, Suppressed);
+ }
+ },
+ _ => (),
+ }
+
+ // Step 7: mutation records.
+ // Step 8.
+ for node in nodes.mut_iter() {
+ parent.add_child(node, child);
+ let is_in_doc = parent.is_in_doc();
+ for kid in node.traverse_preorder() {
+ if is_in_doc {
+ kid.flags.deref().borrow_mut().insert(IsInDoc);
+ } else {
+ kid.flags.deref().borrow_mut().remove(IsInDoc);
+ }
+ }
+ }
+
+ // Step 9.
+ match suppress_observers {
+ Unsuppressed => {
+ for node in nodes.iter() {
+ node.node_inserted();
+ }
+ }
+ Suppressed => ()
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-replace-all
+ fn replace_all(node: Option<JSRef<Node>>, parent: &JSRef<Node>) {
+
+ // Step 1.
+ match node {
+ Some(ref node) => {
+ let document = document_from_node(parent).root();
+ Node::adopt(node, &*document);
+ }
+ None => (),
+ }
+
+ // Step 2.
+ let removedNodes: Vec<JSRef<Node>> = parent.children().collect();
+
+ // Step 3.
+ let addedNodes = match node {
+ None => vec!(),
+ Some(ref node) => match node.type_id() {
+ DocumentFragmentNodeTypeId => node.children().collect(),
+ _ => vec!(node.clone()),
+ },
+ };
+
+ // Step 4.
+ for child in parent.children() {
+ Node::remove(&child, parent, Suppressed);
+ }
+
+ // Step 5.
+ match node {
+ Some(ref node) => Node::insert(node, parent, None, Suppressed),
+ None => (),
+ }
+
+ // Step 6: mutation records.
+
+ // Step 7.
+ let parent_in_doc = parent.is_in_doc();
+ for removedNode in removedNodes.iter() {
+ removedNode.node_removed(parent_in_doc);
+ }
+ for addedNode in addedNodes.iter() {
+ addedNode.node_inserted();
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-pre-remove
+ fn pre_remove(child: &JSRef<Node>, parent: &JSRef<Node>) -> Fallible<Temporary<Node>> {
+ // Step 1.
+ match child.parent_node() {
+ Some(ref node) if *node != Temporary::from_rooted(parent) => return Err(NotFound),
+ _ => ()
+ }
+
+ // Step 2.
+ Node::remove(child, parent, Unsuppressed);
+
+ // Step 3.
+ Ok(Temporary::from_rooted(child))
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-remove
+ fn remove(node: &JSRef<Node>, parent: &JSRef<Node>, suppress_observers: SuppressObserver) {
+ assert!(node.parent_node().map_or(false, |node_parent| node_parent == Temporary::from_rooted(parent)));
+
+ // Step 1-5: ranges.
+ // Step 6-7: mutation observers.
+ // Step 8.
+ parent.remove_child(node);
+
+ node.deref().flags.deref().borrow_mut().remove(IsInDoc);
+
+ // Step 9.
+ match suppress_observers {
+ Suppressed => (),
+ Unsuppressed => node.node_removed(parent.is_in_doc()),
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-clone
+ pub fn clone(node: &JSRef<Node>, maybe_doc: Option<&JSRef<Document>>,
+ clone_children: CloneChildrenFlag) -> Temporary<Node> {
+
+ // Step 1.
+ let document = match maybe_doc {
+ Some(doc) => JS::from_rooted(doc).root(),
+ None => node.owner_doc().root()
+ };
+
+ // Step 2.
+ // XXXabinader: clone() for each node as trait?
+ let copy: Root<Node> = match node.type_id() {
+ DoctypeNodeTypeId => {
+ let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(node).unwrap();
+ let doctype = doctype.deref();
+ let doctype = DocumentType::new(doctype.name.clone(),
+ Some(doctype.public_id.clone()),
+ Some(doctype.system_id.clone()), &*document);
+ NodeCast::from_temporary(doctype)
+ },
+ DocumentFragmentNodeTypeId => {
+ let doc_fragment = DocumentFragment::new(&*document);
+ NodeCast::from_temporary(doc_fragment)
+ },
+ CommentNodeTypeId => {
+ let comment: &JSRef<Comment> = CommentCast::to_ref(node).unwrap();
+ let comment = comment.deref();
+ let comment = Comment::new(comment.characterdata.data.deref().borrow().clone(), &*document);
+ NodeCast::from_temporary(comment)
+ },
+ DocumentNodeTypeId => {
+ let document: &JSRef<Document> = DocumentCast::to_ref(node).unwrap();
+ let is_html_doc = match document.is_html_document {
+ true => HTMLDocument,
+ false => NonHTMLDocument
+ };
+ let window = document.window.root();
+ let document = Document::new(&*window, Some(document.url().clone()),
+ is_html_doc, None);
+ NodeCast::from_temporary(document)
+ },
+ ElementNodeTypeId(..) => {
+ let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ let element = element.deref();
+ let element = build_element_from_tag(element.local_name.as_slice().to_string(),
+ element.namespace.clone(), &*document);
+ NodeCast::from_temporary(element)
+ },
+ TextNodeTypeId => {
+ let text: &JSRef<Text> = TextCast::to_ref(node).unwrap();
+ let text = text.deref();
+ let text = Text::new(text.characterdata.data.deref().borrow().clone(), &*document);
+ NodeCast::from_temporary(text)
+ },
+ ProcessingInstructionNodeTypeId => {
+ let pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap();
+ let pi = pi.deref();
+ let pi = ProcessingInstruction::new(pi.target.clone(),
+ pi.characterdata.data.deref().borrow().clone(), &*document);
+ NodeCast::from_temporary(pi)
+ },
+ }.root();
+
+ // Step 3.
+ let document = if copy.is_document() {
+ let doc: &JSRef<Document> = DocumentCast::to_ref(&*copy).unwrap();
+ JS::from_rooted(doc).root()
+ } else {
+ JS::from_rooted(&*document).root()
+ };
+ assert!(&*copy.owner_doc().root() == &*document);
+
+ // Step 4 (some data already copied in step 2).
+ match node.type_id() {
+ DocumentNodeTypeId => {
+ let node_doc: &JSRef<Document> = DocumentCast::to_ref(node).unwrap();
+ let copy_doc: &JSRef<Document> = DocumentCast::to_ref(&*copy).unwrap();
+ copy_doc.set_encoding_name(node_doc.encoding_name.deref().borrow().clone());
+ copy_doc.set_quirks_mode(node_doc.quirks_mode());
+ },
+ ElementNodeTypeId(..) => {
+ let node_elem: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ let copy_elem: &JSRef<Element> = ElementCast::to_ref(&*copy).unwrap();
+
+ // FIXME: https://github.com/mozilla/servo/issues/1737
+ let window = document.deref().window.root();
+ for attr in node_elem.deref().attrs.borrow().iter().map(|attr| attr.root()) {
+ copy_elem.deref().attrs.borrow_mut().push_unrooted(
+ &Attr::new(&*window,
+ attr.local_name().clone(), attr.deref().value().clone(),
+ attr.deref().name.clone(), attr.deref().namespace.clone(),
+ attr.deref().prefix.clone(), copy_elem));
+ }
+ },
+ _ => ()
+ }
+
+ // Step 5: cloning steps.
+
+ // Step 6.
+ if clone_children == CloneChildren {
+ for ref child in node.children() {
+ let child_copy = Node::clone(&*child, Some(&*document), clone_children).root();
+ let _inserted_node = Node::pre_insert(&*child_copy, &*copy, None);
+ }
+ }
+
+ // Step 7.
+ Temporary::from_rooted(&*copy)
+ }
+
+ /// Sends layout data, if any, back to the layout task to be destroyed.
+ unsafe fn reap_layout_data(&mut self) {
+ if self.layout_data.is_present() {
+ let layout_data = mem::replace(&mut self.layout_data, LayoutDataRef::new());
+ let layout_chan = layout_data.take_chan();
+ match layout_chan {
+ None => {}
+ Some(chan) => {
+ let LayoutChan(chan) = chan;
+ chan.send(ReapLayoutDataMsg(layout_data))
+ },
+ }
+ }
+ }
+
+ pub unsafe fn unsafe_get_flags(&self) -> *const NodeFlags {
+ mem::transmute(&self.flags)
+ }
+
+ pub fn collect_text_contents<'a, T: Iterator<JSRef<'a, Node>>>(mut iterator: T) -> String {
+ let mut content = String::new();
+ for node in iterator {
+ let text: Option<&JSRef<Text>> = TextCast::to_ref(&node);
+ match text {
+ Some(text) => content.push_str(text.characterdata.data.borrow().as_slice()),
+ None => (),
+ }
+ }
+ content
+ }
+}
+
+impl<'a> NodeMethods for JSRef<'a, Node> {
+ // http://dom.spec.whatwg.org/#dom-node-nodetype
+ fn NodeType(&self) -> u16 {
+ match self.type_id {
+ ElementNodeTypeId(_) => NodeConstants::ELEMENT_NODE,
+ TextNodeTypeId => NodeConstants::TEXT_NODE,
+ ProcessingInstructionNodeTypeId => NodeConstants::PROCESSING_INSTRUCTION_NODE,
+ CommentNodeTypeId => NodeConstants::COMMENT_NODE,
+ DocumentNodeTypeId => NodeConstants::DOCUMENT_NODE,
+ DoctypeNodeTypeId => NodeConstants::DOCUMENT_TYPE_NODE,
+ DocumentFragmentNodeTypeId => NodeConstants::DOCUMENT_FRAGMENT_NODE,
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-nodename
+ fn NodeName(&self) -> DOMString {
+ match self.type_id {
+ ElementNodeTypeId(..) => {
+ let elem: &JSRef<Element> = ElementCast::to_ref(self).unwrap();
+ elem.TagName()
+ }
+ TextNodeTypeId => "#text".to_string(),
+ ProcessingInstructionNodeTypeId => {
+ let processing_instruction: &JSRef<ProcessingInstruction> =
+ ProcessingInstructionCast::to_ref(self).unwrap();
+ processing_instruction.Target()
+ }
+ CommentNodeTypeId => "#comment".to_string(),
+ DoctypeNodeTypeId => {
+ let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(self).unwrap();
+ doctype.deref().name.clone()
+ },
+ DocumentFragmentNodeTypeId => "#document-fragment".to_string(),
+ DocumentNodeTypeId => "#document".to_string()
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-baseuri
+ fn GetBaseURI(&self) -> Option<DOMString> {
+ // FIXME (#1824) implement.
+ None
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-ownerdocument
+ fn GetOwnerDocument(&self) -> Option<Temporary<Document>> {
+ match self.type_id {
+ ElementNodeTypeId(..) |
+ CommentNodeTypeId |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId |
+ DoctypeNodeTypeId |
+ DocumentFragmentNodeTypeId => Some(self.owner_doc()),
+ DocumentNodeTypeId => None
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-parentnode
+ fn GetParentNode(&self) -> Option<Temporary<Node>> {
+ self.parent_node.get().map(|node| Temporary::new(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-parentelement
+ fn GetParentElement(&self) -> Option<Temporary<Element>> {
+ self.parent_node.get()
+ .and_then(|parent| {
+ let parent = parent.root();
+ ElementCast::to_ref(&*parent).map(|elem| {
+ Temporary::from_rooted(elem)
+ })
+ })
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-haschildnodes
+ fn HasChildNodes(&self) -> bool {
+ self.first_child.get().is_some()
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-childnodes
+ fn ChildNodes(&self) -> Temporary<NodeList> {
+ match self.child_list.get() {
+ None => (),
+ Some(ref list) => return Temporary::new(list.clone()),
+ }
+
+ let doc = self.owner_doc().root();
+ let window = doc.deref().window.root();
+ let child_list = NodeList::new_child_list(&*window, self);
+ self.child_list.assign(Some(child_list));
+ Temporary::new(self.child_list.get().get_ref().clone())
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-firstchild
+ fn GetFirstChild(&self) -> Option<Temporary<Node>> {
+ self.first_child.get().map(|node| Temporary::new(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-lastchild
+ fn GetLastChild(&self) -> Option<Temporary<Node>> {
+ self.last_child.get().map(|node| Temporary::new(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-previoussibling
+ fn GetPreviousSibling(&self) -> Option<Temporary<Node>> {
+ self.prev_sibling.get().map(|node| Temporary::new(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-nextsibling
+ fn GetNextSibling(&self) -> Option<Temporary<Node>> {
+ self.next_sibling.get().map(|node| Temporary::new(node))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-nodevalue
+ fn GetNodeValue(&self) -> Option<DOMString> {
+ match self.type_id {
+ CommentNodeTypeId |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId => {
+ let chardata: &JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
+ Some(chardata.Data())
+ }
+ _ => {
+ None
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-nodevalue
+ fn SetNodeValue(&self, val: Option<DOMString>) {
+ match self.type_id {
+ CommentNodeTypeId |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId => {
+ self.SetTextContent(val)
+ }
+ _ => {}
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-textcontent
+ fn GetTextContent(&self) -> Option<DOMString> {
+ match self.type_id {
+ DocumentFragmentNodeTypeId |
+ ElementNodeTypeId(..) => {
+ let content = Node::collect_text_contents(self.traverse_preorder());
+ Some(content)
+ }
+ CommentNodeTypeId |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId => {
+ let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
+ Some(characterdata.Data())
+ }
+ DoctypeNodeTypeId |
+ DocumentNodeTypeId => {
+ None
+ }
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-textcontent
+ fn SetTextContent(&self, value: Option<DOMString>) {
+ let value = null_str_as_empty(&value);
+ match self.type_id {
+ DocumentFragmentNodeTypeId |
+ ElementNodeTypeId(..) => {
+ // Step 1-2.
+ let node = if value.len() == 0 {
+ None
+ } else {
+ let document = self.owner_doc().root();
+ Some(NodeCast::from_temporary(document.deref().CreateTextNode(value)))
+ }.root();
+
+ // Step 3.
+ Node::replace_all(node.root_ref(), self);
+ }
+ CommentNodeTypeId |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId => {
+ self.wait_until_safe_to_modify_dom();
+
+ let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(self).unwrap();
+ *characterdata.data.deref().borrow_mut() = value;
+
+ // Notify the document that the content of this node is different
+ let document = self.owner_doc().root();
+ document.deref().content_changed();
+ }
+ DoctypeNodeTypeId |
+ DocumentNodeTypeId => {}
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-insertbefore
+ fn InsertBefore(&self, node: &JSRef<Node>, child: Option<JSRef<Node>>) -> Fallible<Temporary<Node>> {
+ Node::pre_insert(node, self, child)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-appendchild
+ fn AppendChild(&self, node: &JSRef<Node>) -> Fallible<Temporary<Node>> {
+ Node::pre_insert(node, self, None)
+ }
+
+ // http://dom.spec.whatwg.org/#concept-node-replace
+ fn ReplaceChild(&self, node: &JSRef<Node>, child: &JSRef<Node>) -> Fallible<Temporary<Node>> {
+
+ // Step 1.
+ match self.type_id {
+ DocumentNodeTypeId |
+ DocumentFragmentNodeTypeId |
+ ElementNodeTypeId(..) => (),
+ _ => return Err(HierarchyRequest)
+ }
+
+ // Step 2.
+ if node.is_inclusive_ancestor_of(self) {
+ return Err(HierarchyRequest);
+ }
+
+ // Step 3.
+ if !self.is_parent_of(child) {
+ return Err(NotFound);
+ }
+
+ // Step 4-5.
+ match node.type_id() {
+ TextNodeTypeId if self.is_document() => return Err(HierarchyRequest),
+ DoctypeNodeTypeId if !self.is_document() => return Err(HierarchyRequest),
+ DocumentFragmentNodeTypeId |
+ DoctypeNodeTypeId |
+ ElementNodeTypeId(..) |
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId |
+ CommentNodeTypeId => (),
+ DocumentNodeTypeId => return Err(HierarchyRequest)
+ }
+
+ // Step 6.
+ match self.type_id {
+ DocumentNodeTypeId => {
+ match node.type_id() {
+ // Step 6.1
+ DocumentFragmentNodeTypeId => {
+ // Step 6.1.1(b)
+ if node.children().any(|c| c.is_text()) {
+ return Err(HierarchyRequest);
+ }
+ match node.child_elements().count() {
+ 0 => (),
+ // Step 6.1.2
+ 1 => {
+ if self.child_elements().any(|c| NodeCast::from_ref(&c) != child) {
+ return Err(HierarchyRequest);
+ }
+ if child.following_siblings()
+ .any(|child| child.is_doctype()) {
+ return Err(HierarchyRequest);
+ }
+ },
+ // Step 6.1.1(a)
+ _ => return Err(HierarchyRequest)
+ }
+ },
+ // Step 6.2
+ ElementNodeTypeId(..) => {
+ if self.child_elements().any(|c| NodeCast::from_ref(&c) != child) {
+ return Err(HierarchyRequest);
+ }
+ if child.following_siblings()
+ .any(|child| child.is_doctype()) {
+ return Err(HierarchyRequest);
+ }
+ },
+ // Step 6.3
+ DoctypeNodeTypeId => {
+ if self.children().any(|c| c.is_doctype() && &c != child) {
+ return Err(HierarchyRequest);
+ }
+ if self.children()
+ .take_while(|c| c != child)
+ .any(|c| c.is_element()) {
+ return Err(HierarchyRequest);
+ }
+ },
+ TextNodeTypeId |
+ ProcessingInstructionNodeTypeId |
+ CommentNodeTypeId => (),
+ DocumentNodeTypeId => unreachable!()
+ }
+ },
+ _ => ()
+ }
+
+ // Ok if not caught by previous error checks.
+ if *node == *child {
+ return Ok(Temporary::from_rooted(child));
+ }
+
+ // Step 7-8.
+ let next_sibling = child.next_sibling().map(|node| (*node.root()).clone());
+ let reference_child = match next_sibling {
+ Some(ref sibling) if sibling == node => node.next_sibling().map(|node| (*node.root()).clone()),
+ _ => next_sibling
+ };
+
+ // Step 9.
+ let document = document_from_node(self).root();
+ Node::adopt(node, &*document);
+
+ {
+ // Step 10.
+ Node::remove(child, self, Suppressed);
+
+ // Step 11.
+ Node::insert(node, self, reference_child, Suppressed);
+ }
+
+ // Step 12-14.
+ // Step 13: mutation records.
+ child.node_removed(self.is_in_doc());
+ if node.type_id() == DocumentFragmentNodeTypeId {
+ for child_node in node.children() {
+ child_node.node_inserted();
+ }
+ } else {
+ node.node_inserted();
+ }
+
+ // Step 15.
+ Ok(Temporary::from_rooted(child))
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-removechild
+ fn RemoveChild(&self, node: &JSRef<Node>)
+ -> Fallible<Temporary<Node>> {
+ Node::pre_remove(node, self)
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-normalize
+ fn Normalize(&self) {
+ let mut prev_text = None;
+ for child in self.children() {
+ if child.is_text() {
+ let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(&child).unwrap();
+ if characterdata.Length() == 0 {
+ self.remove_child(&child);
+ } else {
+ match prev_text {
+ Some(ref mut text_node) => {
+ let prev_characterdata: &mut JSRef<CharacterData> = CharacterDataCast::to_mut_ref(text_node).unwrap();
+ let _ = prev_characterdata.AppendData(characterdata.Data());
+ self.remove_child(&child);
+ },
+ None => prev_text = Some(child)
+ }
+ }
+ } else {
+ child.Normalize();
+ prev_text = None;
+ }
+
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-clonenode
+ fn CloneNode(&self, deep: bool) -> Temporary<Node> {
+ match deep {
+ true => Node::clone(self, None, CloneChildren),
+ false => Node::clone(self, None, DoNotCloneChildren)
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-isequalnode
+ fn IsEqualNode(&self, maybe_node: Option<JSRef<Node>>) -> bool {
+ fn is_equal_doctype(node: &JSRef<Node>, other: &JSRef<Node>) -> bool {
+ let doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(node).unwrap();
+ let other_doctype: &JSRef<DocumentType> = DocumentTypeCast::to_ref(other).unwrap();
+ (doctype.deref().name == other_doctype.deref().name) &&
+ (doctype.deref().public_id == other_doctype.deref().public_id) &&
+ (doctype.deref().system_id == other_doctype.deref().system_id)
+ }
+ fn is_equal_element(node: &JSRef<Node>, other: &JSRef<Node>) -> bool {
+ let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ let other_element: &JSRef<Element> = ElementCast::to_ref(other).unwrap();
+ // FIXME: namespace prefix
+ let element = element.deref();
+ let other_element = other_element.deref();
+ (element.namespace == other_element.namespace) &&
+ (element.local_name == other_element.local_name) &&
+ (element.attrs.borrow().len() == other_element.attrs.borrow().len())
+ }
+ fn is_equal_processinginstruction(node: &JSRef<Node>, other: &JSRef<Node>) -> bool {
+ let pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(node).unwrap();
+ let other_pi: &JSRef<ProcessingInstruction> = ProcessingInstructionCast::to_ref(other).unwrap();
+ (pi.deref().target == other_pi.deref().target) &&
+ (*pi.deref().characterdata.data.deref().borrow() == *other_pi.deref().characterdata.data.deref().borrow())
+ }
+ fn is_equal_characterdata(node: &JSRef<Node>, other: &JSRef<Node>) -> bool {
+ let characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(node).unwrap();
+ let other_characterdata: &JSRef<CharacterData> = CharacterDataCast::to_ref(other).unwrap();
+ *characterdata.deref().data.deref().borrow() == *other_characterdata.deref().data.deref().borrow()
+ }
+ fn is_equal_element_attrs(node: &JSRef<Node>, other: &JSRef<Node>) -> bool {
+ let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ let other_element: &JSRef<Element> = ElementCast::to_ref(other).unwrap();
+ let element = element.deref();
+ let other_element = other_element.deref();
+ assert!(element.attrs.borrow().len() == other_element.attrs.borrow().len());
+ element.attrs.borrow().iter().map(|attr| attr.root()).all(|attr| {
+ other_element.attrs.borrow().iter().map(|attr| attr.root()).any(|other_attr| {
+ (attr.namespace == other_attr.namespace) &&
+ (attr.local_name() == other_attr.local_name()) &&
+ (attr.deref().value().as_slice() == other_attr.deref().value().as_slice())
+ })
+ })
+ }
+ fn is_equal_node(this: &JSRef<Node>, node: &JSRef<Node>) -> bool {
+ // Step 2.
+ if this.type_id() != node.type_id() {
+ return false;
+ }
+
+ match node.type_id() {
+ // Step 3.
+ DoctypeNodeTypeId if !is_equal_doctype(this, node) => return false,
+ ElementNodeTypeId(..) if !is_equal_element(this, node) => return false,
+ ProcessingInstructionNodeTypeId if !is_equal_processinginstruction(this, node) => return false,
+ TextNodeTypeId |
+ CommentNodeTypeId if !is_equal_characterdata(this, node) => return false,
+ // Step 4.
+ ElementNodeTypeId(..) if !is_equal_element_attrs(this, node) => return false,
+ _ => ()
+ }
+
+ // Step 5.
+ if this.children().count() != node.children().count() {
+ return false;
+ }
+
+ // Step 6.
+ this.children().zip(node.children()).all(|(ref child, ref other_child)| {
+ is_equal_node(child, other_child)
+ })
+ }
+ match maybe_node {
+ // Step 1.
+ None => false,
+ // Step 2-6.
+ Some(ref node) => is_equal_node(self, node)
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-comparedocumentposition
+ fn CompareDocumentPosition(&self, other: &JSRef<Node>) -> u16 {
+ if self == other {
+ // step 2.
+ 0
+ } else {
+ let mut lastself = self.clone();
+ let mut lastother = other.clone();
+ for ancestor in self.ancestors() {
+ if &ancestor == other {
+ // step 4.
+ return NodeConstants::DOCUMENT_POSITION_CONTAINS +
+ NodeConstants::DOCUMENT_POSITION_PRECEDING;
+ }
+ lastself = ancestor.clone();
+ }
+ for ancestor in other.ancestors() {
+ if &ancestor == self {
+ // step 5.
+ return NodeConstants::DOCUMENT_POSITION_CONTAINED_BY +
+ NodeConstants::DOCUMENT_POSITION_FOLLOWING;
+ }
+ lastother = ancestor.clone();
+ }
+
+ if lastself != lastother {
+ let abstract_uint: uintptr_t = as_uintptr(&*self);
+ let other_uint: uintptr_t = as_uintptr(&*other);
+
+ let random = if abstract_uint < other_uint {
+ NodeConstants::DOCUMENT_POSITION_FOLLOWING
+ } else {
+ NodeConstants::DOCUMENT_POSITION_PRECEDING
+ };
+ // step 3.
+ return random +
+ NodeConstants::DOCUMENT_POSITION_DISCONNECTED +
+ NodeConstants::DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
+ }
+
+ for child in lastself.traverse_preorder() {
+ if &child == other {
+ // step 6.
+ return NodeConstants::DOCUMENT_POSITION_PRECEDING;
+ }
+ if &child == self {
+ // step 7.
+ return NodeConstants::DOCUMENT_POSITION_FOLLOWING;
+ }
+ }
+ unreachable!()
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-contains
+ fn Contains(&self, maybe_other: Option<JSRef<Node>>) -> bool {
+ match maybe_other {
+ None => false,
+ Some(ref other) => self.is_inclusive_ancestor_of(other)
+ }
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-lookupprefix
+ fn LookupPrefix(&self, _prefix: Option<DOMString>) -> Option<DOMString> {
+ // FIXME (#1826) implement.
+ None
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri
+ fn LookupNamespaceURI(&self, _namespace: Option<DOMString>) -> Option<DOMString> {
+ // FIXME (#1826) implement.
+ None
+ }
+
+ // http://dom.spec.whatwg.org/#dom-node-isdefaultnamespace
+ fn IsDefaultNamespace(&self, _namespace: Option<DOMString>) -> bool {
+ // FIXME (#1826) implement.
+ false
+ }
+}
+
+
+impl Reflectable for Node {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
+
+pub fn document_from_node<T: NodeBase>(derived: &JSRef<T>) -> Temporary<Document> {
+ let node: &JSRef<Node> = NodeCast::from_ref(derived);
+ node.owner_doc()
+}
+
+pub fn window_from_node<T: NodeBase>(derived: &JSRef<T>) -> Temporary<Window> {
+ let document = document_from_node(derived).root();
+ Temporary::new(document.deref().window.clone())
+}
+
+impl<'a> VirtualMethods for JSRef<'a, Node> {
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ Some(eventtarget as &VirtualMethods)
+ }
+}
+
+impl<'a> style::TNode<JSRef<'a, Element>> for JSRef<'a, Node> {
+ fn parent_node(&self) -> Option<JSRef<'a, Node>> {
+ (self as &NodeHelpers).parent_node().map(|node| *node.root())
+ }
+
+ fn prev_sibling(&self) -> Option<JSRef<'a, Node>> {
+ (self as &NodeHelpers).prev_sibling().map(|node| *node.root())
+ }
+
+ fn next_sibling(&self) -> Option<JSRef<'a, Node>> {
+ (self as &NodeHelpers).next_sibling().map(|node| *node.root())
+ }
+
+ fn is_document(&self) -> bool {
+ (self as &NodeHelpers).is_document()
+ }
+
+ fn is_element(&self) -> bool {
+ (self as &NodeHelpers).is_element()
+ }
+
+ fn as_element(&self) -> JSRef<'a, Element> {
+ let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
+ assert!(elem.is_some());
+ *elem.unwrap()
+ }
+
+ fn match_attr(&self, attr: &style::AttrSelector, test: |&str| -> bool) -> bool {
+ let name = {
+ if self.is_html_element_in_html_document() {
+ attr.lower_name.as_slice()
+ } else {
+ attr.name.as_slice()
+ }
+ };
+ match attr.namespace {
+ style::SpecificNamespace(ref ns) => {
+ self.as_element().get_attribute(ns.clone(), name).root()
+ .map_or(false, |attr| test(attr.deref().Value().as_slice()))
+ },
+ // FIXME: https://github.com/mozilla/servo/issues/1558
+ style::AnyNamespace => false,
+ }
+ }
+
+ fn is_html_element_in_html_document(&self) -> bool {
+ let elem: Option<&JSRef<'a, Element>> = ElementCast::to_ref(self);
+ assert!(elem.is_some());
+ let elem: &ElementHelpers = elem.unwrap() as &ElementHelpers;
+ elem.html_element_in_html_document()
+ }
+}
+
+pub trait DisabledStateHelpers {
+ fn check_ancestors_disabled_state_for_form_control(&self);
+ fn check_parent_disabled_state_for_option(&self);
+ fn check_disabled_attribute(&self);
+}
+
+impl<'a> DisabledStateHelpers for JSRef<'a, Node> {
+ fn check_ancestors_disabled_state_for_form_control(&self) {
+ if self.get_disabled_state() { return; }
+ for ancestor in self.ancestors().filter(|ancestor| ancestor.is_htmlfieldsetelement()) {
+ if !ancestor.get_disabled_state() { continue; }
+ if ancestor.is_parent_of(self) {
+ self.set_disabled_state(true);
+ self.set_enabled_state(false);
+ return;
+ }
+ match ancestor.children().find(|child| child.is_htmllegendelement()) {
+ Some(ref legend) => {
+ // XXXabinader: should we save previous ancestor to avoid this iteration?
+ if self.ancestors().any(|ancestor| ancestor == *legend) { continue; }
+ },
+ None => ()
+ }
+ self.set_disabled_state(true);
+ self.set_enabled_state(false);
+ return;
+ }
+ }
+
+ fn check_parent_disabled_state_for_option(&self) {
+ if self.get_disabled_state() { return; }
+ match self.parent_node().root() {
+ Some(ref parent) if parent.is_htmloptgroupelement() && parent.get_disabled_state() => {
+ self.set_disabled_state(true);
+ self.set_enabled_state(false);
+ },
+ _ => ()
+ }
+ }
+
+ fn check_disabled_attribute(&self) {
+ let elem: &JSRef<'a, Element> = ElementCast::to_ref(self).unwrap();
+ let has_disabled_attrib = elem.has_attribute("disabled");
+ self.set_disabled_state(has_disabled_attrib);
+ self.set_enabled_state(!has_disabled_attrib);
+ }
+}
diff --git a/components/script/dom/nodeiterator.rs b/components/script/dom/nodeiterator.rs
new file mode 100644
index 00000000000..f890f71cf4f
--- /dev/null
+++ b/components/script/dom/nodeiterator.rs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::NodeIteratorBinding;
+use dom::bindings::codegen::Bindings::NodeIteratorBinding::NodeIteratorMethods;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+
+#[deriving(Encodable)]
+pub struct NodeIterator {
+ pub reflector_: Reflector
+}
+
+impl NodeIterator {
+ pub fn new_inherited() -> NodeIterator {
+ NodeIterator {
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(global: &GlobalRef) -> Temporary<NodeIterator> {
+ reflect_dom_object(box NodeIterator::new_inherited(), global, NodeIteratorBinding::Wrap)
+ }
+}
+
+impl<'a> NodeIteratorMethods for JSRef<'a, NodeIterator> {
+}
+
+impl Reflectable for NodeIterator {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/nodelist.rs b/components/script/dom/nodelist.rs
new file mode 100644
index 00000000000..424eb09416c
--- /dev/null
+++ b/components/script/dom/nodelist.rs
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::NodeListBinding;
+use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::node::{Node, NodeHelpers};
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub enum NodeListType {
+ Simple(Vec<JS<Node>>),
+ Children(JS<Node>)
+}
+
+#[deriving(Encodable)]
+pub struct NodeList {
+ list_type: NodeListType,
+ reflector_: Reflector,
+}
+
+impl NodeList {
+ pub fn new_inherited(list_type: NodeListType) -> NodeList {
+ NodeList {
+ list_type: list_type,
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>,
+ list_type: NodeListType) -> Temporary<NodeList> {
+ reflect_dom_object(box NodeList::new_inherited(list_type),
+ &Window(*window), NodeListBinding::Wrap)
+ }
+
+ pub fn new_simple_list(window: &JSRef<Window>, elements: Vec<JSRef<Node>>) -> Temporary<NodeList> {
+ NodeList::new(window, Simple(elements.iter().map(|element| JS::from_rooted(element)).collect()))
+ }
+
+ pub fn new_child_list(window: &JSRef<Window>, node: &JSRef<Node>) -> Temporary<NodeList> {
+ NodeList::new(window, Children(JS::from_rooted(node)))
+ }
+}
+
+impl<'a> NodeListMethods for JSRef<'a, NodeList> {
+ fn Length(&self) -> u32 {
+ match self.list_type {
+ Simple(ref elems) => elems.len() as u32,
+ Children(ref node) => {
+ let node = node.root();
+ node.deref().children().count() as u32
+ }
+ }
+ }
+
+ fn Item(&self, index: u32) -> Option<Temporary<Node>> {
+ match self.list_type {
+ _ if index >= self.Length() => None,
+ Simple(ref elems) => Some(Temporary::new(elems[index as uint].clone())),
+ Children(ref node) => {
+ let node = node.root();
+ node.deref().children().nth(index as uint)
+ .map(|child| Temporary::from_rooted(&child))
+ }
+ }
+ }
+
+ fn IndexedGetter(&self, index: u32, found: &mut bool) -> Option<Temporary<Node>> {
+ let item = self.Item(index);
+ *found = item.is_some();
+ item
+ }
+}
+
+impl Reflectable for NodeList {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/performance.rs b/components/script/dom/performance.rs
new file mode 100644
index 00000000000..82b931b0c2e
--- /dev/null
+++ b/components/script/dom/performance.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::PerformanceBinding;
+use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::performancetiming::{PerformanceTiming, PerformanceTimingHelpers};
+use dom::window::Window;
+use time;
+
+pub type DOMHighResTimeStamp = f64;
+
+#[deriving(Encodable)]
+pub struct Performance {
+ reflector_: Reflector,
+ timing: JS<PerformanceTiming>,
+}
+
+impl Performance {
+ fn new_inherited(window: &JSRef<Window>) -> Performance {
+ Performance {
+ reflector_: Reflector::new(),
+ timing: JS::from_rooted(&PerformanceTiming::new(window)),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<Performance> {
+ let performance = Performance::new_inherited(window);
+ reflect_dom_object(box performance, &Window(*window),
+ PerformanceBinding::Wrap)
+ }
+}
+
+impl<'a> PerformanceMethods for JSRef<'a, Performance> {
+ fn Timing(&self) -> Temporary<PerformanceTiming> {
+ Temporary::new(self.timing.clone())
+ }
+
+ fn Now(&self) -> DOMHighResTimeStamp {
+ let navStart = self.timing.root().NavigationStartPrecise() as f64;
+ (time::precise_time_s() - navStart) as DOMHighResTimeStamp
+ }
+}
+
+impl Reflectable for Performance {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/performancetiming.rs b/components/script/dom/performancetiming.rs
new file mode 100644
index 00000000000..f4331e06c0b
--- /dev/null
+++ b/components/script/dom/performancetiming.rs
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::PerformanceTimingBinding;
+use dom::bindings::codegen::Bindings::PerformanceTimingBinding::PerformanceTimingMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub struct PerformanceTiming {
+ reflector_: Reflector,
+ navigationStart: u64,
+ navigationStartPrecise: f64,
+}
+
+impl PerformanceTiming {
+ pub fn new_inherited(navStart: u64, navStartPrecise: f64)
+ -> PerformanceTiming {
+ PerformanceTiming {
+ reflector_: Reflector::new(),
+ navigationStart: navStart,
+ navigationStartPrecise: navStartPrecise,
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<PerformanceTiming> {
+ let timing = PerformanceTiming::new_inherited(window.navigationStart,
+ window.navigationStartPrecise);
+ reflect_dom_object(box timing, &Window(*window),
+ PerformanceTimingBinding::Wrap)
+ }
+}
+
+impl<'a> PerformanceTimingMethods for JSRef<'a, PerformanceTiming> {
+ fn NavigationStart(&self) -> u64 {
+ self.navigationStart
+ }
+}
+
+pub trait PerformanceTimingHelpers {
+ fn NavigationStartPrecise(&self) -> f64;
+}
+
+impl<'a> PerformanceTimingHelpers for JSRef<'a, PerformanceTiming> {
+ fn NavigationStartPrecise(&self) -> f64 {
+ self.navigationStartPrecise
+ }
+}
+
+impl Reflectable for PerformanceTiming {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/processinginstruction.rs b/components/script/dom/processinginstruction.rs
new file mode 100644
index 00000000000..40e6ca63e04
--- /dev/null
+++ b/components/script/dom/processinginstruction.rs
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::ProcessingInstructionBinding;
+use dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods;
+use dom::bindings::codegen::InheritTypes::ProcessingInstructionDerived;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::characterdata::CharacterData;
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::node::{Node, ProcessingInstructionNodeTypeId};
+use servo_util::str::DOMString;
+
+/// An HTML processing instruction node.
+#[deriving(Encodable)]
+pub struct ProcessingInstruction {
+ pub characterdata: CharacterData,
+ pub target: DOMString,
+}
+
+impl ProcessingInstructionDerived for EventTarget {
+ fn is_processinginstruction(&self) -> bool {
+ self.type_id == NodeTargetTypeId(ProcessingInstructionNodeTypeId)
+ }
+}
+
+impl ProcessingInstruction {
+ pub fn new_inherited(target: DOMString, data: DOMString, document: &JSRef<Document>) -> ProcessingInstruction {
+ ProcessingInstruction {
+ characterdata: CharacterData::new_inherited(ProcessingInstructionNodeTypeId, data, document),
+ target: target
+ }
+ }
+
+ pub fn new(target: DOMString, data: DOMString, document: &JSRef<Document>) -> Temporary<ProcessingInstruction> {
+ let node = ProcessingInstruction::new_inherited(target, data, document);
+ Node::reflect_node(box node, document, ProcessingInstructionBinding::Wrap)
+ }
+}
+
+impl<'a> ProcessingInstructionMethods for JSRef<'a, ProcessingInstruction> {
+ fn Target(&self) -> DOMString {
+ self.target.clone()
+ }
+}
+
+impl Reflectable for ProcessingInstruction {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.characterdata.reflector()
+ }
+}
diff --git a/components/script/dom/progressevent.rs b/components/script/dom/progressevent.rs
new file mode 100644
index 00000000000..d001785984f
--- /dev/null
+++ b/components/script/dom/progressevent.rs
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::ProgressEventBinding;
+use dom::bindings::codegen::Bindings::ProgressEventBinding::ProgressEventMethods;
+use dom::bindings::codegen::InheritTypes::{EventCast, ProgressEventDerived};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::event::{Event, ProgressEventTypeId};
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct ProgressEvent {
+ event: Event,
+ length_computable: bool,
+ loaded: u64,
+ total: u64
+}
+
+impl ProgressEventDerived for Event {
+ fn is_progressevent(&self) -> bool {
+ self.type_id == ProgressEventTypeId
+ }
+}
+
+impl ProgressEvent {
+ pub fn new_inherited(length_computable: bool, loaded: u64, total: u64) -> ProgressEvent {
+ ProgressEvent {
+ event: Event::new_inherited(ProgressEventTypeId),
+ length_computable: length_computable,
+ loaded: loaded,
+ total: total
+ }
+ }
+ pub fn new(global: &GlobalRef, type_: DOMString,
+ can_bubble: bool, cancelable: bool,
+ length_computable: bool, loaded: u64, total: u64) -> Temporary<ProgressEvent> {
+ let ev = reflect_dom_object(box ProgressEvent::new_inherited(length_computable, loaded, total),
+ global,
+ ProgressEventBinding::Wrap).root();
+ let event: &JSRef<Event> = EventCast::from_ref(&*ev);
+ event.InitEvent(type_, can_bubble, cancelable);
+ Temporary::from_rooted(&*ev)
+ }
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &ProgressEventBinding::ProgressEventInit)
+ -> Fallible<Temporary<ProgressEvent>> {
+ let ev = ProgressEvent::new(global, type_, init.parent.bubbles, init.parent.cancelable,
+ init.lengthComputable, init.loaded, init.total);
+ Ok(ev)
+ }
+}
+
+impl<'a> ProgressEventMethods for JSRef<'a, ProgressEvent> {
+ fn LengthComputable(&self) -> bool {
+ self.length_computable
+ }
+ fn Loaded(&self) -> u64{
+ self.loaded
+ }
+ fn Total(&self) -> u64 {
+ self.total
+ }
+}
+
+impl Reflectable for ProgressEvent {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.event.reflector()
+ }
+}
diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs
new file mode 100644
index 00000000000..3ed02410a1e
--- /dev/null
+++ b/components/script/dom/range.rs
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::RangeBinding;
+use dom::bindings::codegen::Bindings::RangeBinding::RangeMethods;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::error::Fallible;
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::document::Document;
+
+#[deriving(Encodable)]
+pub struct Range {
+ reflector_: Reflector
+}
+
+impl Range {
+ pub fn new_inherited() -> Range {
+ Range {
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(document: &JSRef<Document>) -> Temporary<Range> {
+ let window = document.window.root();
+ reflect_dom_object(box Range::new_inherited(),
+ &Window(*window),
+ RangeBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<Range>> {
+ let document = global.as_window().Document().root();
+ Ok(Range::new(&*document))
+ }
+}
+
+impl<'a> RangeMethods for JSRef<'a, Range> {
+ /// http://dom.spec.whatwg.org/#dom-range-detach
+ fn Detach(&self) {
+ // This method intentionally left blank.
+ }
+}
+
+impl Reflectable for Range {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/screen.rs b/components/script/dom/screen.rs
new file mode 100644
index 00000000000..0e184d94ec3
--- /dev/null
+++ b/components/script/dom/screen.rs
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::ScreenBinding;
+use dom::bindings::codegen::Bindings::ScreenBinding::ScreenMethods;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub struct Screen {
+ reflector_: Reflector,
+}
+
+impl Screen {
+ pub fn new_inherited() -> Screen {
+ Screen {
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<Screen> {
+ reflect_dom_object(box Screen::new_inherited(),
+ &Window(*window),
+ ScreenBinding::Wrap)
+ }
+}
+
+impl<'a> ScreenMethods for JSRef<'a, Screen> {
+ fn ColorDepth(&self) -> u32 {
+ 24
+ }
+
+ fn PixelDepth(&self) -> u32 {
+ 24
+ }
+}
+
+impl Reflectable for Screen {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs
new file mode 100644
index 00000000000..b5d83fac620
--- /dev/null
+++ b/components/script/dom/testbinding.rs
@@ -0,0 +1,299 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::TestBindingBinding::TestBindingMethods;
+use dom::bindings::codegen::Bindings::TestBindingBinding::TestEnum;
+use dom::bindings::codegen::Bindings::TestBindingBinding::TestEnumValues::_empty;
+use dom::bindings::codegen::UnionTypes::BlobOrString::BlobOrString;
+use dom::bindings::codegen::UnionTypes::EventOrString::{EventOrString, eString};
+use dom::bindings::codegen::UnionTypes::HTMLElementOrLong::{HTMLElementOrLong, eLong};
+use dom::bindings::global::GlobalField;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::str::ByteString;
+use dom::bindings::utils::{Reflector, Reflectable};
+use dom::blob::Blob;
+use servo_util::str::DOMString;
+
+use js::jsapi::JSContext;
+use js::jsval::{JSVal, NullValue};
+
+#[deriving(Encodable)]
+pub struct TestBinding {
+ reflector: Reflector,
+ global: GlobalField,
+}
+
+impl<'a> TestBindingMethods for JSRef<'a, TestBinding> {
+ fn BooleanAttribute(&self) -> bool { false }
+ fn SetBooleanAttribute(&self, _: bool) {}
+ fn ByteAttribute(&self) -> i8 { 0 }
+ fn SetByteAttribute(&self, _: i8) {}
+ fn OctetAttribute(&self) -> u8 { 0 }
+ fn SetOctetAttribute(&self, _: u8) {}
+ fn ShortAttribute(&self) -> i16 { 0 }
+ fn SetShortAttribute(&self, _: i16) {}
+ fn UnsignedShortAttribute(&self) -> u16 { 0 }
+ fn SetUnsignedShortAttribute(&self, _: u16) {}
+ fn LongAttribute(&self) -> i32 { 0 }
+ fn SetLongAttribute(&self, _: i32) {}
+ fn UnsignedLongAttribute(&self) -> u32 { 0 }
+ fn SetUnsignedLongAttribute(&self, _: u32) {}
+ fn LongLongAttribute(&self) -> i64 { 0 }
+ fn SetLongLongAttribute(&self, _: i64) {}
+ fn UnsignedLongLongAttribute(&self) -> u64 { 0 }
+ fn SetUnsignedLongLongAttribute(&self, _: u64) {}
+ fn FloatAttribute(&self) -> f32 { 0. }
+ fn SetFloatAttribute(&self, _: f32) {}
+ fn DoubleAttribute(&self) -> f64 { 0. }
+ fn SetDoubleAttribute(&self, _: f64) {}
+ fn StringAttribute(&self) -> DOMString { "".to_string() }
+ fn SetStringAttribute(&self, _: DOMString) {}
+ fn ByteStringAttribute(&self) -> ByteString { ByteString::new(vec!()) }
+ fn SetByteStringAttribute(&self, _: ByteString) {}
+ fn EnumAttribute(&self) -> TestEnum { _empty }
+ fn SetEnumAttribute(&self, _: TestEnum) {}
+ fn InterfaceAttribute(&self) -> Temporary<Blob> {
+ let global = self.global.root();
+ Blob::new(&global.root_ref())
+ }
+ fn SetInterfaceAttribute(&self, _: &JSRef<Blob>) {}
+ fn UnionAttribute(&self) -> HTMLElementOrLong { eLong(0) }
+ fn SetUnionAttribute(&self, _: HTMLElementOrLong) {}
+ fn Union2Attribute(&self) -> EventOrString { eString("".to_string()) }
+ fn SetUnion2Attribute(&self, _: EventOrString) {}
+ fn AnyAttribute(&self, _: *mut JSContext) -> JSVal { NullValue() }
+ fn SetAnyAttribute(&self, _: *mut JSContext, _: JSVal) {}
+
+ fn GetBooleanAttributeNullable(&self) -> Option<bool> { Some(false) }
+ fn SetBooleanAttributeNullable(&self, _: Option<bool>) {}
+ fn GetByteAttributeNullable(&self) -> Option<i8> { Some(0) }
+ fn SetByteAttributeNullable(&self, _: Option<i8>) {}
+ fn GetOctetAttributeNullable(&self) -> Option<u8> { Some(0) }
+ fn SetOctetAttributeNullable(&self, _: Option<u8>) {}
+ fn GetShortAttributeNullable(&self) -> Option<i16> { Some(0) }
+ fn SetShortAttributeNullable(&self, _: Option<i16>) {}
+ fn GetUnsignedShortAttributeNullable(&self) -> Option<u16> { Some(0) }
+ fn SetUnsignedShortAttributeNullable(&self, _: Option<u16>) {}
+ fn GetLongAttributeNullable(&self) -> Option<i32> { Some(0) }
+ fn SetLongAttributeNullable(&self, _: Option<i32>) {}
+ fn GetUnsignedLongAttributeNullable(&self) -> Option<u32> { Some(0) }
+ fn SetUnsignedLongAttributeNullable(&self, _: Option<u32>) {}
+ fn GetLongLongAttributeNullable(&self) -> Option<i64> { Some(0) }
+ fn SetLongLongAttributeNullable(&self, _: Option<i64>) {}
+ fn GetUnsignedLongLongAttributeNullable(&self) -> Option<u64> { Some(0) }
+ fn SetUnsignedLongLongAttributeNullable(&self, _: Option<u64>) {}
+ fn GetFloatAttributeNullable(&self) -> Option<f32> { Some(0.) }
+ fn SetFloatAttributeNullable(&self, _: Option<f32>) {}
+ fn GetDoubleAttributeNullable(&self) -> Option<f64> { Some(0.) }
+ fn SetDoubleAttributeNullable(&self, _: Option<f64>) {}
+ fn GetByteStringAttributeNullable(&self) -> Option<ByteString> { Some(ByteString::new(vec!())) }
+ fn SetByteStringAttributeNullable(&self, _: Option<ByteString>) {}
+ fn GetStringAttributeNullable(&self) -> Option<DOMString> { Some("".to_string()) }
+ fn SetStringAttributeNullable(&self, _: Option<DOMString>) {}
+ fn GetEnumAttributeNullable(&self) -> Option<TestEnum> { Some(_empty) }
+ fn GetInterfaceAttributeNullable(&self) -> Option<Temporary<Blob>> {
+ let global = self.global.root();
+ Some(Blob::new(&global.root_ref()))
+ }
+ fn SetInterfaceAttributeNullable(&self, _: Option<JSRef<Blob>>) {}
+ fn GetUnionAttributeNullable(&self) -> Option<HTMLElementOrLong> { Some(eLong(0)) }
+ fn SetUnionAttributeNullable(&self, _: Option<HTMLElementOrLong>) {}
+ fn GetUnion2AttributeNullable(&self) -> Option<EventOrString> { Some(eString("".to_string())) }
+ fn SetUnion2AttributeNullable(&self, _: Option<EventOrString>) {}
+ fn ReceiveVoid(&self) -> () {}
+ fn ReceiveBoolean(&self) -> bool { false }
+ fn ReceiveByte(&self) -> i8 { 0 }
+ fn ReceiveOctet(&self) -> u8 { 0 }
+ fn ReceiveShort(&self) -> i16 { 0 }
+ fn ReceiveUnsignedShort(&self) -> u16 { 0 }
+ fn ReceiveLong(&self) -> i32 { 0 }
+ fn ReceiveUnsignedLong(&self) -> u32 { 0 }
+ fn ReceiveLongLong(&self) -> i64 { 0 }
+ fn ReceiveUnsignedLongLong(&self) -> u64 { 0 }
+ fn ReceiveFloat(&self) -> f32 { 0. }
+ fn ReceiveDouble(&self) -> f64 { 0. }
+ fn ReceiveString(&self) -> DOMString { "".to_string() }
+ fn ReceiveByteString(&self) -> ByteString { ByteString::new(vec!()) }
+ fn ReceiveEnum(&self) -> TestEnum { _empty }
+ fn ReceiveInterface(&self) -> Temporary<Blob> {
+ let global = self.global.root();
+ Blob::new(&global.root_ref())
+ }
+ fn ReceiveAny(&self, _: *mut JSContext) -> JSVal { NullValue() }
+ fn ReceiveUnion(&self) -> HTMLElementOrLong { eLong(0) }
+ fn ReceiveUnion2(&self) -> EventOrString { eString("".to_string()) }
+
+ fn ReceiveNullableBoolean(&self) -> Option<bool> { Some(false) }
+ fn ReceiveNullableByte(&self) -> Option<i8> { Some(0) }
+ fn ReceiveNullableOctet(&self) -> Option<u8> { Some(0) }
+ fn ReceiveNullableShort(&self) -> Option<i16> { Some(0) }
+ fn ReceiveNullableUnsignedShort(&self) -> Option<u16> { Some(0) }
+ fn ReceiveNullableLong(&self) -> Option<i32> { Some(0) }
+ fn ReceiveNullableUnsignedLong(&self) -> Option<u32> { Some(0) }
+ fn ReceiveNullableLongLong(&self) -> Option<i64> { Some(0) }
+ fn ReceiveNullableUnsignedLongLong(&self) -> Option<u64> { Some(0) }
+ fn ReceiveNullableFloat(&self) -> Option<f32> { Some(0.) }
+ fn ReceiveNullableDouble(&self) -> Option<f64> { Some(0.) }
+ fn ReceiveNullableString(&self) -> Option<DOMString> { Some("".to_string()) }
+ fn ReceiveNullableByteString(&self) -> Option<ByteString> { Some(ByteString::new(vec!())) }
+ fn ReceiveNullableEnum(&self) -> Option<TestEnum> { Some(_empty) }
+ fn ReceiveNullableInterface(&self) -> Option<Temporary<Blob>> {
+ let global = self.global.root();
+ Some(Blob::new(&global.root_ref()))
+ }
+ fn ReceiveNullableUnion(&self) -> Option<HTMLElementOrLong> { Some(eLong(0)) }
+ fn ReceiveNullableUnion2(&self) -> Option<EventOrString> { Some(eString("".to_string())) }
+
+ fn PassBoolean(&self, _: bool) {}
+ fn PassByte(&self, _: i8) {}
+ fn PassOctet(&self, _: u8) {}
+ fn PassShort(&self, _: i16) {}
+ fn PassUnsignedShort(&self, _: u16) {}
+ fn PassLong(&self, _: i32) {}
+ fn PassUnsignedLong(&self, _: u32) {}
+ fn PassLongLong(&self, _: i64) {}
+ fn PassUnsignedLongLong(&self, _: u64) {}
+ fn PassFloat(&self, _: f32) {}
+ fn PassDouble(&self, _: f64) {}
+ fn PassString(&self, _: DOMString) {}
+ fn PassByteString(&self, _: ByteString) {}
+ fn PassEnum(&self, _: TestEnum) {}
+ fn PassInterface(&self, _: &JSRef<Blob>) {}
+ fn PassUnion(&self, _: HTMLElementOrLong) {}
+ fn PassUnion2(&self, _: EventOrString) {}
+ fn PassUnion3(&self, _: BlobOrString) {}
+ fn PassAny(&self, _: *mut JSContext, _: JSVal) {}
+
+ fn PassNullableBoolean(&self, _: Option<bool>) {}
+ fn PassNullableByte(&self, _: Option<i8>) {}
+ fn PassNullableOctet(&self, _: Option<u8>) {}
+ fn PassNullableShort(&self, _: Option<i16>) {}
+ fn PassNullableUnsignedShort(&self, _: Option<u16>) {}
+ fn PassNullableLong(&self, _: Option<i32>) {}
+ fn PassNullableUnsignedLong(&self, _: Option<u32>) {}
+ fn PassNullableLongLong(&self, _: Option<i64>) {}
+ fn PassNullableUnsignedLongLong(&self, _: Option<u64>) {}
+ fn PassNullableFloat(&self, _: Option<f32>) {}
+ fn PassNullableDouble(&self, _: Option<f64>) {}
+ fn PassNullableString(&self, _: Option<DOMString>) {}
+ fn PassNullableByteString(&self, _: Option<ByteString>) {}
+ // fn PassNullableEnum(&self, _: Option<TestEnum>) {}
+ fn PassNullableInterface(&self, _: Option<JSRef<Blob>>) {}
+ fn PassNullableUnion(&self, _: Option<HTMLElementOrLong>) {}
+ fn PassNullableUnion2(&self, _: Option<EventOrString>) {}
+
+ fn PassOptionalBoolean(&self, _: Option<bool>) {}
+ fn PassOptionalByte(&self, _: Option<i8>) {}
+ fn PassOptionalOctet(&self, _: Option<u8>) {}
+ fn PassOptionalShort(&self, _: Option<i16>) {}
+ fn PassOptionalUnsignedShort(&self, _: Option<u16>) {}
+ fn PassOptionalLong(&self, _: Option<i32>) {}
+ fn PassOptionalUnsignedLong(&self, _: Option<u32>) {}
+ fn PassOptionalLongLong(&self, _: Option<i64>) {}
+ fn PassOptionalUnsignedLongLong(&self, _: Option<u64>) {}
+ fn PassOptionalFloat(&self, _: Option<f32>) {}
+ fn PassOptionalDouble(&self, _: Option<f64>) {}
+ fn PassOptionalString(&self, _: Option<DOMString>) {}
+ fn PassOptionalByteString(&self, _: Option<ByteString>) {}
+ fn PassOptionalEnum(&self, _: Option<TestEnum>) {}
+ fn PassOptionalInterface(&self, _: Option<JSRef<Blob>>) {}
+ fn PassOptionalUnion(&self, _: Option<HTMLElementOrLong>) {}
+ fn PassOptionalUnion2(&self, _: Option<EventOrString>) {}
+ fn PassOptionalAny(&self, _: *mut JSContext, _: JSVal) {}
+
+ fn PassOptionalNullableBoolean(&self, _: Option<Option<bool>>) {}
+ fn PassOptionalNullableByte(&self, _: Option<Option<i8>>) {}
+ fn PassOptionalNullableOctet(&self, _: Option<Option<u8>>) {}
+ fn PassOptionalNullableShort(&self, _: Option<Option<i16>>) {}
+ fn PassOptionalNullableUnsignedShort(&self, _: Option<Option<u16>>) {}
+ fn PassOptionalNullableLong(&self, _: Option<Option<i32>>) {}
+ fn PassOptionalNullableUnsignedLong(&self, _: Option<Option<u32>>) {}
+ fn PassOptionalNullableLongLong(&self, _: Option<Option<i64>>) {}
+ fn PassOptionalNullableUnsignedLongLong(&self, _: Option<Option<u64>>) {}
+ fn PassOptionalNullableFloat(&self, _: Option<Option<f32>>) {}
+ fn PassOptionalNullableDouble(&self, _: Option<Option<f64>>) {}
+ fn PassOptionalNullableString(&self, _: Option<Option<DOMString>>) {}
+ fn PassOptionalNullableByteString(&self, _: Option<Option<ByteString>>) {}
+ // fn PassOptionalNullableEnum(&self, _: Option<Option<TestEnum>>) {}
+ fn PassOptionalNullableInterface(&self, _: Option<Option<JSRef<Blob>>>) {}
+ fn PassOptionalNullableUnion(&self, _: Option<Option<HTMLElementOrLong>>) {}
+ fn PassOptionalNullableUnion2(&self, _: Option<Option<EventOrString>>) {}
+
+ fn PassOptionalBooleanWithDefault(&self, _: bool) {}
+ fn PassOptionalByteWithDefault(&self, _: i8) {}
+ fn PassOptionalOctetWithDefault(&self, _: u8) {}
+ fn PassOptionalShortWithDefault(&self, _: i16) {}
+ fn PassOptionalUnsignedShortWithDefault(&self, _: u16) {}
+ fn PassOptionalLongWithDefault(&self, _: i32) {}
+ fn PassOptionalUnsignedLongWithDefault(&self, _: u32) {}
+ fn PassOptionalLongLongWithDefault(&self, _: i64) {}
+ fn PassOptionalUnsignedLongLongWithDefault(&self, _: u64) {}
+ fn PassOptionalStringWithDefault(&self, _: DOMString) {}
+ fn PassOptionalEnumWithDefault(&self, _: TestEnum) {}
+
+ fn PassOptionalNullableBooleanWithDefault(&self, _: Option<bool>) {}
+ fn PassOptionalNullableByteWithDefault(&self, _: Option<i8>) {}
+ fn PassOptionalNullableOctetWithDefault(&self, _: Option<u8>) {}
+ fn PassOptionalNullableShortWithDefault(&self, _: Option<i16>) {}
+ fn PassOptionalNullableUnsignedShortWithDefault(&self, _: Option<u16>) {}
+ fn PassOptionalNullableLongWithDefault(&self, _: Option<i32>) {}
+ fn PassOptionalNullableUnsignedLongWithDefault(&self, _: Option<u32>) {}
+ fn PassOptionalNullableLongLongWithDefault(&self, _: Option<i64>) {}
+ fn PassOptionalNullableUnsignedLongLongWithDefault(&self, _: Option<u64>) {}
+ // fn PassOptionalNullableFloatWithDefault(&self, _: Option<f32>) {}
+ // fn PassOptionalNullableDoubleWithDefault(&self, _: Option<f64>) {}
+ fn PassOptionalNullableStringWithDefault(&self, _: Option<DOMString>) {}
+ fn PassOptionalNullableByteStringWithDefault(&self, _: Option<ByteString>) {}
+ // fn PassOptionalNullableEnumWithDefault(&self, _: Option<TestEnum>) {}
+ fn PassOptionalNullableInterfaceWithDefault(&self, _: Option<JSRef<Blob>>) {}
+ fn PassOptionalNullableUnionWithDefault(&self, _: Option<HTMLElementOrLong>) {}
+ fn PassOptionalNullableUnion2WithDefault(&self, _: Option<EventOrString>) {}
+ fn PassOptionalAnyWithDefault(&self, _: *mut JSContext, _: JSVal) {}
+
+ fn PassOptionalNullableBooleanWithNonNullDefault(&self, _: Option<bool>) {}
+ fn PassOptionalNullableByteWithNonNullDefault(&self, _: Option<i8>) {}
+ fn PassOptionalNullableOctetWithNonNullDefault(&self, _: Option<u8>) {}
+ fn PassOptionalNullableShortWithNonNullDefault(&self, _: Option<i16>) {}
+ fn PassOptionalNullableUnsignedShortWithNonNullDefault(&self, _: Option<u16>) {}
+ fn PassOptionalNullableLongWithNonNullDefault(&self, _: Option<i32>) {}
+ fn PassOptionalNullableUnsignedLongWithNonNullDefault(&self, _: Option<u32>) {}
+ fn PassOptionalNullableLongLongWithNonNullDefault(&self, _: Option<i64>) {}
+ fn PassOptionalNullableUnsignedLongLongWithNonNullDefault(&self, _: Option<u64>) {}
+ // fn PassOptionalNullableFloatWithNonNullDefault(&self, _: Option<f32>) {}
+ // fn PassOptionalNullableDoubleWithNonNullDefault(&self, _: Option<f64>) {}
+ fn PassOptionalNullableStringWithNonNullDefault(&self, _: Option<DOMString>) {}
+ // fn PassOptionalNullableEnumWithNonNullDefault(&self, _: Option<TestEnum>) {}
+
+ fn PassVariadicBoolean(&self, _: Vec<bool>) {}
+ fn PassVariadicByte(&self, _: Vec<i8>) {}
+ fn PassVariadicOctet(&self, _: Vec<u8>) {}
+ fn PassVariadicShort(&self, _: Vec<i16>) {}
+ fn PassVariadicUnsignedShort(&self, _: Vec<u16>) {}
+ fn PassVariadicLong(&self, _: Vec<i32>) {}
+ fn PassVariadicUnsignedLong(&self, _: Vec<u32>) {}
+ fn PassVariadicLongLong(&self, _: Vec<i64>) {}
+ fn PassVariadicUnsignedLongLong(&self, _: Vec<u64>) {}
+ fn PassVariadicFloat(&self, _: Vec<f32>) {}
+ fn PassVariadicDouble(&self, _: Vec<f64>) {}
+ fn PassVariadicString(&self, _: Vec<DOMString>) {}
+ fn PassVariadicByteString(&self, _: Vec<ByteString>) {}
+ fn PassVariadicEnum(&self, _: Vec<TestEnum>) {}
+ // fn PassVariadicInterface(&self, _: Vec<JSRef<Blob>>) {}
+ fn PassVariadicUnion(&self, _: Vec<HTMLElementOrLong>) {}
+ fn PassVariadicUnion2(&self, _: Vec<EventOrString>) {}
+ fn PassVariadicUnion3(&self, _: Vec<BlobOrString>) {}
+ fn PassVariadicAny(&self, _: *mut JSContext, _: Vec<JSVal>) {}
+}
+
+impl TestBinding {
+ pub fn BooleanAttributeStatic() -> bool { false }
+ pub fn SetBooleanAttributeStatic(_: bool) {}
+ pub fn ReceiveVoidStatic() {}
+}
+
+impl Reflectable for TestBinding {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector
+ }
+}
diff --git a/components/script/dom/text.rs b/components/script/dom/text.rs
new file mode 100644
index 00000000000..1bbd25cb8ff
--- /dev/null
+++ b/components/script/dom/text.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::TextBinding;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::TextDerived;
+use dom::bindings::error::Fallible;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::characterdata::CharacterData;
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, NodeTargetTypeId};
+use dom::node::{Node, TextNodeTypeId};
+use servo_util::str::DOMString;
+
+/// An HTML text node.
+#[deriving(Encodable)]
+pub struct Text {
+ pub characterdata: CharacterData,
+}
+
+impl TextDerived for EventTarget {
+ fn is_text(&self) -> bool {
+ self.type_id == NodeTargetTypeId(TextNodeTypeId)
+ }
+}
+
+impl Text {
+ pub fn new_inherited(text: DOMString, document: &JSRef<Document>) -> Text {
+ Text {
+ characterdata: CharacterData::new_inherited(TextNodeTypeId, text, document)
+ }
+ }
+
+ pub fn new(text: DOMString, document: &JSRef<Document>) -> Temporary<Text> {
+ let node = Text::new_inherited(text, document);
+ Node::reflect_node(box node, document, TextBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef, text: DOMString) -> Fallible<Temporary<Text>> {
+ let document = global.as_window().Document().root();
+ Ok(Text::new(text, &*document))
+ }
+}
+
+impl Reflectable for Text {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.characterdata.reflector()
+ }
+}
diff --git a/components/script/dom/treewalker.rs b/components/script/dom/treewalker.rs
new file mode 100644
index 00000000000..dc192c359ce
--- /dev/null
+++ b/components/script/dom/treewalker.rs
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::TreeWalkerBinding;
+use dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+
+#[deriving(Encodable)]
+pub struct TreeWalker {
+ pub reflector_: Reflector
+}
+
+impl TreeWalker {
+ pub fn new_inherited() -> TreeWalker {
+ TreeWalker {
+ reflector_: Reflector::new()
+ }
+ }
+
+ pub fn new(global: &GlobalRef) -> Temporary<TreeWalker> {
+ reflect_dom_object(box TreeWalker::new_inherited(), global, TreeWalkerBinding::Wrap)
+ }
+}
+
+impl<'a> TreeWalkerMethods for JSRef<'a, TreeWalker> {
+}
+
+impl Reflectable for TreeWalker {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/uievent.rs b/components/script/dom/uievent.rs
new file mode 100644
index 00000000000..c91f0fdb787
--- /dev/null
+++ b/components/script/dom/uievent.rs
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventBinding::EventMethods;
+use dom::bindings::codegen::Bindings::UIEventBinding;
+use dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
+use dom::bindings::codegen::InheritTypes::{EventCast, UIEventDerived};
+use dom::bindings::error::Fallible;
+use dom::bindings::global::{GlobalRef, Window};
+use dom::bindings::js::{JS, JSRef, RootedReference, Temporary, OptionalSettable};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::event::{Event, EventTypeId, UIEventTypeId};
+use dom::window::Window;
+use servo_util::str::DOMString;
+
+use std::cell::Cell;
+
+#[deriving(Encodable)]
+pub struct UIEvent {
+ pub event: Event,
+ view: Cell<Option<JS<Window>>>,
+ detail: Traceable<Cell<i32>>
+}
+
+impl UIEventDerived for Event {
+ fn is_uievent(&self) -> bool {
+ self.type_id == UIEventTypeId
+ }
+}
+
+impl UIEvent {
+ pub fn new_inherited(type_id: EventTypeId) -> UIEvent {
+ UIEvent {
+ event: Event::new_inherited(type_id),
+ view: Cell::new(None),
+ detail: Traceable::new(Cell::new(0)),
+ }
+ }
+
+ pub fn new_uninitialized(window: &JSRef<Window>) -> Temporary<UIEvent> {
+ reflect_dom_object(box UIEvent::new_inherited(UIEventTypeId),
+ &Window(*window),
+ UIEventBinding::Wrap)
+ }
+
+ pub fn new(window: &JSRef<Window>,
+ type_: DOMString,
+ can_bubble: bool,
+ cancelable: bool,
+ view: Option<JSRef<Window>>,
+ detail: i32) -> Temporary<UIEvent> {
+ let ev = UIEvent::new_uninitialized(window).root();
+ ev.deref().InitUIEvent(type_, can_bubble, cancelable, view, detail);
+ Temporary::from_rooted(&*ev)
+ }
+
+ pub fn Constructor(global: &GlobalRef,
+ type_: DOMString,
+ init: &UIEventBinding::UIEventInit) -> Fallible<Temporary<UIEvent>> {
+ let event = UIEvent::new(global.as_window(), type_,
+ init.parent.bubbles, init.parent.cancelable,
+ init.view.root_ref(), init.detail);
+ Ok(event)
+ }
+}
+
+impl<'a> UIEventMethods for JSRef<'a, UIEvent> {
+ fn GetView(&self) -> Option<Temporary<Window>> {
+ self.view.get().map(|view| Temporary::new(view))
+ }
+
+ fn Detail(&self) -> i32 {
+ self.detail.deref().get()
+ }
+
+ fn InitUIEvent(&self,
+ type_: DOMString,
+ can_bubble: bool,
+ cancelable: bool,
+ view: Option<JSRef<Window>>,
+ detail: i32) {
+ let event: &JSRef<Event> = EventCast::from_ref(self);
+ event.InitEvent(type_, can_bubble, cancelable);
+ self.view.assign(view);
+ self.detail.deref().set(detail);
+ }
+}
+
+impl Reflectable for UIEvent {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.event.reflector()
+ }
+}
diff --git a/components/script/dom/urlsearchparams.rs b/components/script/dom/urlsearchparams.rs
new file mode 100644
index 00000000000..63fffe6bbf5
--- /dev/null
+++ b/components/script/dom/urlsearchparams.rs
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::URLSearchParamsBinding;
+use dom::bindings::codegen::Bindings::URLSearchParamsBinding::URLSearchParamsMethods;
+use dom::bindings::codegen::UnionTypes::StringOrURLSearchParams::{StringOrURLSearchParams, eURLSearchParams, eString};
+use dom::bindings::error::{Fallible};
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::trace::Traceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+
+use servo_util::str::DOMString;
+
+use encoding::all::UTF_8;
+use encoding::types::{Encoding, EncodeReplace};
+
+use std::cell::RefCell;
+use std::collections::hashmap::HashMap;
+use std::fmt::radix;
+use std::ascii::OwnedStrAsciiExt;
+
+#[deriving(Encodable)]
+pub struct URLSearchParams {
+ data: Traceable<RefCell<HashMap<DOMString, Vec<DOMString>>>>,
+ reflector_: Reflector,
+}
+
+impl URLSearchParams {
+ pub fn new_inherited() -> URLSearchParams {
+ URLSearchParams {
+ data: Traceable::new(RefCell::new(HashMap::new())),
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(global: &GlobalRef) -> Temporary<URLSearchParams> {
+ reflect_dom_object(box URLSearchParams::new_inherited(), global, URLSearchParamsBinding::Wrap)
+ }
+
+ pub fn Constructor(global: &GlobalRef, init: Option<StringOrURLSearchParams>) -> Fallible<Temporary<URLSearchParams>> {
+ let usp = URLSearchParams::new(global).root();
+ match init {
+ Some(eString(_s)) => {
+ // XXXManishearth we need to parse the input here
+ // http://url.spec.whatwg.org/#concept-urlencoded-parser
+ // We can use rust-url's implementation here:
+ // https://github.com/SimonSapin/rust-url/blob/master/form_urlencoded.rs#L29
+ },
+ Some(eURLSearchParams(u)) => {
+ let u = u.root();
+ let mut map = usp.deref().data.deref().borrow_mut();
+ *map = u.data.deref().borrow().clone();
+ },
+ None => {}
+ }
+ Ok(Temporary::from_rooted(&*usp))
+ }
+}
+
+impl<'a> URLSearchParamsMethods for JSRef<'a, URLSearchParams> {
+ fn Append(&self, name: DOMString, value: DOMString) {
+ self.data.deref().borrow_mut().insert_or_update_with(name, vec!(value.clone()),
+ |_k, v| v.push(value.clone()));
+ self.update_steps();
+ }
+
+ fn Delete(&self, name: DOMString) {
+ self.data.deref().borrow_mut().remove(&name);
+ self.update_steps();
+ }
+
+ fn Get(&self, name: DOMString) -> Option<DOMString> {
+ self.data.deref().borrow().find_equiv(&name).map(|v| v[0].clone())
+ }
+
+ fn Has(&self, name: DOMString) -> bool {
+ self.data.deref().borrow().contains_key_equiv(&name)
+ }
+
+ fn Set(&self, name: DOMString, value: DOMString) {
+ self.data.deref().borrow_mut().insert(name, vec!(value));
+ self.update_steps();
+ }
+}
+
+impl Reflectable for URLSearchParams {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
+
+pub trait URLSearchParamsHelpers {
+ fn serialize(&self, encoding: Option<&'static Encoding>) -> Vec<u8>;
+ fn update_steps(&self);
+}
+
+impl URLSearchParamsHelpers for URLSearchParams {
+ fn serialize(&self, encoding: Option<&'static Encoding>) -> Vec<u8> {
+ // http://url.spec.whatwg.org/#concept-urlencoded-serializer
+ fn serialize_string(value: &DOMString, encoding: &'static Encoding) -> Vec<u8> {
+ // http://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
+
+ let value = value.as_slice();
+ // XXXManishearth should this be a strict encoding? Can unwrap()ing the result fail?
+ let value = encoding.encode(value, EncodeReplace).unwrap();
+ let mut buf = vec!();
+ for i in value.iter() {
+ let append = match *i {
+ 0x20 => vec!(0x2B),
+ 0x2A | 0x2D | 0x2E |
+ 0x30 .. 0x39 | 0x41 .. 0x5A |
+ 0x5F | 0x61..0x7A => vec!(*i),
+ a => {
+ // http://url.spec.whatwg.org/#percent-encode
+ let mut encoded = vec!(0x25); // %
+ let s = format!("{}", radix(a, 16)).into_ascii_upper();
+ let bytes = s.as_bytes();
+ encoded.push_all(bytes);
+ encoded
+ }
+ };
+ buf.push_all(append.as_slice());
+ }
+ buf
+ }
+ let encoding = encoding.unwrap_or(UTF_8 as &'static Encoding);
+ let mut buf = vec!();
+ let mut first_pair = true;
+ for (k, v) in self.data.deref().borrow().iter() {
+ let name = serialize_string(k, encoding);
+ for val in v.iter() {
+ let value = serialize_string(val, encoding);
+ if first_pair {
+ first_pair = false;
+ } else {
+ buf.push(0x26); // &
+ }
+ buf.push_all(name.as_slice());
+ buf.push(0x3D); // =
+ buf.push_all(value.as_slice())
+ }
+ }
+ buf
+ }
+
+ fn update_steps(&self) {
+ // XXXManishearth Implement this when the URL interface is implemented
+ // http://url.spec.whatwg.org/#concept-uq-update
+ }
+}
diff --git a/components/script/dom/validitystate.rs b/components/script/dom/validitystate.rs
new file mode 100644
index 00000000000..c2901009c41
--- /dev/null
+++ b/components/script/dom/validitystate.rs
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::ValidityStateBinding;
+use dom::bindings::global::Window;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::window::Window;
+
+#[deriving(Encodable)]
+pub struct ValidityState {
+ reflector_: Reflector,
+ state: u8,
+}
+
+impl ValidityState {
+ pub fn new_inherited() -> ValidityState {
+ ValidityState {
+ reflector_: Reflector::new(),
+ state: 0,
+ }
+ }
+
+ pub fn new(window: &JSRef<Window>) -> Temporary<ValidityState> {
+ reflect_dom_object(box ValidityState::new_inherited(),
+ &Window(*window),
+ ValidityStateBinding::Wrap)
+ }
+}
+
+impl Reflectable for ValidityState {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs
new file mode 100644
index 00000000000..9ce38c4908b
--- /dev/null
+++ b/components/script/dom/virtualmethods.rs
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::attr::{AttrValue, StringAttrValue};
+use dom::bindings::codegen::InheritTypes::ElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLAnchorElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLAreaElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLBodyElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLButtonElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLCanvasElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLFieldSetElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLIFrameElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLImageElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLInputElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLLinkElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLObjectElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLOptGroupElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLOptionElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLSelectElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLStyleElementCast;
+use dom::bindings::codegen::InheritTypes::HTMLTextAreaElementCast;
+use dom::bindings::js::JSRef;
+use dom::element::Element;
+use dom::element::ElementTypeId;
+use dom::element::HTMLAnchorElementTypeId;
+use dom::element::HTMLAreaElementTypeId;
+use dom::element::HTMLBodyElementTypeId;
+use dom::element::HTMLButtonElementTypeId;
+use dom::element::HTMLCanvasElementTypeId;
+use dom::element::HTMLFieldSetElementTypeId;
+use dom::element::HTMLIFrameElementTypeId;
+use dom::element::HTMLImageElementTypeId;
+use dom::element::HTMLInputElementTypeId;
+use dom::element::HTMLLinkElementTypeId;
+use dom::element::HTMLObjectElementTypeId;
+use dom::element::HTMLOptGroupElementTypeId;
+use dom::element::HTMLOptionElementTypeId;
+use dom::element::HTMLSelectElementTypeId;
+use dom::element::HTMLStyleElementTypeId;
+use dom::element::HTMLTextAreaElementTypeId;
+use dom::event::Event;
+use dom::htmlanchorelement::HTMLAnchorElement;
+use dom::htmlareaelement::HTMLAreaElement;
+use dom::htmlbodyelement::HTMLBodyElement;
+use dom::htmlbuttonelement::HTMLButtonElement;
+use dom::htmlcanvaselement::HTMLCanvasElement;
+use dom::htmlelement::HTMLElement;
+use dom::htmlfieldsetelement::HTMLFieldSetElement;
+use dom::htmliframeelement::HTMLIFrameElement;
+use dom::htmlimageelement::HTMLImageElement;
+use dom::htmlinputelement::HTMLInputElement;
+use dom::htmllinkelement::HTMLLinkElement;
+use dom::htmlobjectelement::HTMLObjectElement;
+use dom::htmloptgroupelement::HTMLOptGroupElement;
+use dom::htmloptionelement::HTMLOptionElement;
+use dom::htmlselectelement::HTMLSelectElement;
+use dom::htmlstyleelement::HTMLStyleElement;
+use dom::htmltextareaelement::HTMLTextAreaElement;
+use dom::node::{Node, NodeHelpers, ElementNodeTypeId};
+
+use servo_util::atom::Atom;
+use servo_util::str::DOMString;
+
+/// Trait to allow DOM nodes to opt-in to overriding (or adding to) common
+/// behaviours. Replicates the effect of C++ virtual methods.
+pub trait VirtualMethods {
+ /// Returns self as the superclass of the implementation for this trait,
+ /// if any.
+ fn super_type<'a>(&'a self) -> Option<&'a VirtualMethods>;
+
+ /// Called when changing or adding attributes, after the attribute's value
+ /// has been updated.
+ fn after_set_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.after_set_attr(name, value),
+ _ => (),
+ }
+ }
+
+ /// Called when changing or removing attributes, before any modification
+ /// has taken place.
+ fn before_remove_attr(&self, name: &Atom, value: DOMString) {
+ match self.super_type() {
+ Some(ref s) => s.before_remove_attr(name, value),
+ _ => (),
+ }
+ }
+
+ /// Returns the right AttrValue variant for the attribute with name `name`
+ /// on this element.
+ fn parse_plain_attribute(&self, name: &str, value: DOMString) -> AttrValue {
+ match self.super_type() {
+ Some(ref s) => s.parse_plain_attribute(name, value),
+ _ => StringAttrValue(value),
+ }
+ }
+
+ /// Called when a Node is appended to a tree, where 'tree_in_doc' indicates
+ /// whether the tree is part of a Document.
+ fn bind_to_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.bind_to_tree(tree_in_doc),
+ _ => (),
+ }
+ }
+
+ /// Called when a Node is removed from a tree, where 'tree_in_doc'
+ /// indicates whether the tree is part of a Document.
+ fn unbind_from_tree(&self, tree_in_doc: bool) {
+ match self.super_type() {
+ Some(ref s) => s.unbind_from_tree(tree_in_doc),
+ _ => (),
+ }
+ }
+
+ /// Called on the parent when a node is added to its child list.
+ fn child_inserted(&self, child: &JSRef<Node>) {
+ match self.super_type() {
+ Some(ref s) => s.child_inserted(child),
+ _ => (),
+ }
+ }
+
+ /// Called during event dispatch after the bubbling phase completes.
+ fn handle_event(&self, event: &JSRef<Event>) {
+ match self.super_type() {
+ Some(s) => {
+ s.handle_event(event);
+ }
+ _ => (),
+ }
+ }
+}
+
+/// Obtain a VirtualMethods instance for a given Node-derived object. Any
+/// method call on the trait object will invoke the corresponding method on the
+/// concrete type, propagating up the parent hierarchy unless otherwise
+/// interrupted.
+pub fn vtable_for<'a>(node: &'a JSRef<Node>) -> &'a VirtualMethods {
+ match node.type_id() {
+ ElementNodeTypeId(HTMLAnchorElementTypeId) => {
+ let element: &JSRef<HTMLAnchorElement> = HTMLAnchorElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLAreaElementTypeId) => {
+ let element: &JSRef<HTMLAreaElement> = HTMLAreaElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLBodyElementTypeId) => {
+ let element: &JSRef<HTMLBodyElement> = HTMLBodyElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLButtonElementTypeId) => {
+ let element: &JSRef<HTMLButtonElement> = HTMLButtonElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLCanvasElementTypeId) => {
+ let element: &JSRef<HTMLCanvasElement> = HTMLCanvasElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLFieldSetElementTypeId) => {
+ let element: &JSRef<HTMLFieldSetElement> = HTMLFieldSetElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLImageElementTypeId) => {
+ let element: &JSRef<HTMLImageElement> = HTMLImageElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLIFrameElementTypeId) => {
+ let element: &JSRef<HTMLIFrameElement> = HTMLIFrameElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLInputElementTypeId) => {
+ let element: &JSRef<HTMLInputElement> = HTMLInputElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLLinkElementTypeId) => {
+ let element: &JSRef<HTMLLinkElement> = HTMLLinkElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLObjectElementTypeId) => {
+ let element: &JSRef<HTMLObjectElement> = HTMLObjectElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLOptGroupElementTypeId) => {
+ let element: &JSRef<HTMLOptGroupElement> = HTMLOptGroupElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLOptionElementTypeId) => {
+ let element: &JSRef<HTMLOptionElement> = HTMLOptionElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLSelectElementTypeId) => {
+ let element: &JSRef<HTMLSelectElement> = HTMLSelectElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLStyleElementTypeId) => {
+ let element: &JSRef<HTMLStyleElement> = HTMLStyleElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) => {
+ let element: &JSRef<HTMLTextAreaElement> = HTMLTextAreaElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(ElementTypeId) => {
+ let element: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ ElementNodeTypeId(_) => {
+ let element: &JSRef<HTMLElement> = HTMLElementCast::to_ref(node).unwrap();
+ element as &VirtualMethods
+ }
+ _ => {
+ node as &VirtualMethods
+ }
+ }
+}
diff --git a/components/script/dom/webidls/Attr.webidl b/components/script/dom/webidls/Attr.webidl
new file mode 100644
index 00000000000..2b3d18150d8
--- /dev/null
+++ b/components/script/dom/webidls/Attr.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-attr
+ *
+ */
+
+interface Attr {
+ readonly attribute DOMString localName;
+ attribute DOMString value;
+
+ readonly attribute DOMString name;
+ readonly attribute DOMString? namespaceURI;
+ readonly attribute DOMString? prefix;
+};
diff --git a/components/script/dom/webidls/Blob.webidl b/components/script/dom/webidls/Blob.webidl
new file mode 100644
index 00000000000..3a544024338
--- /dev/null
+++ b/components/script/dom/webidls/Blob.webidl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dev.w3.org/2006/webapi/FileAPI/#dfn-Blob
+//[Exposed=Window,Worker][Constructor,
+// Constructor(sequence<(ArrayBuffer or ArrayBufferView or Blob or DOMString)> blobParts, optional BlobPropertyBag options)]
+[Constructor]
+interface Blob {
+
+ //readonly attribute unsigned long long size;
+ //readonly attribute DOMString type;
+ //readonly attribute boolean isClosed;
+
+ //slice Blob into byte-ranged chunks
+
+ //Blob slice([Clamp] optional long long start,
+ // [Clamp] optional long long end,
+ // optional DOMString contentType);
+ //void close();
+
+};
+
+dictionary BlobPropertyBag {
+
+ DOMString type = "";
+
+};
diff --git a/components/script/dom/webidls/CanvasRenderingContext2D.webidl b/components/script/dom/webidls/CanvasRenderingContext2D.webidl
new file mode 100644
index 00000000000..2043347bfd2
--- /dev/null
+++ b/components/script/dom/webidls/CanvasRenderingContext2D.webidl
@@ -0,0 +1,104 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#2dcontext
+//[Constructor(optional unsigned long width, unsigned long height), Exposed=Window,Worker]
+interface CanvasRenderingContext2D {
+
+ // back-reference to the canvas
+ readonly attribute HTMLCanvasElement canvas;
+
+ // canvas dimensions
+ // attribute unsigned long width;
+ // attribute unsigned long height;
+
+ // for contexts that aren't directly fixed to a specific canvas
+ //void commit(); // push the image to the output bitmap
+
+ // state
+ //void save(); // push state on state stack
+ //void restore(); // pop state stack and restore state
+
+ // transformations (default transform is the identity matrix)
+ // attribute SVGMatrix currentTransform;
+ //void scale(unrestricted double x, unrestricted double y);
+ //void rotate(unrestricted double angle);
+ //void translate(unrestricted double x, unrestricted double y);
+ //void transform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
+ //void setTransform(unrestricted double a, unrestricted double b, unrestricted double c, unrestricted double d, unrestricted double e, unrestricted double f);
+ //void resetTransform();
+
+ // compositing
+ // attribute unrestricted double globalAlpha; // (default 1.0)
+ // attribute DOMString globalCompositeOperation; // (default source-over)
+
+ // image smoothing
+ // attribute boolean imageSmoothingEnabled; // (default true)
+
+ // colours and styles (see also the CanvasDrawingStyles interface)
+ // attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black)
+ // attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black)
+ //CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
+ //CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
+ //CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
+
+ // shadows
+ // attribute unrestricted double shadowOffsetX; // (default 0)
+ // attribute unrestricted double shadowOffsetY; // (default 0)
+ // attribute unrestricted double shadowBlur; // (default 0)
+ // attribute DOMString shadowColor; // (default transparent black)
+
+ // rects
+ //void clearRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
+ //[LenientFloat]
+ void clearRect(double x, double y, double w, double h);
+ //void fillRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
+ //[LenientFloat]
+ void fillRect(double x, double y, double w, double h);
+ //void strokeRect(unrestricted double x, unrestricted double y, unrestricted double w, unrestricted double h);
+ //[LenientFloat]
+ void strokeRect(double x, double y, double w, double h);
+
+ // path API (see also CanvasPathMethods)
+ //void beginPath();
+ //void fill(optional CanvasFillRule fillRule = "nonzero");
+ //void fill(Path2D path, optional CanvasFillRule fillRule = "nonzero");
+ //void stroke();
+ //void stroke(Path2D path);
+ //void drawSystemFocusRing(Element element);
+ //void drawSystemFocusRing(Path2D path, Element element);
+ //boolean drawCustomFocusRing(Element element);
+ //boolean drawCustomFocusRing(Path2D path, Element element);
+ //void scrollPathIntoView();
+ //void scrollPathIntoView(Path2D path);
+ //void clip(optional CanvasFillRule fillRule = "nonzero");
+ //void clip(Path2D path, optional CanvasFillRule fillRule = "nonzero");
+ //void resetClip();
+ //boolean isPointInPath(unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero");
+ //boolean isPointInPath(Path2D path, unrestricted double x, unrestricted double y, optional CanvasFillRule fillRule = "nonzero");
+ //boolean isPointInStroke(unrestricted double x, unrestricted double y);
+ //boolean isPointInStroke(Path2D path, unrestricted double x, unrestricted double y);
+
+ // text (see also the CanvasDrawingStyles interface)
+ //void fillText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
+ //void strokeText(DOMString text, unrestricted double x, unrestricted double y, optional unrestricted double maxWidth);
+ //TextMetrics measureText(DOMString text);
+
+ // drawing images
+ //void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy);
+ //void drawImage(CanvasImageSource image, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
+ //void drawImage(CanvasImageSource image, unrestricted double sx, unrestricted double sy, unrestricted double sw, unrestricted double sh, unrestricted double dx, unrestricted double dy, unrestricted double dw, unrestricted double dh);
+
+ // hit regions
+ //void addHitRegion(optional HitRegionOptions options);
+ //void removeHitRegion(DOMString id);
+
+ // pixel manipulation
+ //ImageData createImageData(double sw, double sh);
+ //ImageData createImageData(ImageData imagedata);
+ //ImageData getImageData(double sx, double sy, double sw, double sh);
+ //void putImageData(ImageData imagedata, double dx, double dy);
+ //void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
+};
diff --git a/components/script/dom/webidls/CharacterData.webidl b/components/script/dom/webidls/CharacterData.webidl
new file mode 100644
index 00000000000..d1b222bc168
--- /dev/null
+++ b/components/script/dom/webidls/CharacterData.webidl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#characterdata
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+interface CharacterData : Node {
+ [TreatNullAs=EmptyString,SetterThrows] attribute DOMString data;
+ readonly attribute unsigned long length;
+ [Throws]
+ DOMString substringData(unsigned long offset, unsigned long count);
+ [Throws]
+ void appendData(DOMString data);
+ [Throws]
+ void insertData(unsigned long offset, DOMString data);
+ [Throws]
+ void deleteData(unsigned long offset, unsigned long count);
+ [Throws]
+ void replaceData(unsigned long offset, unsigned long count, DOMString data);
+};
+
+CharacterData implements ChildNode;
diff --git a/components/script/dom/webidls/ChildNode.webidl b/components/script/dom/webidls/ChildNode.webidl
new file mode 100644
index 00000000000..16562fbafbf
--- /dev/null
+++ b/components/script/dom/webidls/ChildNode.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * http://dom.spec.whatwg.org/#interface-childnode
+ */
+
+[NoInterfaceObject]
+interface ChildNode {
+// Not implemented yet:
+// void before((Node or DOMString)... nodes);
+// void after((Node or DOMString)... nodes);
+// void replace((Node or DOMString)... nodes);
+ void remove();
+};
+
+// [NoInterfaceObject]
+// interface NonDocumentTypeChildNode {
+// [Pure]
+// readonly attribute Element? previousElementSibling;
+// [Pure]
+// readonly attribute Element? nextElementSibling;
+// };
diff --git a/components/script/dom/webidls/Comment.webidl b/components/script/dom/webidls/Comment.webidl
new file mode 100644
index 00000000000..023335f166a
--- /dev/null
+++ b/components/script/dom/webidls/Comment.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#comment
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[Constructor(optional DOMString data = "")]
+interface Comment : CharacterData {
+};
diff --git a/components/script/dom/webidls/Console.webidl b/components/script/dom/webidls/Console.webidl
new file mode 100644
index 00000000000..23b294596a8
--- /dev/null
+++ b/components/script/dom/webidls/Console.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * References:
+ * MDN Docs - https://developer.mozilla.org/en-US/docs/Web/API/console
+ * Draft Spec - http://sideshowbarker.github.io/console-spec/
+ *
+ * © Copyright 2014 Mozilla Foundation.
+ */
+
+interface Console {
+ // These should be DOMString message, DOMString message2, ...
+ void log(DOMString message);
+ void debug(DOMString message);
+ void info(DOMString message);
+ void warn(DOMString message);
+ void error(DOMString message);
+ void assert(boolean condition, optional DOMString message);
+};
diff --git a/components/script/dom/webidls/CustomEvent.webidl b/components/script/dom/webidls/CustomEvent.webidl
new file mode 100644
index 00000000000..bfc99c99954
--- /dev/null
+++ b/components/script/dom/webidls/CustomEvent.webidl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * For more information on this interface please see
+ * http://dom.spec.whatwg.org/#interface-customevent
+ *
+ * To the extent possible under law, the editors have waived
+ * all copyright and related or neighboring rights to this work.
+ * In addition, as of 1 May 2014, the editors have made this specification
+ * available under the Open Web Foundation Agreement Version 1.0,
+ * which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+[Constructor(DOMString type, optional CustomEventInit eventInitDict)/*,
+ Exposed=Window,Worker*/]
+interface CustomEvent : Event {
+ readonly attribute any detail;
+
+ void initCustomEvent(DOMString type, boolean bubbles, boolean cancelable, any detail);
+};
+
+dictionary CustomEventInit : EventInit {
+ any detail = null;
+};
diff --git a/components/script/dom/webidls/DOMException.webidl b/components/script/dom/webidls/DOMException.webidl
new file mode 100644
index 00000000000..7347d2e76cc
--- /dev/null
+++ b/components/script/dom/webidls/DOMException.webidl
@@ -0,0 +1,47 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * http://dom.spec.whatwg.org/#domexception
+ */
+
+// XXXkhuey this is an 'exception', not an interface, but we don't have any
+// parser or codegen mechanisms for dealing with exceptions.
+interface DOMException {
+ const unsigned short INDEX_SIZE_ERR = 1;
+ const unsigned short DOMSTRING_SIZE_ERR = 2; // historical
+ const unsigned short HIERARCHY_REQUEST_ERR = 3;
+ const unsigned short WRONG_DOCUMENT_ERR = 4;
+ const unsigned short INVALID_CHARACTER_ERR = 5;
+ const unsigned short NO_DATA_ALLOWED_ERR = 6; // historical
+ const unsigned short NO_MODIFICATION_ALLOWED_ERR = 7;
+ const unsigned short NOT_FOUND_ERR = 8;
+ const unsigned short NOT_SUPPORTED_ERR = 9;
+ const unsigned short INUSE_ATTRIBUTE_ERR = 10; // historical
+ const unsigned short INVALID_STATE_ERR = 11;
+ const unsigned short SYNTAX_ERR = 12;
+ const unsigned short INVALID_MODIFICATION_ERR = 13;
+ const unsigned short NAMESPACE_ERR = 14;
+ const unsigned short INVALID_ACCESS_ERR = 15;
+ const unsigned short VALIDATION_ERR = 16; // historical
+ const unsigned short TYPE_MISMATCH_ERR = 17; // historical; use JavaScript's TypeError instead
+ const unsigned short SECURITY_ERR = 18;
+ const unsigned short NETWORK_ERR = 19;
+ const unsigned short ABORT_ERR = 20;
+ const unsigned short URL_MISMATCH_ERR = 21;
+ const unsigned short QUOTA_EXCEEDED_ERR = 22;
+ const unsigned short TIMEOUT_ERR = 23;
+ const unsigned short INVALID_NODE_TYPE_ERR = 24;
+ const unsigned short DATA_CLONE_ERR = 25;
+
+ // Error code as u16
+ readonly attribute unsigned short code;
+
+ // The name of the error code (ie, a string repr of |code|)
+ readonly attribute DOMString name;
+
+ // A custom message set by the thrower.
+ readonly attribute DOMString message;
+};
diff --git a/components/script/dom/webidls/DOMImplementation.webidl b/components/script/dom/webidls/DOMImplementation.webidl
new file mode 100644
index 00000000000..50f7510b800
--- /dev/null
+++ b/components/script/dom/webidls/DOMImplementation.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-domimplementation
+ *
+ * Copyright:
+ * To the extent possible under law, the editors have waived all copyright and
+ * related or neighboring rights to this work.
+ */
+
+interface DOMImplementation {
+ /*boolean hasFeature(DOMString feature,
+ [TreatNullAs=EmptyString] DOMString version);*/
+ [Throws]
+ DocumentType createDocumentType(DOMString qualifiedName, DOMString publicId,
+ DOMString systemId);
+ [Throws]
+ Document createDocument(DOMString? namespace,
+ [TreatNullAs=EmptyString] DOMString qualifiedName,
+ optional DocumentType? doctype = null);
+ Document createHTMLDocument(optional DOMString title);
+};
diff --git a/components/script/dom/webidls/DOMParser.webidl b/components/script/dom/webidls/DOMParser.webidl
new file mode 100644
index 00000000000..236fae785f2
--- /dev/null
+++ b/components/script/dom/webidls/DOMParser.webidl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://domparsing.spec.whatwg.org/#the-domparser-interface
+ */
+
+enum SupportedType {
+ "text/html",
+ "text/xml",
+ "application/xml",
+ "application/xhtml+xml",
+ "image/svg+xml"
+};
+
+[Constructor]
+interface DOMParser {
+ [Throws]
+ Document parseFromString(DOMString str, SupportedType type);
+};
diff --git a/components/script/dom/webidls/DOMRect.webidl b/components/script/dom/webidls/DOMRect.webidl
new file mode 100644
index 00000000000..6e0fe24b57d
--- /dev/null
+++ b/components/script/dom/webidls/DOMRect.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dev.w3.org/fxtf/geometry/#DOMRect
+interface DOMRect {
+ readonly attribute float top;
+ readonly attribute float right;
+ readonly attribute float bottom;
+ readonly attribute float left;
+ readonly attribute float width;
+ readonly attribute float height;
+};
diff --git a/components/script/dom/webidls/DOMRectList.webidl b/components/script/dom/webidls/DOMRectList.webidl
new file mode 100644
index 00000000000..064014e9abe
--- /dev/null
+++ b/components/script/dom/webidls/DOMRectList.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dev.w3.org/fxtf/geometry/#DOMRectList
+[NoInterfaceObject/*,
+ ArrayClass*/]
+interface DOMRectList {
+ readonly attribute unsigned long length;
+ getter DOMRect? item(unsigned long index);
+};
diff --git a/components/script/dom/webidls/DOMTokenList.webidl b/components/script/dom/webidls/DOMTokenList.webidl
new file mode 100644
index 00000000000..bc32f4bf256
--- /dev/null
+++ b/components/script/dom/webidls/DOMTokenList.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dom.spec.whatwg.org/#domtokenlist
+interface DOMTokenList {
+ readonly attribute unsigned long length;
+ getter DOMString? item(unsigned long index);
+
+ [Throws]
+ boolean contains(DOMString token);
+
+ //void add(DOMString... tokens);
+ //void remove(DOMString... tokens);
+ //boolean toggle(DOMString token, optional boolean force);
+ //stringifier;
+};
diff --git a/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl
new file mode 100644
index 00000000000..dbf2891f72a
--- /dev/null
+++ b/components/script/dom/webidls/DedicatedWorkerGlobalScope.webidl
@@ -0,0 +1,10 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#dedicatedworkerglobalscope
+[Global/*=Worker,DedicatedWorker*/]
+/*sealed*/ interface DedicatedWorkerGlobalScope : WorkerGlobalScope {
+ void postMessage(any message/*, optional sequence<Transferable> transfer*/);
+ attribute EventHandler onmessage;
+};
diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl
new file mode 100644
index 00000000000..0599ba71f95
--- /dev/null
+++ b/components/script/dom/webidls/Document.webidl
@@ -0,0 +1,71 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * http://dom.spec.whatwg.org/#interface-document
+ * http://www.whatwg.org/specs/web-apps/current-work/#the-document-object
+ */
+
+/* http://dom.spec.whatwg.org/#interface-document */
+[Constructor]
+interface Document : Node {
+ readonly attribute DOMImplementation implementation;
+ readonly attribute DOMString URL;
+ readonly attribute DOMString documentURI;
+ readonly attribute DOMString compatMode;
+ readonly attribute DOMString characterSet;
+ readonly attribute DOMString contentType;
+ readonly attribute Location location;
+
+ readonly attribute DocumentType? doctype;
+ readonly attribute Element? documentElement;
+ HTMLCollection getElementsByTagName(DOMString localName);
+ HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
+ HTMLCollection getElementsByClassName(DOMString classNames);
+ Element? getElementById(DOMString elementId);
+
+ [Throws]
+ Element createElement(DOMString localName);
+ [Throws]
+ Element createElementNS(DOMString? namespace, DOMString qualifiedName);
+ DocumentFragment createDocumentFragment();
+ Text createTextNode(DOMString data);
+ Comment createComment(DOMString data);
+ [Throws]
+ ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data);
+
+ [Throws]
+ Node importNode(Node node, optional boolean deep = false);
+ [Throws]
+ Node adoptNode(Node node);
+
+ [Throws]
+ Event createEvent(DOMString interface_);
+
+ Range createRange();
+};
+
+/* http://www.whatwg.org/specs/web-apps/current-work/#the-document-object */
+partial interface Document {
+ readonly attribute DOMString lastModified;
+ [SetterThrows]
+ attribute DOMString title;
+ [SetterThrows]
+ attribute HTMLElement? body;
+ readonly attribute HTMLHeadElement? head;
+ NodeList getElementsByName(DOMString elementName);
+
+ readonly attribute HTMLCollection images;
+ readonly attribute HTMLCollection embeds;
+ readonly attribute HTMLCollection plugins;
+ readonly attribute HTMLCollection links;
+ readonly attribute HTMLCollection forms;
+ readonly attribute HTMLCollection scripts;
+ readonly attribute HTMLCollection anchors;
+ readonly attribute HTMLCollection applets;
+};
+
+Document implements ParentNode;
+Document implements GlobalEventHandlers;
diff --git a/components/script/dom/webidls/DocumentFragment.webidl b/components/script/dom/webidls/DocumentFragment.webidl
new file mode 100644
index 00000000000..4248975f768
--- /dev/null
+++ b/components/script/dom/webidls/DocumentFragment.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dom.spec.whatwg.org/#interface-documentfragment
+[Constructor]
+interface DocumentFragment : Node {
+};
+
+DocumentFragment implements ParentNode;
diff --git a/components/script/dom/webidls/DocumentType.webidl b/components/script/dom/webidls/DocumentType.webidl
new file mode 100644
index 00000000000..89190266fde
--- /dev/null
+++ b/components/script/dom/webidls/DocumentType.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#documenttype
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+interface DocumentType : Node {
+ readonly attribute DOMString name;
+ readonly attribute DOMString publicId;
+ readonly attribute DOMString systemId;
+};
+
+DocumentType implements ChildNode;
diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl
new file mode 100644
index 00000000000..fc737bdb9ec
--- /dev/null
+++ b/components/script/dom/webidls/Element.webidl
@@ -0,0 +1,70 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#element and
+ * http://domparsing.spec.whatwg.org/ and
+ * http://dev.w3.org/csswg/cssom-view/ and
+ * http://www.w3.org/TR/selectors-api/
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+interface Element : Node {
+
+ readonly attribute DOMString? prefix;
+ readonly attribute DOMString localName;
+
+ [Constant]
+ readonly attribute DOMString? namespaceURI;
+ // Not [Constant] because it depends on which document we're in
+ [Pure]
+ readonly attribute DOMString tagName;
+
+ [Pure]
+ attribute DOMString id;
+ [Pure]
+ attribute DOMString className;
+ [Constant]
+ readonly attribute DOMTokenList classList;
+
+ [Constant]
+ readonly attribute NamedNodeMap attributes;
+ DOMString? getAttribute(DOMString name);
+ DOMString? getAttributeNS(DOMString? namespace, DOMString localName);
+ [Throws]
+ void setAttribute(DOMString name, DOMString value);
+ [Throws]
+ void setAttributeNS(DOMString? namespace, DOMString name, DOMString value);
+ void removeAttribute(DOMString name);
+ void removeAttributeNS(DOMString? namespace, DOMString localName);
+ boolean hasAttribute(DOMString name);
+ boolean hasAttributeNS(DOMString? namespace, DOMString localName);
+
+ [Throws]
+ boolean matches(DOMString selectors);
+
+ HTMLCollection getElementsByTagName(DOMString localName);
+ HTMLCollection getElementsByTagNameNS(DOMString? namespace, DOMString localName);
+ HTMLCollection getElementsByClassName(DOMString classNames);
+};
+
+// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface
+partial interface Element {
+ DOMRectList getClientRects();
+ DOMRect getBoundingClientRect();
+};
+
+// http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
+partial interface Element {
+ [Throws,TreatNullAs=EmptyString]
+ readonly attribute DOMString innerHTML;
+ [Throws,TreatNullAs=EmptyString]
+ readonly attribute DOMString outerHTML;
+};
+
+Element implements ChildNode;
+Element implements ParentNode;
diff --git a/components/script/dom/webidls/Event.webidl b/components/script/dom/webidls/Event.webidl
new file mode 100644
index 00000000000..6e574427548
--- /dev/null
+++ b/components/script/dom/webidls/Event.webidl
@@ -0,0 +1,43 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * For more information on this interface please see
+ * http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[Constructor(DOMString type, optional EventInit eventInitDict)]
+interface Event {
+ readonly attribute DOMString type;
+ readonly attribute EventTarget? target;
+ readonly attribute EventTarget? currentTarget;
+
+ const unsigned short NONE = 0;
+ const unsigned short CAPTURING_PHASE = 1;
+ const unsigned short AT_TARGET = 2;
+ const unsigned short BUBBLING_PHASE = 3;
+ readonly attribute unsigned short eventPhase;
+
+ void stopPropagation();
+ void stopImmediatePropagation();
+
+ readonly attribute boolean bubbles;
+ readonly attribute boolean cancelable;
+ void preventDefault();
+ readonly attribute boolean defaultPrevented;
+
+ readonly attribute boolean isTrusted;
+ readonly attribute DOMTimeStamp timeStamp;
+
+ void initEvent(DOMString type, boolean bubbles, boolean cancelable);
+};
+
+dictionary EventInit {
+ boolean bubbles = false;
+ boolean cancelable = false;
+};
+
diff --git a/components/script/dom/webidls/EventHandler.webidl b/components/script/dom/webidls/EventHandler.webidl
new file mode 100644
index 00000000000..1278d7467fd
--- /dev/null
+++ b/components/script/dom/webidls/EventHandler.webidl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.whatwg.org/specs/web-apps/current-work/#eventhandler
+ *
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+[TreatNonObjectAsNull]
+callback EventHandlerNonNull = any (Event event);
+typedef EventHandlerNonNull? EventHandler;
+
+[TreatNonObjectAsNull]
+callback OnErrorEventHandlerNonNull = boolean ((Event or DOMString) event, optional DOMString source, optional unsigned long lineno, optional unsigned long column, optional any error);
+typedef OnErrorEventHandlerNonNull? OnErrorEventHandler;
+
+[NoInterfaceObject]
+interface GlobalEventHandlers {
+ attribute EventHandler onclick;
+ attribute EventHandler onload;
+};
+
+[NoInterfaceObject]
+interface WindowEventHandlers {
+ attribute EventHandler onunload;
+};
+
+// The spec has |attribute OnErrorEventHandler onerror;| on
+// GlobalEventHandlers, and calls the handler differently depending on
+// whether an ErrorEvent was fired. We don't do that, and until we do we'll
+// need to distinguish between onerror on Window or on nodes.
+
+/*[NoInterfaceObject]
+interface OnErrorEventHandlerForNodes {
+ attribute EventHandler onerror;
+};*/
+
+[NoInterfaceObject]
+interface OnErrorEventHandlerForWindow {
+ attribute OnErrorEventHandler onerror;
+};
diff --git a/components/script/dom/webidls/EventListener.webidl b/components/script/dom/webidls/EventListener.webidl
new file mode 100644
index 00000000000..05e1684d31e
--- /dev/null
+++ b/components/script/dom/webidls/EventListener.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.w3.org/TR/2012/WD-dom-20120105/
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+callback interface EventListener {
+ void handleEvent(Event event);
+};
+
diff --git a/components/script/dom/webidls/EventTarget.webidl b/components/script/dom/webidls/EventTarget.webidl
new file mode 100644
index 00000000000..897756fa273
--- /dev/null
+++ b/components/script/dom/webidls/EventTarget.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.w3.org/TR/2012/WD-dom-20120105/
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+interface EventTarget {
+ void addEventListener(DOMString type,
+ EventListener? listener,
+ optional boolean capture = false);
+ void removeEventListener(DOMString type,
+ EventListener? listener,
+ optional boolean capture = false);
+ [Throws]
+ boolean dispatchEvent(Event event);
+};
diff --git a/components/script/dom/webidls/File.webidl b/components/script/dom/webidls/File.webidl
new file mode 100644
index 00000000000..0d5967b5e55
--- /dev/null
+++ b/components/script/dom/webidls/File.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dev.w3.org/2006/webapi/FileAPI/#dfn-file
+
+// [Constructor(sequence<(Blob or DOMString or ArrayBufferView or ArrayBuffer)> fileBits,
+// [EnsureUTF16] DOMString fileName, optional FilePropertyBag options)]
+interface File : Blob {
+
+ readonly attribute DOMString name;
+ // readonly attribute Date lastModifiedDate;
+
+}; \ No newline at end of file
diff --git a/components/script/dom/webidls/FormData.webidl b/components/script/dom/webidls/FormData.webidl
new file mode 100644
index 00000000000..cfa3e89b3cd
--- /dev/null
+++ b/components/script/dom/webidls/FormData.webidl
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://xhr.spec.whatwg.org
+ */
+
+typedef (File or DOMString) FormDataEntryValue;
+
+[Constructor(optional HTMLFormElement form)]
+interface FormData {
+ void append(DOMString name, Blob value, optional DOMString filename);
+ void append(DOMString name, DOMString value);
+ void delete(DOMString name);
+ FormDataEntryValue? get(DOMString name);
+ // sequence<FormDataEntryValue> getAll(DOMString name);
+ boolean has(DOMString name);
+ void set(DOMString name, Blob value, optional DOMString filename);
+ void set(DOMString name, DOMString value);
+};
diff --git a/components/script/dom/webidls/HTMLAnchorElement.webidl b/components/script/dom/webidls/HTMLAnchorElement.webidl
new file mode 100644
index 00000000000..de80a803514
--- /dev/null
+++ b/components/script/dom/webidls/HTMLAnchorElement.webidl
@@ -0,0 +1,38 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.whatwg.org/specs/web-apps/current-work/#the-a-element
+ * http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+// http://www.whatwg.org/html/#htmlanchorelement
+interface HTMLAnchorElement : HTMLElement {
+ // attribute DOMString target;
+ // attribute DOMString download;
+ //[PutForwards=value] attribute DOMSettableTokenList ping;
+ // attribute DOMString rel;
+ //readonly attribute DOMTokenList relList;
+ // attribute DOMString hreflang;
+ // attribute DOMString type;
+
+ [Pure]
+ attribute DOMString text;
+
+ // also has obsolete members
+};
+//HTMLAnchorElement implements URLUtils;
+
+// http://www.whatwg.org/html/#HTMLAnchorElement-partial
+partial interface HTMLAnchorElement {
+ // attribute DOMString coords;
+ // attribute DOMString charset;
+ // attribute DOMString name;
+ // attribute DOMString rev;
+ // attribute DOMString shape;
+};
diff --git a/components/script/dom/webidls/HTMLAppletElement.webidl b/components/script/dom/webidls/HTMLAppletElement.webidl
new file mode 100644
index 00000000000..2612ffee2a1
--- /dev/null
+++ b/components/script/dom/webidls/HTMLAppletElement.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlappletelement
+interface HTMLAppletElement : HTMLElement {
+ // attribute DOMString align;
+ // attribute DOMString alt;
+ // attribute DOMString archive;
+ // attribute DOMString code;
+ // attribute DOMString codeBase;
+ // attribute DOMString height;
+ // attribute unsigned long hspace;
+ // attribute DOMString name;
+ // attribute DOMString _object; // the underscore is not part of the identifier
+ // attribute unsigned long vspace;
+ // attribute DOMString width;
+};
diff --git a/components/script/dom/webidls/HTMLAreaElement.webidl b/components/script/dom/webidls/HTMLAreaElement.webidl
new file mode 100644
index 00000000000..0cbbe2d7ce1
--- /dev/null
+++ b/components/script/dom/webidls/HTMLAreaElement.webidl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlareaelement
+interface HTMLAreaElement : HTMLElement {
+ // attribute DOMString alt;
+ // attribute DOMString coords;
+ // attribute DOMString shape;
+ // attribute DOMString target;
+ // attribute DOMString download;
+ //[PutForwards=value] attribute DOMSettableTokenList ping;
+ // attribute DOMString rel;
+ //readonly attribute DOMTokenList relList;
+ // attribute DOMString hreflang;
+ // attribute DOMString type;
+
+ // also has obsolete members
+};
+//HTMLAreaElement implements URLUtils;
+
+// http://www.whatwg.org/html/#HTMLAreaElement-partial
+partial interface HTMLAreaElement {
+ // attribute boolean noHref;
+};
diff --git a/components/script/dom/webidls/HTMLAudioElement.webidl b/components/script/dom/webidls/HTMLAudioElement.webidl
new file mode 100644
index 00000000000..9832eeda044
--- /dev/null
+++ b/components/script/dom/webidls/HTMLAudioElement.webidl
@@ -0,0 +1,8 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlaudioelement
+//[NamedConstructor=Audio(optional DOMString src)]
+interface HTMLAudioElement : HTMLMediaElement {};
diff --git a/components/script/dom/webidls/HTMLBRElement.webidl b/components/script/dom/webidls/HTMLBRElement.webidl
new file mode 100644
index 00000000000..972b9377a0e
--- /dev/null
+++ b/components/script/dom/webidls/HTMLBRElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlbrelement
+interface HTMLBRElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLBRElement-partial
+partial interface HTMLBRElement {
+ // attribute DOMString clear;
+};
diff --git a/components/script/dom/webidls/HTMLBaseElement.webidl b/components/script/dom/webidls/HTMLBaseElement.webidl
new file mode 100644
index 00000000000..c39951b6783
--- /dev/null
+++ b/components/script/dom/webidls/HTMLBaseElement.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlbaseelement
+interface HTMLBaseElement : HTMLElement {
+ // attribute DOMString href;
+ // attribute DOMString target;
+};
diff --git a/components/script/dom/webidls/HTMLBodyElement.webidl b/components/script/dom/webidls/HTMLBodyElement.webidl
new file mode 100644
index 00000000000..6d6967d2709
--- /dev/null
+++ b/components/script/dom/webidls/HTMLBodyElement.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlbodyelement
+interface HTMLBodyElement : HTMLElement {
+
+ // also has obsolete members
+};
+HTMLBodyElement implements WindowEventHandlers;
+
+// http://www.whatwg.org/html/#HTMLBodyElement-partial
+partial interface HTMLBodyElement {
+ //[TreatNullAs=EmptyString] attribute DOMString text;
+ //[TreatNullAs=EmptyString] attribute DOMString link;
+ //[TreatNullAs=EmptyString] attribute DOMString vLink;
+ //[TreatNullAs=EmptyString] attribute DOMString aLink;
+ //[TreatNullAs=EmptyString] attribute DOMString bgColor;
+ // attribute DOMString background;
+};
diff --git a/components/script/dom/webidls/HTMLButtonElement.webidl b/components/script/dom/webidls/HTMLButtonElement.webidl
new file mode 100644
index 00000000000..ad21e11370f
--- /dev/null
+++ b/components/script/dom/webidls/HTMLButtonElement.webidl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlbuttonelement
+interface HTMLButtonElement : HTMLElement {
+ // attribute boolean autofocus;
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString formAction;
+ // attribute DOMString formEnctype;
+ // attribute DOMString formMethod;
+ // attribute boolean formNoValidate;
+ // attribute DOMString formTarget;
+ // attribute DOMString name;
+ // attribute DOMString type;
+ // attribute DOMString value;
+ // attribute HTMLMenuElement? menu;
+
+ //readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //readonly attribute NodeList labels;
+};
diff --git a/components/script/dom/webidls/HTMLCanvasElement.webidl b/components/script/dom/webidls/HTMLCanvasElement.webidl
new file mode 100644
index 00000000000..baff0dc745c
--- /dev/null
+++ b/components/script/dom/webidls/HTMLCanvasElement.webidl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlcanvaselement
+//typedef (CanvasRenderingContext2D or WebGLRenderingContext) RenderingContext;
+
+interface HTMLCanvasElement : HTMLElement {
+ [Pure]
+ attribute unsigned long width;
+ [Pure]
+ attribute unsigned long height;
+
+ //RenderingContext? getContext(DOMString contextId, any... arguments);
+ CanvasRenderingContext2D? getContext(DOMString contextId);
+ //boolean probablySupportsContext(DOMString contextId, any... arguments);
+
+ //void setContext(RenderingContext context);
+ //CanvasProxy transferControlToProxy();
+
+ //DOMString toDataURL(optional DOMString type, any... arguments);
+ //void toBlob(FileCallback? _callback, optional DOMString type, any... arguments);
+};
diff --git a/components/script/dom/webidls/HTMLCollection.webidl b/components/script/dom/webidls/HTMLCollection.webidl
new file mode 100644
index 00000000000..26227c54c4a
--- /dev/null
+++ b/components/script/dom/webidls/HTMLCollection.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface HTMLCollection {
+ readonly attribute unsigned long length;
+ getter Element? item(unsigned long index);
+ getter Element? namedItem(DOMString name);
+};
diff --git a/components/script/dom/webidls/HTMLDListElement.webidl b/components/script/dom/webidls/HTMLDListElement.webidl
new file mode 100644
index 00000000000..3c0a8b9c63d
--- /dev/null
+++ b/components/script/dom/webidls/HTMLDListElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmldlistelement
+interface HTMLDListElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLDListElement-partial
+partial interface HTMLDListElement {
+ // attribute boolean compact;
+};
diff --git a/components/script/dom/webidls/HTMLDataElement.webidl b/components/script/dom/webidls/HTMLDataElement.webidl
new file mode 100644
index 00000000000..c6025423fb0
--- /dev/null
+++ b/components/script/dom/webidls/HTMLDataElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmldataelement
+interface HTMLDataElement : HTMLElement {
+ // attribute DOMString value;
+};
diff --git a/components/script/dom/webidls/HTMLDataListElement.webidl b/components/script/dom/webidls/HTMLDataListElement.webidl
new file mode 100644
index 00000000000..ae7055bb48c
--- /dev/null
+++ b/components/script/dom/webidls/HTMLDataListElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmldatalistelement
+interface HTMLDataListElement : HTMLElement {
+ readonly attribute HTMLCollection options;
+};
diff --git a/components/script/dom/webidls/HTMLDirectoryElement.webidl b/components/script/dom/webidls/HTMLDirectoryElement.webidl
new file mode 100644
index 00000000000..6015b4e8859
--- /dev/null
+++ b/components/script/dom/webidls/HTMLDirectoryElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmldirectoryelement
+interface HTMLDirectoryElement : HTMLElement {
+ // attribute boolean compact;
+};
diff --git a/components/script/dom/webidls/HTMLDivElement.webidl b/components/script/dom/webidls/HTMLDivElement.webidl
new file mode 100644
index 00000000000..be451ce3e23
--- /dev/null
+++ b/components/script/dom/webidls/HTMLDivElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmldivelement
+interface HTMLDivElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLDivElement-partial
+partial interface HTMLDivElement {
+ // attribute DOMString align;
+};
diff --git a/components/script/dom/webidls/HTMLElement.webidl b/components/script/dom/webidls/HTMLElement.webidl
new file mode 100644
index 00000000000..ebaa83a19bf
--- /dev/null
+++ b/components/script/dom/webidls/HTMLElement.webidl
@@ -0,0 +1,48 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlelement
+interface HTMLElement : Element {
+ // metadata attributes
+ // attribute DOMString title;
+ // attribute DOMString lang;
+ // attribute boolean translate;
+ // attribute DOMString dir;
+ //readonly attribute DOMStringMap dataset;
+
+ // microdata
+ // attribute boolean itemScope;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList itemType;
+ // attribute DOMString itemId;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList itemRef;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList itemProp;
+ //readonly attribute HTMLPropertiesCollection properties;
+ // attribute any itemValue; // acts as DOMString on setting
+
+ // user interaction
+ // attribute boolean hidden;
+ //void click();
+ // attribute long tabIndex;
+ //void focus();
+ //void blur();
+ // attribute DOMString accessKey;
+ //readonly attribute DOMString accessKeyLabel;
+ // attribute boolean draggable;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList dropzone;
+ // attribute DOMString contentEditable;
+ //readonly attribute boolean isContentEditable;
+ // attribute HTMLMenuElement? contextMenu;
+ // attribute boolean spellcheck;
+ //void forceSpellCheck();
+
+ // command API
+ //readonly attribute DOMString? commandType;
+ //readonly attribute DOMString? commandLabel;
+ //readonly attribute DOMString? commandIcon;
+ //readonly attribute boolean? commandHidden;
+ //readonly attribute boolean? commandDisabled;
+ //readonly attribute boolean? commandChecked;
+};
+HTMLElement implements GlobalEventHandlers;
diff --git a/components/script/dom/webidls/HTMLEmbedElement.webidl b/components/script/dom/webidls/HTMLEmbedElement.webidl
new file mode 100644
index 00000000000..0b708113b55
--- /dev/null
+++ b/components/script/dom/webidls/HTMLEmbedElement.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlembedelement
+interface HTMLEmbedElement : HTMLElement {
+ // attribute DOMString src;
+ // attribute DOMString type;
+ // attribute DOMString width;
+ // attribute DOMString height;
+ //legacycaller any (any... arguments);
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLEmbedElement-partial
+partial interface HTMLEmbedElement {
+ // attribute DOMString align;
+ // attribute DOMString name;
+};
diff --git a/components/script/dom/webidls/HTMLFieldSetElement.webidl b/components/script/dom/webidls/HTMLFieldSetElement.webidl
new file mode 100644
index 00000000000..6b64c60bd21
--- /dev/null
+++ b/components/script/dom/webidls/HTMLFieldSetElement.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlfieldsetelement
+interface HTMLFieldSetElement : HTMLElement {
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString name;
+
+ //readonly attribute DOMString type;
+
+ //readonly attribute HTMLFormControlsCollection elements;
+ readonly attribute HTMLCollection elements;
+
+ //readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+};
diff --git a/components/script/dom/webidls/HTMLFontElement.webidl b/components/script/dom/webidls/HTMLFontElement.webidl
new file mode 100644
index 00000000000..4bdcb766745
--- /dev/null
+++ b/components/script/dom/webidls/HTMLFontElement.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlfontelement
+interface HTMLFontElement : HTMLElement {
+ //[TreatNullAs=EmptyString] attribute DOMString color;
+ // attribute DOMString face;
+ // attribute DOMString size;
+};
diff --git a/components/script/dom/webidls/HTMLFormElement.webidl b/components/script/dom/webidls/HTMLFormElement.webidl
new file mode 100644
index 00000000000..ffa36bc4d6b
--- /dev/null
+++ b/components/script/dom/webidls/HTMLFormElement.webidl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlformelement
+//[OverrideBuiltins]
+interface HTMLFormElement : HTMLElement {
+ // attribute DOMString acceptCharset;
+ // attribute DOMString action;
+ // attribute DOMString autocomplete;
+ // attribute DOMString enctype;
+ // attribute DOMString encoding;
+ // attribute DOMString method;
+ // attribute DOMString name;
+ // attribute boolean noValidate;
+ // attribute DOMString target;
+
+ //readonly attribute HTMLFormControlsCollection elements;
+ //readonly attribute long length;
+ //getter Element (unsigned long index);
+ //getter (RadioNodeList or Element) (DOMString name);
+
+ //void submit();
+ //void reset();
+ //boolean checkValidity();
+ //boolean reportValidity();
+
+ //void requestAutocomplete();
+};
diff --git a/components/script/dom/webidls/HTMLFrameElement.webidl b/components/script/dom/webidls/HTMLFrameElement.webidl
new file mode 100644
index 00000000000..effa8d13e99
--- /dev/null
+++ b/components/script/dom/webidls/HTMLFrameElement.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlframeelement
+interface HTMLFrameElement : HTMLElement {
+ // attribute DOMString name;
+ // attribute DOMString scrolling;
+ // attribute DOMString src;
+ // attribute DOMString frameBorder;
+ // attribute DOMString longDesc;
+ // attribute boolean noResize;
+ //readonly attribute Document? contentDocument;
+ //readonly attribute WindowProxy? contentWindow;
+
+ //[TreatNullAs=EmptyString] attribute DOMString marginHeight;
+ //[TreatNullAs=EmptyString] attribute DOMString marginWidth;
+};
diff --git a/components/script/dom/webidls/HTMLFrameSetElement.webidl b/components/script/dom/webidls/HTMLFrameSetElement.webidl
new file mode 100644
index 00000000000..50245baa049
--- /dev/null
+++ b/components/script/dom/webidls/HTMLFrameSetElement.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlframesetelement
+interface HTMLFrameSetElement : HTMLElement {
+ // attribute DOMString cols;
+ // attribute DOMString rows;
+};
+//HTMLFrameSetElement implements WindowEventHandlers;
diff --git a/components/script/dom/webidls/HTMLHRElement.webidl b/components/script/dom/webidls/HTMLHRElement.webidl
new file mode 100644
index 00000000000..482e1bca516
--- /dev/null
+++ b/components/script/dom/webidls/HTMLHRElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlhrelement
+interface HTMLHRElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLHRElement-partial
+partial interface HTMLHRElement {
+ // attribute DOMString align;
+ // attribute DOMString color;
+ // attribute boolean noShade;
+ // attribute DOMString size;
+ // attribute DOMString width;
+};
diff --git a/components/script/dom/webidls/HTMLHeadElement.webidl b/components/script/dom/webidls/HTMLHeadElement.webidl
new file mode 100644
index 00000000000..b7a53d2052b
--- /dev/null
+++ b/components/script/dom/webidls/HTMLHeadElement.webidl
@@ -0,0 +1,7 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlheadelement
+interface HTMLHeadElement : HTMLElement {};
diff --git a/components/script/dom/webidls/HTMLHeadingElement.webidl b/components/script/dom/webidls/HTMLHeadingElement.webidl
new file mode 100644
index 00000000000..21a6060c335
--- /dev/null
+++ b/components/script/dom/webidls/HTMLHeadingElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlheadingelement
+interface HTMLHeadingElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLHeadingElement-partial
+partial interface HTMLHeadingElement {
+ // attribute DOMString align;
+};
diff --git a/components/script/dom/webidls/HTMLHtmlElement.webidl b/components/script/dom/webidls/HTMLHtmlElement.webidl
new file mode 100644
index 00000000000..f48fc6dafac
--- /dev/null
+++ b/components/script/dom/webidls/HTMLHtmlElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlhtmlelement
+interface HTMLHtmlElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLHtmlElement-partial
+partial interface HTMLHtmlElement {
+ // attribute DOMString version;
+};
diff --git a/components/script/dom/webidls/HTMLIFrameElement.webidl b/components/script/dom/webidls/HTMLIFrameElement.webidl
new file mode 100644
index 00000000000..201f8700ce4
--- /dev/null
+++ b/components/script/dom/webidls/HTMLIFrameElement.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmliframeelement
+interface HTMLIFrameElement : HTMLElement {
+ attribute DOMString src;
+ // attribute DOMString srcdoc;
+ // attribute DOMString name;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList sandbox;
+ attribute DOMString sandbox;
+ // attribute boolean seamless;
+ // attribute boolean allowFullscreen;
+ // attribute DOMString width;
+ // attribute DOMString height;
+ //readonly attribute Document? contentDocument;
+ //readonly attribute WindowProxy? contentWindow;
+ readonly attribute Window? contentWindow;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLIFrameElement-partial
+partial interface HTMLIFrameElement {
+ // attribute DOMString align;
+ // attribute DOMString scrolling;
+ // attribute DOMString frameBorder;
+ // attribute DOMString longDesc;
+
+ //[TreatNullAs=EmptyString] attribute DOMString marginHeight;
+ //[TreatNullAs=EmptyString] attribute DOMString marginWidth;
+};
diff --git a/components/script/dom/webidls/HTMLImageElement.webidl b/components/script/dom/webidls/HTMLImageElement.webidl
new file mode 100644
index 00000000000..08e71ff8ee7
--- /dev/null
+++ b/components/script/dom/webidls/HTMLImageElement.webidl
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlimageelement
+//[NamedConstructor=Image(optional unsigned long width, optional unsigned long height)]
+interface HTMLImageElement : HTMLElement {
+ attribute DOMString alt;
+ attribute DOMString src;
+ // attribute DOMString srcset;
+ // attribute DOMString crossOrigin;
+ attribute DOMString useMap;
+ attribute boolean isMap;
+ attribute unsigned long width;
+ attribute unsigned long height;
+ //readonly attribute unsigned long naturalWidth;
+ //readonly attribute unsigned long naturalHeight;
+ //readonly attribute boolean complete;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLImageElement-partial
+partial interface HTMLImageElement {
+ attribute DOMString name;
+ // attribute DOMString lowsrc;
+ attribute DOMString align;
+ attribute unsigned long hspace;
+ attribute unsigned long vspace;
+ attribute DOMString longDesc;
+
+ [TreatNullAs=EmptyString] attribute DOMString border;
+};
diff --git a/components/script/dom/webidls/HTMLInputElement.webidl b/components/script/dom/webidls/HTMLInputElement.webidl
new file mode 100644
index 00000000000..1caa9137e0b
--- /dev/null
+++ b/components/script/dom/webidls/HTMLInputElement.webidl
@@ -0,0 +1,76 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlinputelement
+interface HTMLInputElement : HTMLElement {
+ // attribute DOMString accept;
+ // attribute DOMString alt;
+ // attribute DOMString autocomplete;
+ // attribute boolean autofocus;
+ // attribute boolean defaultChecked;
+ // attribute boolean checked;
+ // attribute DOMString dirName;
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ //readonly attribute FileList? files;
+ // attribute DOMString formAction;
+ // attribute DOMString formEnctype;
+ // attribute DOMString formMethod;
+ // attribute boolean formNoValidate;
+ // attribute DOMString formTarget;
+ // attribute unsigned long height;
+ // attribute boolean indeterminate;
+ // attribute DOMString inputMode;
+ //readonly attribute HTMLElement? list;
+ // attribute DOMString max;
+ // attribute long maxLength;
+ // attribute DOMString min;
+ // attribute long minLength;
+ // attribute boolean multiple;
+ // attribute DOMString name;
+ // attribute DOMString pattern;
+ // attribute DOMString placeholder;
+ // attribute boolean readOnly;
+ // attribute boolean required;
+ // attribute unsigned long size;
+ // attribute DOMString src;
+ // attribute DOMString step;
+ // attribute DOMString type;
+ // attribute DOMString defaultValue;
+ //[TreatNullAs=EmptyString] attribute DOMString value;
+ // attribute Date? valueAsDate;
+ // attribute unrestricted double valueAsNumber;
+ // attribute double valueLow;
+ // attribute double valueHigh;
+ // attribute unsigned long width;
+
+ //void stepUp(optional long n = 1);
+ //void stepDown(optional long n = 1);
+
+ //readonly attribute boolean willValidate;
+ //readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //readonly attribute NodeList labels;
+
+ //void select();
+ // attribute unsigned long selectionStart;
+ // attribute unsigned long selectionEnd;
+ // attribute DOMString selectionDirection;
+ //void setRangeText(DOMString replacement);
+ //void setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve");
+ //void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLInputElement-partial
+partial interface HTMLInputElement {
+ // attribute DOMString align;
+ // attribute DOMString useMap;
+};
diff --git a/components/script/dom/webidls/HTMLLIElement.webidl b/components/script/dom/webidls/HTMLLIElement.webidl
new file mode 100644
index 00000000000..87d8b78b175
--- /dev/null
+++ b/components/script/dom/webidls/HTMLLIElement.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmllielement
+interface HTMLLIElement : HTMLElement {
+ // attribute long value;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLLIElement-partial
+partial interface HTMLLIElement {
+ // attribute DOMString type;
+};
diff --git a/components/script/dom/webidls/HTMLLabelElement.webidl b/components/script/dom/webidls/HTMLLabelElement.webidl
new file mode 100644
index 00000000000..c3ff7fb50cd
--- /dev/null
+++ b/components/script/dom/webidls/HTMLLabelElement.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmllabelelement
+interface HTMLLabelElement : HTMLElement {
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString htmlFor;
+ //readonly attribute HTMLElement? control;
+};
diff --git a/components/script/dom/webidls/HTMLLegendElement.webidl b/components/script/dom/webidls/HTMLLegendElement.webidl
new file mode 100644
index 00000000000..3622cd27672
--- /dev/null
+++ b/components/script/dom/webidls/HTMLLegendElement.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmllegendelement
+interface HTMLLegendElement : HTMLElement {
+ //readonly attribute HTMLFormElement? form;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLLegendElement-partial
+partial interface HTMLLegendElement {
+ // attribute DOMString align;
+};
diff --git a/components/script/dom/webidls/HTMLLinkElement.webidl b/components/script/dom/webidls/HTMLLinkElement.webidl
new file mode 100644
index 00000000000..3757bada2b3
--- /dev/null
+++ b/components/script/dom/webidls/HTMLLinkElement.webidl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmllinkelement
+interface HTMLLinkElement : HTMLElement {
+ // attribute DOMString href;
+ // attribute DOMString crossOrigin;
+ // attribute DOMString rel;
+ //readonly attribute DOMTokenList relList;
+ // attribute DOMString media;
+ // attribute DOMString hreflang;
+ // attribute DOMString type;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList sizes;
+
+ // also has obsolete members
+};
+//HTMLLinkElement implements LinkStyle;
+
+// http://www.whatwg.org/html/#HTMLLinkElement-partial
+partial interface HTMLLinkElement {
+ // attribute DOMString charset;
+ // attribute DOMString rev;
+ // attribute DOMString target;
+};
diff --git a/components/script/dom/webidls/HTMLMapElement.webidl b/components/script/dom/webidls/HTMLMapElement.webidl
new file mode 100644
index 00000000000..c5eb1cea3e4
--- /dev/null
+++ b/components/script/dom/webidls/HTMLMapElement.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlmapelement
+interface HTMLMapElement : HTMLElement {
+ // attribute DOMString name;
+ //readonly attribute HTMLCollection areas;
+ //readonly attribute HTMLCollection images;
+};
diff --git a/components/script/dom/webidls/HTMLMediaElement.webidl b/components/script/dom/webidls/HTMLMediaElement.webidl
new file mode 100644
index 00000000000..53f5770f54f
--- /dev/null
+++ b/components/script/dom/webidls/HTMLMediaElement.webidl
@@ -0,0 +1,67 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlmediaelement
+//enum CanPlayTypeResult { "" /* empty string */, "maybe", "probably" };
+interface HTMLMediaElement : HTMLElement {
+
+ // error state
+ //readonly attribute MediaError? error;
+
+ // network state
+ // attribute DOMString src;
+ //readonly attribute DOMString currentSrc;
+ // attribute DOMString crossOrigin;
+ //const unsigned short NETWORK_EMPTY = 0;
+ //const unsigned short NETWORK_IDLE = 1;
+ //const unsigned short NETWORK_LOADING = 2;
+ //const unsigned short NETWORK_NO_SOURCE = 3;
+ //readonly attribute unsigned short networkState;
+ // attribute DOMString preload;
+ //readonly attribute TimeRanges buffered;
+ //void load();
+ //CanPlayTypeResult canPlayType(DOMString type);
+
+ // ready state
+ //const unsigned short HAVE_NOTHING = 0;
+ //const unsigned short HAVE_METADATA = 1;
+ //const unsigned short HAVE_CURRENT_DATA = 2;
+ //const unsigned short HAVE_FUTURE_DATA = 3;
+ //const unsigned short HAVE_ENOUGH_DATA = 4;
+ //readonly attribute unsigned short readyState;
+ //readonly attribute boolean seeking;
+
+ // playback state
+ // attribute double currentTime;
+ //void fastSeek(double time);
+ //readonly attribute unrestricted double duration;
+ //Date getStartDate();
+ //readonly attribute boolean paused;
+ // attribute double defaultPlaybackRate;
+ // attribute double playbackRate;
+ //readonly attribute TimeRanges played;
+ //readonly attribute TimeRanges seekable;
+ //readonly attribute boolean ended;
+ // attribute boolean autoplay;
+ // attribute boolean loop;
+ //void play();
+ //void pause();
+
+ // media controller
+ // attribute DOMString mediaGroup;
+ // attribute MediaController? controller;
+
+ // controls
+ // attribute boolean controls;
+ // attribute double volume;
+ // attribute boolean muted;
+ // attribute boolean defaultMuted;
+
+ // tracks
+ //readonly attribute AudioTrackList audioTracks;
+ //readonly attribute VideoTrackList videoTracks;
+ //readonly attribute TextTrackList textTracks;
+ //TextTrack addTextTrack(TextTrackKind kind, optional DOMString label = "", optional DOMString language = "");
+};
diff --git a/components/script/dom/webidls/HTMLMetaElement.webidl b/components/script/dom/webidls/HTMLMetaElement.webidl
new file mode 100644
index 00000000000..97f89b35576
--- /dev/null
+++ b/components/script/dom/webidls/HTMLMetaElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlmetaelement
+interface HTMLMetaElement : HTMLElement {
+ // attribute DOMString name;
+ // attribute DOMString httpEquiv;
+ // attribute DOMString content;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLMetaElement-partial
+partial interface HTMLMetaElement {
+ // attribute DOMString scheme;
+};
diff --git a/components/script/dom/webidls/HTMLMeterElement.webidl b/components/script/dom/webidls/HTMLMeterElement.webidl
new file mode 100644
index 00000000000..96c40ba6114
--- /dev/null
+++ b/components/script/dom/webidls/HTMLMeterElement.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlmeterelement
+interface HTMLMeterElement : HTMLElement {
+ // attribute double value;
+ // attribute double min;
+ // attribute double max;
+ // attribute double low;
+ // attribute double high;
+ // attribute double optimum;
+ //readonly attribute NodeList labels;
+};
diff --git a/components/script/dom/webidls/HTMLModElement.webidl b/components/script/dom/webidls/HTMLModElement.webidl
new file mode 100644
index 00000000000..3f8f0e62638
--- /dev/null
+++ b/components/script/dom/webidls/HTMLModElement.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlmodelement
+interface HTMLModElement : HTMLElement {
+ // attribute DOMString cite;
+ // attribute DOMString dateTime;
+};
diff --git a/components/script/dom/webidls/HTMLOListElement.webidl b/components/script/dom/webidls/HTMLOListElement.webidl
new file mode 100644
index 00000000000..9f9f654acc5
--- /dev/null
+++ b/components/script/dom/webidls/HTMLOListElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlolistelement
+interface HTMLOListElement : HTMLElement {
+ // attribute boolean reversed;
+ // attribute long start;
+ // attribute DOMString type;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLOListElement-partial
+partial interface HTMLOListElement {
+ // attribute boolean compact;
+};
diff --git a/components/script/dom/webidls/HTMLObjectElement.webidl b/components/script/dom/webidls/HTMLObjectElement.webidl
new file mode 100644
index 00000000000..56fc290e546
--- /dev/null
+++ b/components/script/dom/webidls/HTMLObjectElement.webidl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlobjectelement
+interface HTMLObjectElement : HTMLElement {
+ // attribute DOMString data;
+ // attribute DOMString type;
+ // attribute boolean typeMustMatch;
+ // attribute DOMString name;
+ // attribute DOMString useMap;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString width;
+ // attribute DOMString height;
+ //readonly attribute Document? contentDocument;
+ //readonly attribute WindowProxy? contentWindow;
+
+ //readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //legacycaller any (any... arguments);
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLObjectElement-partial
+partial interface HTMLObjectElement {
+ // attribute DOMString align;
+ // attribute DOMString archive;
+ // attribute DOMString code;
+ // attribute boolean declare;
+ // attribute unsigned long hspace;
+ // attribute DOMString standby;
+ // attribute unsigned long vspace;
+ // attribute DOMString codeBase;
+ // attribute DOMString codeType;
+
+ //[TreatNullAs=EmptyString] attribute DOMString border;
+};
diff --git a/components/script/dom/webidls/HTMLOptGroupElement.webidl b/components/script/dom/webidls/HTMLOptGroupElement.webidl
new file mode 100644
index 00000000000..13646f00ab1
--- /dev/null
+++ b/components/script/dom/webidls/HTMLOptGroupElement.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmloptgroupelement
+interface HTMLOptGroupElement : HTMLElement {
+ attribute boolean disabled;
+ // attribute DOMString label;
+};
diff --git a/components/script/dom/webidls/HTMLOptionElement.webidl b/components/script/dom/webidls/HTMLOptionElement.webidl
new file mode 100644
index 00000000000..7855449c6f4
--- /dev/null
+++ b/components/script/dom/webidls/HTMLOptionElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmloptionelement
+//[NamedConstructor=Option(optional DOMString text = "", optional DOMString value, optional boolean defaultSelected = false, optional boolean selected = false)]
+interface HTMLOptionElement : HTMLElement {
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString label;
+ // attribute boolean defaultSelected;
+ // attribute boolean selected;
+ // attribute DOMString value;
+
+ // attribute DOMString text;
+ //readonly attribute long index;
+};
diff --git a/components/script/dom/webidls/HTMLOutputElement.webidl b/components/script/dom/webidls/HTMLOutputElement.webidl
new file mode 100644
index 00000000000..d0d23d87d42
--- /dev/null
+++ b/components/script/dom/webidls/HTMLOutputElement.webidl
@@ -0,0 +1,24 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmloutputelement
+interface HTMLOutputElement : HTMLElement {
+ //[PutForwards=value] readonly attribute DOMSettableTokenList htmlFor;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString name;
+
+ //readonly attribute DOMString type;
+ // attribute DOMString defaultValue;
+ // attribute DOMString value;
+
+ //readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //readonly attribute NodeList labels;
+};
diff --git a/components/script/dom/webidls/HTMLParagraphElement.webidl b/components/script/dom/webidls/HTMLParagraphElement.webidl
new file mode 100644
index 00000000000..86cae7cbe87
--- /dev/null
+++ b/components/script/dom/webidls/HTMLParagraphElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlparagraphelement
+interface HTMLParagraphElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLParagraphElement-partial
+partial interface HTMLParagraphElement {
+ // attribute DOMString align;
+};
diff --git a/components/script/dom/webidls/HTMLParamElement.webidl b/components/script/dom/webidls/HTMLParamElement.webidl
new file mode 100644
index 00000000000..afcb6ec1d9b
--- /dev/null
+++ b/components/script/dom/webidls/HTMLParamElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlparamelement
+interface HTMLParamElement : HTMLElement {
+ // attribute DOMString name;
+ // attribute DOMString value;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLParamElement-partial
+partial interface HTMLParamElement {
+ // attribute DOMString type;
+ // attribute DOMString valueType;
+};
diff --git a/components/script/dom/webidls/HTMLPreElement.webidl b/components/script/dom/webidls/HTMLPreElement.webidl
new file mode 100644
index 00000000000..f0498ebd32c
--- /dev/null
+++ b/components/script/dom/webidls/HTMLPreElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlpreelement
+interface HTMLPreElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLPreElement-partial
+partial interface HTMLPreElement {
+ // attribute long width;
+};
diff --git a/components/script/dom/webidls/HTMLProgressElement.webidl b/components/script/dom/webidls/HTMLProgressElement.webidl
new file mode 100644
index 00000000000..53a95297afb
--- /dev/null
+++ b/components/script/dom/webidls/HTMLProgressElement.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlprogresselement
+interface HTMLProgressElement : HTMLElement {
+ // attribute double value;
+ // attribute double max;
+ //readonly attribute double position;
+ //readonly attribute NodeList labels;
+};
diff --git a/components/script/dom/webidls/HTMLQuoteElement.webidl b/components/script/dom/webidls/HTMLQuoteElement.webidl
new file mode 100644
index 00000000000..a7b1ae41276
--- /dev/null
+++ b/components/script/dom/webidls/HTMLQuoteElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlquoteelement
+interface HTMLQuoteElement : HTMLElement {
+ // attribute DOMString cite;
+};
diff --git a/components/script/dom/webidls/HTMLScriptElement.webidl b/components/script/dom/webidls/HTMLScriptElement.webidl
new file mode 100644
index 00000000000..260850fa78c
--- /dev/null
+++ b/components/script/dom/webidls/HTMLScriptElement.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlscriptelement
+interface HTMLScriptElement : HTMLElement {
+ // attribute DOMString src;
+ readonly attribute DOMString src;
+ // attribute DOMString type;
+ // attribute DOMString charset;
+ // attribute boolean async;
+ // attribute boolean defer;
+ // attribute DOMString crossOrigin;
+ [Pure]
+ attribute DOMString text;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLScriptElement-partial
+partial interface HTMLScriptElement {
+ // attribute DOMString event;
+ // attribute DOMString htmlFor;
+};
diff --git a/components/script/dom/webidls/HTMLSelectElement.webidl b/components/script/dom/webidls/HTMLSelectElement.webidl
new file mode 100644
index 00000000000..91d4c3b0917
--- /dev/null
+++ b/components/script/dom/webidls/HTMLSelectElement.webidl
@@ -0,0 +1,40 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlselectelement
+interface HTMLSelectElement : HTMLElement {
+ // attribute boolean autofocus;
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ // attribute boolean multiple;
+ // attribute DOMString name;
+ // attribute boolean required;
+ // attribute unsigned long size;
+
+ //readonly attribute DOMString type;
+
+ //readonly attribute HTMLOptionsCollection options;
+ // attribute unsigned long length;
+ //getter Element? item(unsigned long index);
+ //HTMLOptionElement? namedItem(DOMString name);
+ // Note: this function currently only exists for test_union.html.
+ void add((HTMLOptionElement or HTMLOptGroupElement) element, optional (HTMLElement or long)? before = null);
+ //void remove(); // ChildNode overload
+ //void remove(long index);
+ //setter creator void (unsigned long index, HTMLOptionElement? option);
+
+ //readonly attribute HTMLCollection selectedOptions;
+ // attribute long selectedIndex;
+ // attribute DOMString value;
+
+ //readonly attribute boolean willValidate;
+ readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //readonly attribute NodeList labels;
+};
diff --git a/components/script/dom/webidls/HTMLSourceElement.webidl b/components/script/dom/webidls/HTMLSourceElement.webidl
new file mode 100644
index 00000000000..6739f1cd0c1
--- /dev/null
+++ b/components/script/dom/webidls/HTMLSourceElement.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlsourceelement
+interface HTMLSourceElement : HTMLElement {
+ // attribute DOMString src;
+ // attribute DOMString type;
+};
diff --git a/components/script/dom/webidls/HTMLSpanElement.webidl b/components/script/dom/webidls/HTMLSpanElement.webidl
new file mode 100644
index 00000000000..ab7ac3edc85
--- /dev/null
+++ b/components/script/dom/webidls/HTMLSpanElement.webidl
@@ -0,0 +1,7 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlspanelement
+interface HTMLSpanElement : HTMLElement {};
diff --git a/components/script/dom/webidls/HTMLStyleElement.webidl b/components/script/dom/webidls/HTMLStyleElement.webidl
new file mode 100644
index 00000000000..0dd71a58769
--- /dev/null
+++ b/components/script/dom/webidls/HTMLStyleElement.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlstyleelement
+interface HTMLStyleElement : HTMLElement {
+ // attribute DOMString media;
+ // attribute DOMString type;
+ // attribute boolean scoped;
+};
+//HTMLStyleElement implements LinkStyle;
diff --git a/components/script/dom/webidls/HTMLTableCaptionElement.webidl b/components/script/dom/webidls/HTMLTableCaptionElement.webidl
new file mode 100644
index 00000000000..7ab036c8eb1
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableCaptionElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltablecaptionelement
+interface HTMLTableCaptionElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableCaptionElement-partial
+partial interface HTMLTableCaptionElement {
+ // attribute DOMString align;
+};
diff --git a/components/script/dom/webidls/HTMLTableCellElement.webidl b/components/script/dom/webidls/HTMLTableCellElement.webidl
new file mode 100644
index 00000000000..131bf02b5bc
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableCellElement.webidl
@@ -0,0 +1,29 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltablecellelement
+interface HTMLTableCellElement : HTMLElement {
+ // attribute unsigned long colSpan;
+ // attribute unsigned long rowSpan;
+ //[PutForwards=value] readonly attribute DOMSettableTokenList headers;
+ //readonly attribute long cellIndex;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableCellElement-partial
+partial interface HTMLTableCellElement {
+ // attribute DOMString align;
+ // attribute DOMString axis;
+ // attribute DOMString height;
+ // attribute DOMString width;
+
+ // attribute DOMString ch;
+ // attribute DOMString chOff;
+ // attribute boolean noWrap;
+ // attribute DOMString vAlign;
+
+ //[TreatNullAs=EmptyString] attribute DOMString bgColor;
+};
diff --git a/components/script/dom/webidls/HTMLTableColElement.webidl b/components/script/dom/webidls/HTMLTableColElement.webidl
new file mode 100644
index 00000000000..5a7cfc4b5c4
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableColElement.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltablecolelement
+interface HTMLTableColElement : HTMLElement {
+ // attribute unsigned long span;
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableColElement-partial
+partial interface HTMLTableColElement {
+ // attribute DOMString align;
+ // attribute DOMString ch;
+ // attribute DOMString chOff;
+ // attribute DOMString vAlign;
+ // attribute DOMString width;
+};
diff --git a/components/script/dom/webidls/HTMLTableDataCellElement.webidl b/components/script/dom/webidls/HTMLTableDataCellElement.webidl
new file mode 100644
index 00000000000..62669ae026a
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableDataCellElement.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltabledatacellelement
+interface HTMLTableDataCellElement : HTMLTableCellElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableDataCellElement-partial
+partial interface HTMLTableDataCellElement {
+ // attribute DOMString abbr;
+};
diff --git a/components/script/dom/webidls/HTMLTableElement.webidl b/components/script/dom/webidls/HTMLTableElement.webidl
new file mode 100644
index 00000000000..d71a38c12a5
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableElement.webidl
@@ -0,0 +1,40 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltableelement
+interface HTMLTableElement : HTMLElement {
+ attribute HTMLTableCaptionElement? caption;
+ //HTMLElement createCaption();
+ //void deleteCaption();
+ // attribute HTMLTableSectionElement? tHead;
+ //HTMLElement createTHead();
+ //void deleteTHead();
+ // attribute HTMLTableSectionElement? tFoot;
+ //HTMLElement createTFoot();
+ //void deleteTFoot();
+ //readonly attribute HTMLCollection tBodies;
+ //HTMLElement createTBody();
+ //readonly attribute HTMLCollection rows;
+ //HTMLElement insertRow(optional long index = -1);
+ //void deleteRow(long index);
+ // attribute boolean sortable;
+ //void stopSorting();
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableElement-partial
+partial interface HTMLTableElement {
+ // attribute DOMString align;
+ // attribute DOMString border;
+ // attribute DOMString frame;
+ // attribute DOMString rules;
+ // attribute DOMString summary;
+ // attribute DOMString width;
+
+ //[TreatNullAs=EmptyString] attribute DOMString bgColor;
+ //[TreatNullAs=EmptyString] attribute DOMString cellPadding;
+ //[TreatNullAs=EmptyString] attribute DOMString cellSpacing;
+};
diff --git a/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl b/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl
new file mode 100644
index 00000000000..9bbf4d6f436
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableHeaderCellElement.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltableheadercellelement
+interface HTMLTableHeaderCellElement : HTMLTableCellElement {
+ // attribute DOMString scope;
+ // attribute DOMString abbr;
+ // attribute DOMString sorted;
+ //void sort();
+};
diff --git a/components/script/dom/webidls/HTMLTableRowElement.webidl b/components/script/dom/webidls/HTMLTableRowElement.webidl
new file mode 100644
index 00000000000..7cacb013936
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableRowElement.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltablerowelement
+interface HTMLTableRowElement : HTMLElement {
+ //readonly attribute long rowIndex;
+ //readonly attribute long sectionRowIndex;
+ //readonly attribute HTMLCollection cells;
+ //HTMLElement insertCell(optional long index = -1);
+ //void deleteCell(long index);
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableRowElement-partial
+partial interface HTMLTableRowElement {
+ // attribute DOMString align;
+ // attribute DOMString ch;
+ // attribute DOMString chOff;
+ // attribute DOMString vAlign;
+
+ //[TreatNullAs=EmptyString] attribute DOMString bgColor;
+};
diff --git a/components/script/dom/webidls/HTMLTableSectionElement.webidl b/components/script/dom/webidls/HTMLTableSectionElement.webidl
new file mode 100644
index 00000000000..c3909f3f3e0
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTableSectionElement.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltablesectionelement
+interface HTMLTableSectionElement : HTMLElement {
+ //readonly attribute HTMLCollection rows;
+ //HTMLElement insertRow(optional long index = -1);
+ //void deleteRow(long index);
+
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLTableSectionElement-partial
+partial interface HTMLTableSectionElement {
+ // attribute DOMString align;
+ // attribute DOMString ch;
+ // attribute DOMString chOff;
+ // attribute DOMString vAlign;
+};
diff --git a/components/script/dom/webidls/HTMLTemplateElement.webidl b/components/script/dom/webidls/HTMLTemplateElement.webidl
new file mode 100644
index 00000000000..e148dfe2236
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTemplateElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltemplateelement
+interface HTMLTemplateElement : HTMLElement {
+ //readonly attribute DocumentFragment content;
+};
diff --git a/components/script/dom/webidls/HTMLTextAreaElement.webidl b/components/script/dom/webidls/HTMLTextAreaElement.webidl
new file mode 100644
index 00000000000..534bb87a0e5
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTextAreaElement.webidl
@@ -0,0 +1,45 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltextareaelement
+interface HTMLTextAreaElement : HTMLElement {
+ // attribute DOMString autocomplete;
+ // attribute boolean autofocus;
+ // attribute unsigned long cols;
+ // attribute DOMString dirName;
+ attribute boolean disabled;
+ //readonly attribute HTMLFormElement? form;
+ // attribute DOMString inputMode;
+ // attribute long maxLength;
+ // attribute long minLength;
+ // attribute DOMString name;
+ // attribute DOMString placeholder;
+ // attribute boolean readOnly;
+ // attribute boolean required;
+ // attribute unsigned long rows;
+ // attribute DOMString wrap;
+
+ //readonly attribute DOMString type;
+ // attribute DOMString defaultValue;
+ //[TreatNullAs=EmptyString] attribute DOMString value;
+ //readonly attribute unsigned long textLength;
+
+ //readonly attribute boolean willValidate;
+ //readonly attribute ValidityState validity;
+ //readonly attribute DOMString validationMessage;
+ //boolean checkValidity();
+ //boolean reportValidity();
+ //void setCustomValidity(DOMString error);
+
+ //readonly attribute NodeList labels;
+
+ //void select();
+ // attribute unsigned long selectionStart;
+ // attribute unsigned long selectionEnd;
+ // attribute DOMString selectionDirection;
+ //void setRangeText(DOMString replacement);
+ //void setRangeText(DOMString replacement, unsigned long start, unsigned long end, optional SelectionMode selectionMode = "preserve");
+ //void setSelectionRange(unsigned long start, unsigned long end, optional DOMString direction);
+};
diff --git a/components/script/dom/webidls/HTMLTimeElement.webidl b/components/script/dom/webidls/HTMLTimeElement.webidl
new file mode 100644
index 00000000000..20ab9b04556
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTimeElement.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltimeelement
+interface HTMLTimeElement : HTMLElement {
+ // attribute DOMString dateTime;
+};
diff --git a/components/script/dom/webidls/HTMLTitleElement.webidl b/components/script/dom/webidls/HTMLTitleElement.webidl
new file mode 100644
index 00000000000..789fba3cf17
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTitleElement.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltitleelement
+interface HTMLTitleElement : HTMLElement {
+ [Pure]
+ attribute DOMString text;
+};
diff --git a/components/script/dom/webidls/HTMLTrackElement.webidl b/components/script/dom/webidls/HTMLTrackElement.webidl
new file mode 100644
index 00000000000..bab698709ab
--- /dev/null
+++ b/components/script/dom/webidls/HTMLTrackElement.webidl
@@ -0,0 +1,21 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmltrackelement
+interface HTMLTrackElement : HTMLElement {
+ // attribute DOMString kind;
+ // attribute DOMString src;
+ // attribute DOMString srclang;
+ // attribute DOMString label;
+ // attribute boolean default;
+
+ //const unsigned short NONE = 0;
+ //const unsigned short LOADING = 1;
+ //const unsigned short LOADED = 2;
+ //const unsigned short ERROR = 3;
+ //readonly attribute unsigned short readyState;
+
+ //readonly attribute TextTrack track;
+};
diff --git a/components/script/dom/webidls/HTMLUListElement.webidl b/components/script/dom/webidls/HTMLUListElement.webidl
new file mode 100644
index 00000000000..10c6451d4c7
--- /dev/null
+++ b/components/script/dom/webidls/HTMLUListElement.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlulistelement
+interface HTMLUListElement : HTMLElement {
+ // also has obsolete members
+};
+
+// http://www.whatwg.org/html/#HTMLUListElement-partial
+partial interface HTMLUListElement {
+ // attribute boolean compact;
+ // attribute DOMString type;
+};
diff --git a/components/script/dom/webidls/HTMLUnknownElement.webidl b/components/script/dom/webidls/HTMLUnknownElement.webidl
new file mode 100644
index 00000000000..db1307ae714
--- /dev/null
+++ b/components/script/dom/webidls/HTMLUnknownElement.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.whatwg.org/specs/web-apps/current-work/ and
+ * http://dev.w3.org/csswg/cssom-view/
+ *
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+interface HTMLUnknownElement : HTMLElement {
+};
diff --git a/components/script/dom/webidls/HTMLVideoElement.webidl b/components/script/dom/webidls/HTMLVideoElement.webidl
new file mode 100644
index 00000000000..9d5d02cc530
--- /dev/null
+++ b/components/script/dom/webidls/HTMLVideoElement.webidl
@@ -0,0 +1,13 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#htmlvideoelement
+interface HTMLVideoElement : HTMLMediaElement {
+ // attribute unsigned long width;
+ // attribute unsigned long height;
+ //readonly attribute unsigned long videoWidth;
+ //readonly attribute unsigned long videoHeight;
+ // attribute DOMString poster;
+};
diff --git a/components/script/dom/webidls/Location.webidl b/components/script/dom/webidls/Location.webidl
new file mode 100644
index 00000000000..99076988122
--- /dev/null
+++ b/components/script/dom/webidls/Location.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#location
+/*[Unforgeable]*/ interface Location {
+ //void assign(DOMString url);
+ //void replace(DOMString url);
+ //void reload();
+};
+Location implements URLUtils;
diff --git a/components/script/dom/webidls/MessageEvent.webidl b/components/script/dom/webidls/MessageEvent.webidl
new file mode 100644
index 00000000000..7198708499e
--- /dev/null
+++ b/components/script/dom/webidls/MessageEvent.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#messageevent
+[Constructor(DOMString type, optional MessageEventInit eventInitDict)/*, Exposed=Window,Worker*/]
+interface MessageEvent : Event {
+ readonly attribute any data;
+ readonly attribute DOMString origin;
+ readonly attribute DOMString lastEventId;
+ //readonly attribute (WindowProxy or MessagePort)? source;
+ //readonly attribute MessagePort[]? ports;
+};
+
+dictionary MessageEventInit : EventInit {
+ any data = null;
+ DOMString origin = "";
+ DOMString lastEventId = "";
+ //DOMString channel;
+ //(WindowProxy or MessagePort)? source;
+ //sequence<MessagePort> ports;
+};
diff --git a/components/script/dom/webidls/MouseEvent.webidl b/components/script/dom/webidls/MouseEvent.webidl
new file mode 100644
index 00000000000..cdef58228c1
--- /dev/null
+++ b/components/script/dom/webidls/MouseEvent.webidl
@@ -0,0 +1,43 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-MouseEvent
+[Constructor(DOMString typeArg, optional MouseEventInit mouseEventInitDict)]
+interface MouseEvent : UIEvent {
+ readonly attribute long screenX;
+ readonly attribute long screenY;
+ readonly attribute long clientX;
+ readonly attribute long clientY;
+ readonly attribute boolean ctrlKey;
+ readonly attribute boolean shiftKey;
+ readonly attribute boolean altKey;
+ readonly attribute boolean metaKey;
+ readonly attribute short button;
+ readonly attribute EventTarget? relatedTarget;
+ // Introduced in DOM Level 3
+ //readonly attribute unsigned short buttons;
+ //boolean getModifierState (DOMString keyArg);
+};
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-MouseEventInit
+dictionary MouseEventInit : UIEventInit {
+ long screenX = 0;
+ long screenY = 0;
+ long clientX = 0;
+ long clientY = 0;
+ boolean ctrlKey = false;
+ boolean shiftKey = false;
+ boolean altKey = false;
+ boolean metaKey = false;
+ short button = 0;
+ //unsigned short buttons = 0;
+ EventTarget? relatedTarget = null;
+};
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-MouseEvent-1
+partial interface MouseEvent {
+ // Deprecated in DOM Level 3
+ void initMouseEvent (DOMString typeArg, boolean bubblesArg, boolean cancelableArg, Window? viewArg, long detailArg, long screenXArg, long screenYArg, long clientXArg, long clientYArg, boolean ctrlKeyArg, boolean altKeyArg, boolean shiftKeyArg, boolean metaKeyArg, short buttonArg, EventTarget? relatedTargetArg);
+};
diff --git a/components/script/dom/webidls/NamedNodeMap.webidl b/components/script/dom/webidls/NamedNodeMap.webidl
new file mode 100644
index 00000000000..636c4a2782f
--- /dev/null
+++ b/components/script/dom/webidls/NamedNodeMap.webidl
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface NamedNodeMap {
+ readonly attribute unsigned long length;
+ getter Attr? item(unsigned long index);
+};
diff --git a/components/script/dom/webidls/Navigator.webidl b/components/script/dom/webidls/Navigator.webidl
new file mode 100644
index 00000000000..16d96d53470
--- /dev/null
+++ b/components/script/dom/webidls/Navigator.webidl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#navigator
+interface Navigator {
+ // objects implementing this interface also implement the interfaces given below
+};
+Navigator implements NavigatorID;
+//Navigator implements NavigatorLanguage;
+//Navigator implements NavigatorOnLine;
+//Navigator implements NavigatorContentUtils;
+//Navigator implements NavigatorStorageUtils;
+//Navigator implements NavigatorPlugins;
+
+// http://www.whatwg.org/html/#navigatorid
+[NoInterfaceObject/*, Exposed=Window,Worker*/]
+interface NavigatorID {
+ readonly attribute DOMString appCodeName; // constant "Mozilla"
+ readonly attribute DOMString appName;
+ //readonly attribute DOMString appVersion;
+ readonly attribute DOMString platform;
+ readonly attribute DOMString product; // constant "Gecko"
+ boolean taintEnabled(); // constant false
+ //readonly attribute DOMString userAgent;
+};
diff --git a/components/script/dom/webidls/Node.webidl b/components/script/dom/webidls/Node.webidl
new file mode 100644
index 00000000000..3297b2c3ab1
--- /dev/null
+++ b/components/script/dom/webidls/Node.webidl
@@ -0,0 +1,79 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * http://dom.spec.whatwg.org/#interface-node
+ */
+
+interface Node : EventTarget {
+ const unsigned short ELEMENT_NODE = 1;
+ const unsigned short ATTRIBUTE_NODE = 2; // historical
+ const unsigned short TEXT_NODE = 3;
+ const unsigned short CDATA_SECTION_NODE = 4; // historical
+ const unsigned short ENTITY_REFERENCE_NODE = 5; // historical
+ const unsigned short ENTITY_NODE = 6; // historical
+ const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
+ const unsigned short COMMENT_NODE = 8;
+ const unsigned short DOCUMENT_NODE = 9;
+ const unsigned short DOCUMENT_TYPE_NODE = 10;
+ const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
+ const unsigned short NOTATION_NODE = 12; // historical
+ [Constant]
+ readonly attribute unsigned short nodeType;
+ [Pure]
+ readonly attribute DOMString nodeName;
+
+ [Pure]
+ readonly attribute DOMString? baseURI;
+
+ [Pure]
+ readonly attribute Document? ownerDocument;
+ [Pure]
+ readonly attribute Node? parentNode;
+ [Pure]
+ readonly attribute Element? parentElement;
+ boolean hasChildNodes();
+ [Constant]
+ readonly attribute NodeList childNodes;
+ [Pure]
+ readonly attribute Node? firstChild;
+ [Pure]
+ readonly attribute Node? lastChild;
+ [Pure]
+ readonly attribute Node? previousSibling;
+ [Pure]
+ readonly attribute Node? nextSibling;
+
+ [Pure]
+ attribute DOMString? nodeValue;
+ [Pure]
+ attribute DOMString? textContent;
+ void normalize();
+
+ Node cloneNode(optional boolean deep = true);
+ boolean isEqualNode(Node? node);
+
+ const unsigned short DOCUMENT_POSITION_DISCONNECTED = 0x01;
+ const unsigned short DOCUMENT_POSITION_PRECEDING = 0x02;
+ const unsigned short DOCUMENT_POSITION_FOLLOWING = 0x04;
+ const unsigned short DOCUMENT_POSITION_CONTAINS = 0x08;
+ const unsigned short DOCUMENT_POSITION_CONTAINED_BY = 0x10;
+ const unsigned short DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
+ unsigned short compareDocumentPosition(Node other);
+ boolean contains(Node? other);
+
+ DOMString? lookupPrefix(DOMString? namespace);
+ DOMString? lookupNamespaceURI(DOMString? prefix);
+ boolean isDefaultNamespace(DOMString? namespace);
+
+ [Throws]
+ Node insertBefore(Node node, Node? child);
+ [Throws]
+ Node appendChild(Node node);
+ [Throws]
+ Node replaceChild(Node node, Node child);
+ [Throws]
+ Node removeChild(Node child);
+};
diff --git a/components/script/dom/webidls/NodeFilter.webidl b/components/script/dom/webidls/NodeFilter.webidl
new file mode 100644
index 00000000000..b84b369829e
--- /dev/null
+++ b/components/script/dom/webidls/NodeFilter.webidl
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-nodefilter
+ */
+// Import form http://hg.mozilla.org/mozilla-central/file/a5a720259d79/dom/webidl/NodeFilter.webidl
+
+callback interface NodeFilter {
+ // Constants for acceptNode()
+ // const unsigned short FILTER_ACCEPT = 1;
+ // const unsigned short FILTER_REJECT = 2;
+ // const unsigned short FILTER_SKIP = 3;
+
+ // Constants for whatToShow
+ // const unsigned long SHOW_ALL = 0xFFFFFFFF;
+ // const unsigned long SHOW_ELEMENT = 0x1;
+ // const unsigned long SHOW_ATTRIBUTE = 0x2; // historical
+ // const unsigned long SHOW_TEXT = 0x4;
+ // const unsigned long SHOW_CDATA_SECTION = 0x8; // historical
+ // const unsigned long SHOW_ENTITY_REFERENCE = 0x10; // historical
+ // const unsigned long SHOW_ENTITY = 0x20; // historical
+ // const unsigned long SHOW_PROCESSING_INSTRUCTION = 0x40;
+ // const unsigned long SHOW_COMMENT = 0x80;
+ // const unsigned long SHOW_DOCUMENT = 0x100;
+ // const unsigned long SHOW_DOCUMENT_TYPE = 0x200;
+ // const unsigned long SHOW_DOCUMENT_FRAGMENT = 0x400;
+ // const unsigned long SHOW_NOTATION = 0x800; // historical
+
+ unsigned short acceptNode(Node node);
+};
diff --git a/components/script/dom/webidls/NodeIterator.webidl b/components/script/dom/webidls/NodeIterator.webidl
new file mode 100644
index 00000000000..6eb684dd9f9
--- /dev/null
+++ b/components/script/dom/webidls/NodeIterator.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://www.w3.org/TR/2012/WD-dom-20120105/
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+// Import from http://hg.mozilla.org/mozilla-central/raw-file/a5a720259d79/dom/webidl/NodeIterator.webidl
+
+interface NodeIterator {
+ // [Constant]
+ // readonly attribute Node root;
+ // [Pure]
+ // readonly attribute Node? referenceNode;
+ // [Pure]
+ // readonly attribute boolean pointerBeforeReferenceNode;
+ // [Constant]
+ // readonly attribute unsigned long whatToShow;
+ // [Constant]
+ // readonly attribute NodeFilter? filter;
+
+ // [Throws]
+ // Node? nextNode();
+ // [Throws]
+ // Node? previousNode();
+
+ // void detach();
+};
diff --git a/components/script/dom/webidls/NodeList.webidl b/components/script/dom/webidls/NodeList.webidl
new file mode 100644
index 00000000000..9773f8efcef
--- /dev/null
+++ b/components/script/dom/webidls/NodeList.webidl
@@ -0,0 +1,13 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is:
+ * http://dom.spec.whatwg.org/#interface-nodelist
+ */
+
+interface NodeList {
+ readonly attribute unsigned long length;
+ getter Node? item(unsigned long index);
+};
diff --git a/components/script/dom/webidls/ParentNode.webidl b/components/script/dom/webidls/ParentNode.webidl
new file mode 100644
index 00000000000..daa4339611f
--- /dev/null
+++ b/components/script/dom/webidls/ParentNode.webidl
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-parentnode
+ */
+
+[NoInterfaceObject]
+interface ParentNode {
+ [Constant]
+ readonly attribute HTMLCollection children;
+ /*
+ [Pure]
+ readonly attribute Element? firstElementChild;
+ [Pure]
+ readonly attribute Element? lastElementChild;
+ [Pure]
+ readonly attribute unsigned long childElementCount;
+ */
+ // Not implemented yet
+ // void prepend((Node or DOMString)... nodes);
+ // void append((Node or DOMString)... nodes);
+
+ //Element? query(DOMString relativeSelectors);
+ //[NewObject]
+ //Elements queryAll(DOMString relativeSelectors);
+ [Throws]
+ Element? querySelector(DOMString selectors);
+ //[NewObject]
+ [Throws]
+ NodeList querySelectorAll(DOMString selectors);
+};
diff --git a/components/script/dom/webidls/Performance.webidl b/components/script/dom/webidls/Performance.webidl
new file mode 100644
index 00000000000..ff7e0ee3754
--- /dev/null
+++ b/components/script/dom/webidls/Performance.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute
+ */
+
+typedef double DOMHighResTimeStamp;
+
+interface Performance {
+ readonly attribute PerformanceTiming timing;
+ /* readonly attribute PerformanceNavigation navigation; */
+};
+
+partial interface Performance {
+ DOMHighResTimeStamp now();
+};
diff --git a/components/script/dom/webidls/PerformanceTiming.webidl b/components/script/dom/webidls/PerformanceTiming.webidl
new file mode 100644
index 00000000000..c5dfd4502c7
--- /dev/null
+++ b/components/script/dom/webidls/PerformanceTiming.webidl
@@ -0,0 +1,32 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-navigation-timing-interface
+ */
+
+interface PerformanceTiming {
+ readonly attribute unsigned long long navigationStart;
+ /* readonly attribute unsigned long long unloadEventStart;
+ readonly attribute unsigned long long unloadEventEnd;
+ readonly attribute unsigned long long redirectStart;
+ readonly attribute unsigned long long redirectEnd;
+ readonly attribute unsigned long long fetchStart;
+ readonly attribute unsigned long long domainLookupStart;
+ readonly attribute unsigned long long domainLookupEnd;
+ readonly attribute unsigned long long connectStart;
+ readonly attribute unsigned long long connectEnd;
+ readonly attribute unsigned long long secureConnectionStart;
+ readonly attribute unsigned long long requestStart;
+ readonly attribute unsigned long long responseStart;
+ readonly attribute unsigned long long responseEnd;
+ readonly attribute unsigned long long domLoading;
+ readonly attribute unsigned long long domInteractive;
+ readonly attribute unsigned long long domContentLoadedEventStart;
+ readonly attribute unsigned long long domContentLoadedEventEnd;
+ readonly attribute unsigned long long domComplete;
+ readonly attribute unsigned long long loadEventStart;
+ readonly attribute unsigned long long loadEventEnd; */
+};
diff --git a/components/script/dom/webidls/ProcessingInstruction.webidl b/components/script/dom/webidls/ProcessingInstruction.webidl
new file mode 100644
index 00000000000..96426538900
--- /dev/null
+++ b/components/script/dom/webidls/ProcessingInstruction.webidl
@@ -0,0 +1,12 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-processinginstruction
+ */
+
+interface ProcessingInstruction : CharacterData {
+ readonly attribute DOMString target;
+};
diff --git a/components/script/dom/webidls/ProgressEvent.webidl b/components/script/dom/webidls/ProgressEvent.webidl
new file mode 100644
index 00000000000..420d745fe14
--- /dev/null
+++ b/components/script/dom/webidls/ProgressEvent.webidl
@@ -0,0 +1,28 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://xhr.spec.whatwg.org/#interface-progressevent
+ *
+ * To the extent possible under law, the editor has waived all copyright
+ * and related or neighboring rights to this work. In addition, as of 1 May 2014,
+ * the editor has made this specification available under the Open Web Foundation
+ * Agreement Version 1.0, which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+[Constructor(DOMString type, optional ProgressEventInit eventInitDict)/*,
+ Exposed=Window,Worker*/]
+interface ProgressEvent : Event {
+ readonly attribute boolean lengthComputable;
+ readonly attribute unsigned long long loaded;
+ readonly attribute unsigned long long total;
+};
+
+dictionary ProgressEventInit : EventInit {
+ boolean lengthComputable = false;
+ unsigned long long loaded = 0;
+ unsigned long long total = 0;
+};
diff --git a/components/script/dom/webidls/Range.webidl b/components/script/dom/webidls/Range.webidl
new file mode 100644
index 00000000000..d74411b1dd9
--- /dev/null
+++ b/components/script/dom/webidls/Range.webidl
@@ -0,0 +1,85 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#range
+ * http://domparsing.spec.whatwg.org/#dom-range-createcontextualfragment
+ * http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
+ */
+
+[Constructor]
+interface Range {
+ // [Throws]
+ // readonly attribute Node startContainer;
+ // [Throws]
+ // readonly attribute unsigned long startOffset;
+ // [Throws]
+ // readonly attribute Node endContainer;
+ // [Throws]
+ // readonly attribute unsigned long endOffset;
+ // readonly attribute boolean collapsed;
+ // [Throws]
+ // readonly attribute Node commonAncestorContainer;
+
+ // [Throws]
+ // void setStart(Node refNode, unsigned long offset);
+ // [Throws]
+ // void setEnd(Node refNode, unsigned long offset);
+ // [Throws]
+ // void setStartBefore(Node refNode);
+ // [Throws]
+ // void setStartAfter(Node refNode);
+ // [Throws]
+ // void setEndBefore(Node refNode);
+ // [Throws]
+ // void setEndAfter(Node refNode);
+ // void collapse(optional boolean toStart = false);
+ // [Throws]
+ // void selectNode(Node refNode);
+ // [Throws]
+ // void selectNodeContents(Node refNode);
+
+ // const unsigned short START_TO_START = 0;
+ // const unsigned short START_TO_END = 1;
+ // const unsigned short END_TO_END = 2;
+ // const unsigned short END_TO_START = 3;
+ // [Throws]
+ // short compareBoundaryPoints(unsigned short how, Range sourceRange);
+ // [Throws]
+ // void deleteContents();
+ // [Throws]
+ // DocumentFragment extractContents();
+ // [Throws]
+ // DocumentFragment cloneContents();
+ // [Throws]
+ // void insertNode(Node node);
+ // [Throws]
+ // void surroundContents(Node newParent);
+
+ // Range cloneRange();
+ void detach();
+
+ // [Throws]
+ // boolean isPointInRange(Node node, unsigned long offset);
+ // [Throws]
+ // short comparePoint(Node node, unsigned long offset);
+
+ // [Throws]
+ // boolean intersectsNode(Node node);
+
+ // stringifier;
+};
+
+// http://domparsing.spec.whatwg.org/#dom-range-createcontextualfragment
+partial interface Range {
+ // [Throws]
+ // DocumentFragment createContextualFragment(DOMString fragment);
+};//
+
+//// http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
+partial interface Range {
+ // DOMRectList? getClientRects();
+ // DOMRect getBoundingClientRect();
+};
diff --git a/components/script/dom/webidls/Screen.webidl b/components/script/dom/webidls/Screen.webidl
new file mode 100644
index 00000000000..3065c113b96
--- /dev/null
+++ b/components/script/dom/webidls/Screen.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://dev.w3.org/csswg/cssom-view/#the-screen-interface
+interface Screen {
+ //readonly attribute double availWidth;
+ //readonly attribute double availHeight;
+ //readonly attribute double width;
+ //readonly attribute double height;
+ readonly attribute unsigned long colorDepth;
+ readonly attribute unsigned long pixelDepth;
+};
diff --git a/components/script/dom/webidls/TestBinding.webidl b/components/script/dom/webidls/TestBinding.webidl
new file mode 100644
index 00000000000..e8ef05d8242
--- /dev/null
+++ b/components/script/dom/webidls/TestBinding.webidl
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+enum TestEnum { "", "foo", "bar" };
+
+dictionary TestDictionary {
+ boolean booleanValue;
+ byte byteValue;
+ octet octetValue;
+ short shortValue;
+ unsigned short unsignedShortValue;
+ long longValue;
+ unsigned long unsignedLongValue;
+ long long longLongValue;
+ unsigned long long unsignedLongLongValue;
+ float floatValue;
+ double doubleValue;
+ DOMString stringValue;
+ TestEnum enumValue;
+ Blob interfaceValue;
+ any anyValue;
+};
+
+dictionary TestDictionaryDefaults {
+ boolean booleanValue = false;
+ byte byteValue = 7;
+ octet octetValue = 7;
+ short shortValue = 7;
+ unsigned short unsignedShortValue = 7;
+ long longValue = 7;
+ unsigned long unsignedLongValue = 7;
+ long long longLongValue = 7;
+ unsigned long long unsignedLongLongValue = 7;
+ // float floatValue = 7.0;
+ // double doubleValue = 7.0;
+ DOMString stringValue = "";
+ TestEnum enumValue = "bar";
+ any anyValue = null;
+
+ boolean? nullableBooleanValue = false;
+ byte? nullableByteValue = 7;
+ octet? nullableOctetValue = 7;
+ short? nullableShortValue = 7;
+ unsigned short? nullableUnsignedShortValue = 7;
+ long? nullableLongValue = 7;
+ unsigned long? nullableUnsignedLongValue = 7;
+ long long? nullableLongLongValue = 7;
+ unsigned long long? nullableUnsignedLongLongValue = 7;
+ // float? nullableFloatValue = 7.0;
+ // double? nullableDoubleValue = 7.0;
+ DOMString? nullableStringValue = "";
+ // TestEnum? nullableEnumValue = "bar";
+};
+
+interface TestBinding {
+ attribute boolean booleanAttribute;
+ attribute byte byteAttribute;
+ attribute octet octetAttribute;
+ attribute short shortAttribute;
+ attribute unsigned short unsignedShortAttribute;
+ attribute long longAttribute;
+ attribute unsigned long unsignedLongAttribute;
+ attribute long long longLongAttribute;
+ attribute unsigned long long unsignedLongLongAttribute;
+ attribute float floatAttribute;
+ attribute double doubleAttribute;
+ attribute DOMString stringAttribute;
+ attribute ByteString byteStringAttribute;
+ attribute TestEnum enumAttribute;
+ attribute Blob interfaceAttribute;
+ attribute (HTMLElement or long) unionAttribute;
+ attribute (Event or DOMString) union2Attribute;
+ attribute any anyAttribute;
+
+ attribute boolean? booleanAttributeNullable;
+ attribute byte? byteAttributeNullable;
+ attribute octet? octetAttributeNullable;
+ attribute short? shortAttributeNullable;
+ attribute unsigned short? unsignedShortAttributeNullable;
+ attribute long? longAttributeNullable;
+ attribute unsigned long? unsignedLongAttributeNullable;
+ attribute long long? longLongAttributeNullable;
+ attribute unsigned long long? unsignedLongLongAttributeNullable;
+ attribute float? floatAttributeNullable;
+ attribute double? doubleAttributeNullable;
+ attribute DOMString? stringAttributeNullable;
+ attribute ByteString? byteStringAttributeNullable;
+ readonly attribute TestEnum? enumAttributeNullable;
+ attribute Blob? interfaceAttributeNullable;
+ attribute (HTMLElement or long)? unionAttributeNullable;
+ attribute (Event or DOMString)? union2AttributeNullable;
+
+ void receiveVoid();
+ boolean receiveBoolean();
+ byte receiveByte();
+ octet receiveOctet();
+ short receiveShort();
+ unsigned short receiveUnsignedShort();
+ long receiveLong();
+ unsigned long receiveUnsignedLong();
+ long long receiveLongLong();
+ unsigned long long receiveUnsignedLongLong();
+ float receiveFloat();
+ double receiveDouble();
+ DOMString receiveString();
+ ByteString receiveByteString();
+ TestEnum receiveEnum();
+ Blob receiveInterface();
+ any receiveAny();
+ (HTMLElement or long) receiveUnion();
+ (Event or DOMString) receiveUnion2();
+
+ byte? receiveNullableByte();
+ boolean? receiveNullableBoolean();
+ octet? receiveNullableOctet();
+ short? receiveNullableShort();
+ unsigned short? receiveNullableUnsignedShort();
+ long? receiveNullableLong();
+ unsigned long? receiveNullableUnsignedLong();
+ long long? receiveNullableLongLong();
+ unsigned long long? receiveNullableUnsignedLongLong();
+ float? receiveNullableFloat();
+ double? receiveNullableDouble();
+ DOMString? receiveNullableString();
+ ByteString? receiveNullableByteString();
+ TestEnum? receiveNullableEnum();
+ Blob? receiveNullableInterface();
+ (HTMLElement or long)? receiveNullableUnion();
+ (Event or DOMString)? receiveNullableUnion2();
+
+ void passBoolean(boolean arg);
+ void passByte(byte arg);
+ void passOctet(octet arg);
+ void passShort(short arg);
+ void passUnsignedShort(unsigned short arg);
+ void passLong(long arg);
+ void passUnsignedLong(unsigned long arg);
+ void passLongLong(long long arg);
+ void passUnsignedLongLong(unsigned long long arg);
+ void passFloat(float arg);
+ void passDouble(double arg);
+ void passString(DOMString arg);
+ void passByteString(ByteString arg);
+ void passEnum(TestEnum arg);
+ void passInterface(Blob arg);
+ void passUnion((HTMLElement or long) arg);
+ void passUnion2((Event or DOMString) data);
+ void passUnion3((Blob or DOMString) data);
+ void passAny(any arg);
+
+ void passNullableBoolean(boolean? arg);
+ void passNullableByte(byte? arg);
+ void passNullableOctet(octet? arg);
+ void passNullableShort(short? arg);
+ void passNullableUnsignedShort(unsigned short? arg);
+ void passNullableLong(long? arg);
+ void passNullableUnsignedLong(unsigned long? arg);
+ void passNullableLongLong(long long? arg);
+ void passNullableUnsignedLongLong(unsigned long long? arg);
+ void passNullableFloat(float? arg);
+ void passNullableDouble(double? arg);
+ void passNullableString(DOMString? arg);
+ void passNullableByteString(ByteString? arg);
+ // void passNullableEnum(TestEnum? arg);
+ void passNullableInterface(Blob? arg);
+ void passNullableUnion((HTMLElement or long)? arg);
+ void passNullableUnion2((Event or DOMString)? data);
+
+ void passOptionalBoolean(optional boolean arg);
+ void passOptionalByte(optional byte arg);
+ void passOptionalOctet(optional octet arg);
+ void passOptionalShort(optional short arg);
+ void passOptionalUnsignedShort(optional unsigned short arg);
+ void passOptionalLong(optional long arg);
+ void passOptionalUnsignedLong(optional unsigned long arg);
+ void passOptionalLongLong(optional long long arg);
+ void passOptionalUnsignedLongLong(optional unsigned long long arg);
+ void passOptionalFloat(optional float arg);
+ void passOptionalDouble(optional double arg);
+ void passOptionalString(optional DOMString arg);
+ void passOptionalByteString(optional ByteString arg);
+ void passOptionalEnum(optional TestEnum arg);
+ void passOptionalInterface(optional Blob arg);
+ void passOptionalUnion(optional (HTMLElement or long) arg);
+ void passOptionalUnion2(optional (Event or DOMString) data);
+ void passOptionalAny(optional any arg);
+
+ void passOptionalNullableBoolean(optional boolean? arg);
+ void passOptionalNullableByte(optional byte? arg);
+ void passOptionalNullableOctet(optional octet? arg);
+ void passOptionalNullableShort(optional short? arg);
+ void passOptionalNullableUnsignedShort(optional unsigned short? arg);
+ void passOptionalNullableLong(optional long? arg);
+ void passOptionalNullableUnsignedLong(optional unsigned long? arg);
+ void passOptionalNullableLongLong(optional long long? arg);
+ void passOptionalNullableUnsignedLongLong(optional unsigned long long? arg);
+ void passOptionalNullableFloat(optional float? arg);
+ void passOptionalNullableDouble(optional double? arg);
+ void passOptionalNullableString(optional DOMString? arg);
+ void passOptionalNullableByteString(optional ByteString? arg);
+ // void passOptionalNullableEnum(optional TestEnum? arg);
+ void passOptionalNullableInterface(optional Blob? arg);
+ void passOptionalNullableUnion(optional (HTMLElement or long)? arg);
+ void passOptionalNullableUnion2(optional (Event or DOMString)? data);
+
+ void passOptionalBooleanWithDefault(optional boolean arg = false);
+ void passOptionalByteWithDefault(optional byte arg = 0);
+ void passOptionalOctetWithDefault(optional octet arg = 19);
+ void passOptionalShortWithDefault(optional short arg = 5);
+ void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
+ void passOptionalLongWithDefault(optional long arg = 7);
+ void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
+ void passOptionalLongLongWithDefault(optional long long arg = -12);
+ void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
+ void passOptionalStringWithDefault(optional DOMString arg = "");
+ void passOptionalEnumWithDefault(optional TestEnum arg = "foo");
+ // void passOptionalUnionWithDefault(optional (HTMLElement or long) arg = 9);
+ // void passOptionalUnion2WithDefault(optional(Event or DOMString)? data = "foo");
+
+ void passOptionalNullableBooleanWithDefault(optional boolean? arg = null);
+ void passOptionalNullableByteWithDefault(optional byte? arg = null);
+ void passOptionalNullableOctetWithDefault(optional octet? arg = null);
+ void passOptionalNullableShortWithDefault(optional short? arg = null);
+ void passOptionalNullableUnsignedShortWithDefault(optional unsigned short? arg = null);
+ void passOptionalNullableLongWithDefault(optional long? arg = null);
+ void passOptionalNullableUnsignedLongWithDefault(optional unsigned long? arg = null);
+ void passOptionalNullableLongLongWithDefault(optional long long? arg = null);
+ void passOptionalNullableUnsignedLongLongWithDefault(optional unsigned long long? arg = null);
+ void passOptionalNullableStringWithDefault(optional DOMString? arg = null);
+ void passOptionalNullableByteStringWithDefault(optional ByteString? arg = null);
+ // void passOptionalNullableEnumWithDefault(optional TestEnum? arg = null);
+ void passOptionalNullableInterfaceWithDefault(optional Blob? arg = null);
+ void passOptionalNullableUnionWithDefault(optional (HTMLElement or long)? arg = null);
+ void passOptionalNullableUnion2WithDefault(optional (Event or DOMString)? data = null);
+ void passOptionalAnyWithDefault(optional any arg = null);
+
+ void passOptionalNullableBooleanWithNonNullDefault(optional boolean? arg = false);
+ void passOptionalNullableByteWithNonNullDefault(optional byte? arg = 7);
+ void passOptionalNullableOctetWithNonNullDefault(optional octet? arg = 7);
+ void passOptionalNullableShortWithNonNullDefault(optional short? arg = 7);
+ void passOptionalNullableUnsignedShortWithNonNullDefault(optional unsigned short? arg = 7);
+ void passOptionalNullableLongWithNonNullDefault(optional long? arg = 7);
+ void passOptionalNullableUnsignedLongWithNonNullDefault(optional unsigned long? arg = 7);
+ void passOptionalNullableLongLongWithNonNullDefault(optional long long? arg = 7);
+ void passOptionalNullableUnsignedLongLongWithNonNullDefault(optional unsigned long long? arg = 7);
+ // void passOptionalNullableFloatWithNonNullDefault(optional float? arg = 0.0);
+ // void passOptionalNullableDoubleWithNonNullDefault(optional double? arg = 0.0);
+ void passOptionalNullableStringWithNonNullDefault(optional DOMString? arg = "");
+ // void passOptionalNullableEnumWithNonNullDefault(optional TestEnum? arg = "foo");
+ // void passOptionalNullableUnionWithNonNullDefault(optional (HTMLElement or long)? arg = 7);
+ // void passOptionalNullableUnion2WithNonNullDefault(optional (Event or DOMString)? data = "foo");
+
+ void passVariadicBoolean(boolean... args);
+ void passVariadicByte(byte... args);
+ void passVariadicOctet(octet... args);
+ void passVariadicShort(short... args);
+ void passVariadicUnsignedShort(unsigned short... args);
+ void passVariadicLong(long... args);
+ void passVariadicUnsignedLong(unsigned long... args);
+ void passVariadicLongLong(long long... args);
+ void passVariadicUnsignedLongLong(unsigned long long... args);
+ void passVariadicFloat(float... args);
+ void passVariadicDouble(double... args);
+ void passVariadicString(DOMString... args);
+ void passVariadicByteString(ByteString... args);
+ void passVariadicEnum(TestEnum... args);
+ // void passVariadicInterface(Blob... args);
+ void passVariadicUnion((HTMLElement or long)... args);
+ void passVariadicUnion2((Event or DOMString)... args);
+ void passVariadicUnion3((Blob or DOMString)... args);
+ void passVariadicAny(any... args);
+
+ static attribute boolean booleanAttributeStatic;
+ static void receiveVoidStatic();
+};
diff --git a/components/script/dom/webidls/Text.webidl b/components/script/dom/webidls/Text.webidl
new file mode 100644
index 00000000000..972797c73c8
--- /dev/null
+++ b/components/script/dom/webidls/Text.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/
+ *
+ * To the extent possible under law, the editors have waived all copyright
+ * and related or neighboring rights to this work.
+ */
+
+// http://dom.spec.whatwg.org/#text
+[Constructor(optional DOMString data = "")]
+interface Text : CharacterData {
+ //[NewObject] Text splitText(unsigned long offset);
+ //readonly attribute DOMString wholeText;
+};
diff --git a/components/script/dom/webidls/TreeWalker.webidl b/components/script/dom/webidls/TreeWalker.webidl
new file mode 100644
index 00000000000..70987abb528
--- /dev/null
+++ b/components/script/dom/webidls/TreeWalker.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://dom.spec.whatwg.org/#interface-treewalker
+ */
+
+interface TreeWalker {
+ // [SameObject] readonly attribute Node root;
+ // readonly attribute unsigned long whatToShow;
+ // readonly attribute NodeFilter? filter;
+ // attribute Node currentNode;
+
+ // Node? parentNode();
+ // Node? firstChild();
+ // Node? lastChild();
+ // Node? previousSibling();
+ // Node? nextSibling();
+ // Node? previousNode();
+ // Node? nextNode();
+};
diff --git a/components/script/dom/webidls/UIEvent.webidl b/components/script/dom/webidls/UIEvent.webidl
new file mode 100644
index 00000000000..4f5caeaad14
--- /dev/null
+++ b/components/script/dom/webidls/UIEvent.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-UIEvent
+[Constructor(DOMString type, optional UIEventInit eventInitDict)]
+interface UIEvent : Event {
+ // readonly attribute WindowProxy? view;
+ readonly attribute Window? view;
+ readonly attribute long detail;
+};
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-UIEventInit
+dictionary UIEventInit : EventInit {
+ // WindowProxy? view = null;
+ Window? view = null;
+ long detail = 0;
+};
+
+// https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#idl-def-UIEvent-1
+partial interface UIEvent {
+ // Deprecated in DOM Level 3
+ void initUIEvent (DOMString typeArg, boolean bubblesArg, boolean cancelableArg, Window? viewArg, long detailArg);
+};
diff --git a/components/script/dom/webidls/URLSearchParams.webidl b/components/script/dom/webidls/URLSearchParams.webidl
new file mode 100644
index 00000000000..c2e401c45f0
--- /dev/null
+++ b/components/script/dom/webidls/URLSearchParams.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://url.spec.whatwg.org/#interface-urlsearchparams
+ */
+
+[Constructor(optional (DOMString or URLSearchParams) init)]
+interface URLSearchParams {
+ void append(DOMString name, DOMString value);
+ void delete(DOMString name);
+ DOMString? get(DOMString name);
+ // sequence<DOMString> getAll(DOMString name);
+ boolean has(DOMString name);
+ void set(DOMString name, DOMString value);
+ //stringifier;
+};
diff --git a/components/script/dom/webidls/URLUtils.webidl b/components/script/dom/webidls/URLUtils.webidl
new file mode 100644
index 00000000000..58fe13c5508
--- /dev/null
+++ b/components/script/dom/webidls/URLUtils.webidl
@@ -0,0 +1,25 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://url.spec.whatwg.org/#urlutils
+[NoInterfaceObject]
+interface URLUtils {
+ //stringifier attribute ScalarValueString href;
+ readonly attribute DOMString href;
+ //readonly attribute ScalarValueString origin;
+
+ // attribute ScalarValueString protocol;
+ // attribute ScalarValueString username;
+ // attribute ScalarValueString password;
+ // attribute ScalarValueString host;
+ // attribute ScalarValueString hostname;
+ // attribute ScalarValueString port;
+ // attribute ScalarValueString pathname;
+ // attribute ScalarValueString search;
+ readonly attribute DOMString search;
+ // attribute URLSearchParams searchParams;
+ // attribute ScalarValueString hash;
+ readonly attribute DOMString hash;
+};
diff --git a/components/script/dom/webidls/URLUtilsReadOnly.webidl b/components/script/dom/webidls/URLUtilsReadOnly.webidl
new file mode 100644
index 00000000000..8518019a6c1
--- /dev/null
+++ b/components/script/dom/webidls/URLUtilsReadOnly.webidl
@@ -0,0 +1,23 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://url.spec.whatwg.org/#urlutilsreadonly
+[NoInterfaceObject/*,
+ Exposed=(Window,Worker)*/]
+interface URLUtilsReadOnly {
+ //stringifier readonly attribute ScalarValueString href;
+ readonly attribute DOMString href;
+ //readonly attribute ScalarValueString origin;
+
+ //readonly attribute ScalarValueString protocol;
+ //readonly attribute ScalarValueString host;
+ //readonly attribute ScalarValueString hostname;
+ //readonly attribute ScalarValueString port;
+ //readonly attribute ScalarValueString pathname;
+ //readonly attribute ScalarValueString search;
+ readonly attribute DOMString search;
+ //readonly attribute ScalarValueString hash;
+ readonly attribute DOMString hash;
+};
diff --git a/components/script/dom/webidls/ValidityState.webidl b/components/script/dom/webidls/ValidityState.webidl
new file mode 100644
index 00000000000..d99677574cd
--- /dev/null
+++ b/components/script/dom/webidls/ValidityState.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#validitystate
+interface ValidityState {
+ //readonly attribute boolean valueMissing;
+ //readonly attribute boolean typeMismatch;
+ //readonly attribute boolean patternMismatch;
+ //readonly attribute boolean tooLong;
+ //readonly attribute boolean tooShort;
+ //readonly attribute boolean rangeUnderflow;
+ //readonly attribute boolean rangeOverflow;
+ //readonly attribute boolean stepMismatch;
+ //readonly attribute boolean badInput;
+ //readonly attribute boolean customError;
+ //readonly attribute boolean valid;
+};
diff --git a/components/script/dom/webidls/Window.webidl b/components/script/dom/webidls/Window.webidl
new file mode 100644
index 00000000000..9cd6ed1c045
--- /dev/null
+++ b/components/script/dom/webidls/Window.webidl
@@ -0,0 +1,131 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#window
+[PrimaryGlobal]
+/*sealed*/ interface Window : EventTarget {
+ // the current browsing context
+ //[Unforgeable] readonly attribute WindowProxy window;
+ //[Replaceable] readonly attribute WindowProxy self;
+ readonly attribute Window window;
+ readonly attribute Window self;
+ /*[Unforgeable]*/ readonly attribute Document document;
+ // attribute DOMString name;
+ /*[PutForwards=href, Unforgeable]*/ readonly attribute Location location;
+ //readonly attribute History history;
+ //[Replaceable] readonly attribute BarProp locationbar;
+ //[Replaceable] readonly attribute BarProp menubar;
+ //[Replaceable] readonly attribute BarProp personalbar;
+ //[Replaceable] readonly attribute BarProp scrollbars;
+ //[Replaceable] readonly attribute BarProp statusbar;
+ //[Replaceable] readonly attribute BarProp toolbar;
+ // attribute DOMString status;
+ void close();
+ //readonly attribute boolean closed;
+ //void stop();
+ //void focus();
+ //void blur();
+
+ // other browsing contexts
+ //[Replaceable] readonly attribute WindowProxy frames;
+ readonly attribute Window frames;
+ //[Replaceable] readonly attribute unsigned long length;
+ //[Unforgeable] readonly attribute WindowProxy top;
+ // attribute any opener;
+ //readonly attribute WindowProxy parent;
+ readonly attribute Window parent;
+ //readonly attribute Element? frameElement;
+ //WindowProxy open(optional DOMString url = "about:blank", optional DOMString target = "_blank", optional DOMString features = "", optional boolean replace = false);
+ //getter WindowProxy (unsigned long index);
+ //getter object (DOMString name);
+
+ // the user agent
+ readonly attribute Navigator navigator;
+ //[Replaceable] readonly attribute External external;
+ //readonly attribute ApplicationCache applicationCache;
+
+ // user prompts
+ //void alert();
+ void alert(DOMString message);
+ //boolean confirm(optional DOMString message = "");
+ //DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
+ //void print();
+ //any showModalDialog(DOMString url, optional any argument);
+
+ //void postMessage(any message, DOMString targetOrigin, optional sequence<Transferable> transfer);
+
+ // also has obsolete members
+};
+Window implements GlobalEventHandlers;
+Window implements WindowEventHandlers;
+
+// http://www.whatwg.org/html/#windowtimers
+[NoInterfaceObject/*, Exposed=Window,Worker*/]
+interface WindowTimers {
+ //long setTimeout(Function handler, optional long timeout = 0, any... arguments);
+ //long setTimeout(DOMString handler, optional long timeout = 0, any... arguments);
+ long setTimeout(any handler, optional long timeout = 0);
+ void clearTimeout(optional long handle = 0);
+ //long setInterval(Function handler, optional long timeout = 0, any... arguments);
+ //long setInterval(DOMString handler, optional long timeout = 0, any... arguments);
+ long setInterval(any handler, optional long timeout = 0);
+ void clearInterval(optional long handle = 0);
+};
+Window implements WindowTimers;
+
+// http://www.whatwg.org/html/#atob
+[NoInterfaceObject/*, Exposed=Window,Worker*/]
+interface WindowBase64 {
+ [Throws]
+ DOMString btoa(DOMString btoa);
+ [Throws]
+ DOMString atob(DOMString atob);
+};
+Window implements WindowBase64;
+
+// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute
+partial interface Window {
+ /*[Replaceable]*/ readonly attribute Performance performance;
+};
+
+// http://dev.w3.org/csswg/cssom-view/#extensions-to-the-window-interface
+partial interface Window {
+ //MediaQueryList matchMedia(DOMString query);
+ [SameObject] readonly attribute Screen screen;
+
+ // browsing context
+ //void moveTo(double x, double y);
+ //void moveBy(double x, double y);
+ //void resizeTo(double x, double y);
+ //void resizeBy(double x, double y);
+
+ // viewport
+ //readonly attribute double innerWidth;
+ //readonly attribute double innerHeight;
+
+ // viewport scrolling
+ //readonly attribute double scrollX;
+ //readonly attribute double pageXOffset;
+ //readonly attribute double scrollY;
+ //readonly attribute double pageYOffset;
+ //void scroll(double x, double y, optional ScrollOptions options);
+ //void scrollTo(double x, double y, optional ScrollOptions options);
+ //void scrollBy(double x, double y, optional ScrollOptions options);
+
+ // client
+ //readonly attribute double screenX;
+ //readonly attribute double screenY;
+ //readonly attribute double outerWidth;
+ //readonly attribute double outerHeight;
+ //readonly attribute double devicePixelRatio;
+};
+
+// Proprietary extensions.
+partial interface Window {
+ readonly attribute Console console;
+ void debug(DOMString arg);
+ void gc();
+};
+Window implements OnErrorEventHandlerForWindow;
diff --git a/components/script/dom/webidls/Worker.webidl b/components/script/dom/webidls/Worker.webidl
new file mode 100644
index 00000000000..2228c203781
--- /dev/null
+++ b/components/script/dom/webidls/Worker.webidl
@@ -0,0 +1,20 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#abstractworker
+[NoInterfaceObject/*, Exposed=Window,Worker*/]
+interface AbstractWorker {
+ // attribute EventHandler onerror;
+};
+
+// http://www.whatwg.org/html/#worker
+[Constructor(DOMString scriptURL)/*, Exposed=Window,Worker*/]
+interface Worker : EventTarget {
+ //void terminate();
+
+ void postMessage(any message/*, optional sequence<Transferable> transfer*/);
+ attribute EventHandler onmessage;
+};
+Worker implements AbstractWorker;
diff --git a/components/script/dom/webidls/WorkerGlobalScope.webidl b/components/script/dom/webidls/WorkerGlobalScope.webidl
new file mode 100644
index 00000000000..9c50682b056
--- /dev/null
+++ b/components/script/dom/webidls/WorkerGlobalScope.webidl
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#workerglobalscope
+//[Exposed=Worker]
+interface WorkerGlobalScope : EventTarget {
+ readonly attribute WorkerGlobalScope self;
+ readonly attribute WorkerLocation location;
+
+ //void close();
+ // attribute OnErrorEventHandler onerror;
+ // attribute EventHandler onlanguagechange;
+ // attribute EventHandler onoffline;
+ // attribute EventHandler ononline;
+};
+
+// http://www.whatwg.org/html/#WorkerGlobalScope-partial
+//[Exposed=Worker]
+partial interface WorkerGlobalScope { // not obsolete
+ [Throws]
+ void importScripts(DOMString... urls);
+ readonly attribute WorkerNavigator navigator;
+};
+//WorkerGlobalScope implements WindowTimers;
+//WorkerGlobalScope implements WindowBase64;
+
+// Proprietary
+partial interface WorkerGlobalScope {
+ [Replaceable]
+ readonly attribute Console console;
+};
diff --git a/components/script/dom/webidls/WorkerLocation.webidl b/components/script/dom/webidls/WorkerLocation.webidl
new file mode 100644
index 00000000000..04d4c8e5cc7
--- /dev/null
+++ b/components/script/dom/webidls/WorkerLocation.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#worker-locations
+//[Exposed=Worker]
+interface WorkerLocation { };
+WorkerLocation implements URLUtilsReadOnly;
diff --git a/components/script/dom/webidls/WorkerNavigator.webidl b/components/script/dom/webidls/WorkerNavigator.webidl
new file mode 100644
index 00000000000..aa8e19342e4
--- /dev/null
+++ b/components/script/dom/webidls/WorkerNavigator.webidl
@@ -0,0 +1,11 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://www.whatwg.org/html/#workernavigator
+//[Exposed=Worker]
+interface WorkerNavigator {};
+WorkerNavigator implements NavigatorID;
+//WorkerNavigator implements NavigatorLanguage;
+//WorkerNavigator implements NavigatorOnLine;
diff --git a/components/script/dom/webidls/XMLHttpRequest.webidl b/components/script/dom/webidls/XMLHttpRequest.webidl
new file mode 100644
index 00000000000..ba100ca23ad
--- /dev/null
+++ b/components/script/dom/webidls/XMLHttpRequest.webidl
@@ -0,0 +1,72 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://xhr.spec.whatwg.org/#interface-xmlhttprequest
+ *
+ * To the extent possible under law, the editor has waived all copyright
+ * and related or neighboring rights to this work. In addition, as of 1 May 2014,
+ * the editor has made this specification available under the Open Web Foundation
+ * Agreement Version 1.0, which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+// http://fetch.spec.whatwg.org/#fetchbodyinit
+typedef (/*ArrayBuffer or ArrayBufferView or Blob or FormData or */DOMString or URLSearchParams) FetchBodyInit;
+
+enum XMLHttpRequestResponseType {
+ "",
+ "arraybuffer",
+ "blob",
+ "document",
+ "json",
+ "text"
+};
+
+[Constructor/*,
+ Exposed=Window,Worker*/]
+interface XMLHttpRequest : XMLHttpRequestEventTarget {
+ // event handler
+ attribute EventHandler onreadystatechange;
+
+ // states
+ const unsigned short UNSENT = 0;
+ const unsigned short OPENED = 1;
+ const unsigned short HEADERS_RECEIVED = 2;
+ const unsigned short LOADING = 3;
+ const unsigned short DONE = 4;
+
+ readonly attribute unsigned short readyState;
+
+ // request
+ [Throws]
+ void open(ByteString method, /* [EnsureUTF16] */ DOMString url);
+ [Throws]
+ void open(ByteString method, /* [EnsureUTF16] */ DOMString url, boolean async, optional /* [EnsureUTF16] */ DOMString? username = null, optional /* [EnsureUTF16] */ DOMString? password = null);
+
+ [Throws]
+ void setRequestHeader(ByteString name, ByteString value);
+ [SetterThrows]
+ attribute unsigned long timeout;
+ attribute boolean withCredentials;
+ readonly attribute XMLHttpRequestUpload upload;
+ [Throws]
+ void send(optional /*Document or*/ FetchBodyInit? data = null);
+ void abort();
+
+ // response
+ readonly attribute DOMString responseURL;
+ readonly attribute unsigned short status;
+ readonly attribute ByteString statusText;
+ ByteString? getResponseHeader(ByteString name);
+ ByteString getAllResponseHeaders();
+ // void overrideMimeType(DOMString mime);
+ [SetterThrows]
+ attribute XMLHttpRequestResponseType responseType;
+ readonly attribute any response;
+ [Throws]
+ readonly attribute DOMString responseText;
+ /*[Exposed=Window]*/ readonly attribute Document? responseXML;
+};
diff --git a/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl b/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl
new file mode 100644
index 00000000000..0d772edca0b
--- /dev/null
+++ b/components/script/dom/webidls/XMLHttpRequestEventTarget.webidl
@@ -0,0 +1,26 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://xhr.spec.whatwg.org/#interface-xmlhttprequest
+ *
+ * To the extent possible under law, the editor has waived all copyright
+ * and related or neighboring rights to this work. In addition, as of 1 May 2014,
+ * the editor has made this specification available under the Open Web Foundation
+ * Agreement Version 1.0, which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+[NoInterfaceObject]
+interface XMLHttpRequestEventTarget : EventTarget {
+ // event handlers
+ attribute EventHandler onloadstart;
+ attribute EventHandler onprogress;
+ attribute EventHandler onabort;
+ attribute EventHandler onerror;
+ attribute EventHandler onload;
+ attribute EventHandler ontimeout;
+ attribute EventHandler onloadend;
+};
diff --git a/components/script/dom/webidls/XMLHttpRequestUpload.webidl b/components/script/dom/webidls/XMLHttpRequestUpload.webidl
new file mode 100644
index 00000000000..9ff8b4cc8e6
--- /dev/null
+++ b/components/script/dom/webidls/XMLHttpRequestUpload.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * http://xhr.spec.whatwg.org/#interface-xmlhttprequest
+ *
+ * To the extent possible under law, the editor has waived all copyright
+ * and related or neighboring rights to this work. In addition, as of 1 May 2014,
+ * the editor has made this specification available under the Open Web Foundation
+ * Agreement Version 1.0, which is available at
+ * http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0.
+ */
+
+//[Exposed=Window,Worker]
+interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
+};
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
new file mode 100644
index 00000000000..23b6c71e029
--- /dev/null
+++ b/components/script/dom/window.rs
@@ -0,0 +1,513 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventHandlerBinding::{OnErrorEventHandlerNonNull, EventHandlerNonNull};
+use dom::bindings::codegen::Bindings::WindowBinding;
+use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::error::{Fallible, InvalidCharacter};
+use dom::bindings::global;
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable};
+use dom::bindings::trace::{Traceable, Untraceable};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::browsercontext::BrowserContext;
+use dom::console::Console;
+use dom::document::Document;
+use dom::eventtarget::{EventTarget, WindowTypeId, EventTargetHelpers};
+use dom::location::Location;
+use dom::navigator::Navigator;
+use dom::performance::Performance;
+use dom::screen::Screen;
+use layout_interface::{ReflowForDisplay, DocumentDamageLevel};
+use page::Page;
+use script_task::{ExitWindowMsg, FireTimerMsg, ScriptChan, TriggerLoadMsg, TriggerFragmentMsg};
+use script_traits::ScriptControlChan;
+
+use servo_msg::compositor_msg::ScriptListener;
+use servo_net::image_cache_task::ImageCacheTask;
+use servo_util::str::{DOMString,HTML_SPACE_CHARACTERS};
+use servo_util::task::{spawn_named};
+
+use js::jsapi::JS_CallFunctionValue;
+use js::jsapi::JSContext;
+use js::jsapi::{JS_GC, JS_GetRuntime};
+use js::jsval::JSVal;
+use js::jsval::NullValue;
+use js::rust::with_compartment;
+use url::{Url, UrlParser};
+
+use serialize::base64::{FromBase64, ToBase64, STANDARD};
+use std::collections::hashmap::HashMap;
+use std::cell::{Cell, RefCell};
+use std::cmp;
+use std::comm::{channel, Sender};
+use std::comm::Select;
+use std::hash::{Hash, sip};
+use std::io::timer::Timer;
+use std::ptr;
+use std::rc::Rc;
+use time;
+
+#[deriving(PartialEq, Encodable, Eq)]
+pub struct TimerId(i32);
+
+#[deriving(Encodable)]
+pub struct TimerHandle {
+ handle: TimerId,
+ pub data: TimerData,
+ cancel_chan: Untraceable<Option<Sender<()>>>,
+}
+
+impl Hash for TimerId {
+ fn hash(&self, state: &mut sip::SipState) {
+ let TimerId(id) = *self;
+ id.hash(state);
+ }
+}
+
+impl TimerHandle {
+ fn cancel(&mut self) {
+ self.cancel_chan.as_ref().map(|chan| chan.send_opt(()).ok());
+ }
+}
+
+#[deriving(Encodable)]
+pub struct Window {
+ eventtarget: EventTarget,
+ pub script_chan: ScriptChan,
+ control_chan: ScriptControlChan,
+ console: Cell<Option<JS<Console>>>,
+ location: Cell<Option<JS<Location>>>,
+ navigator: Cell<Option<JS<Navigator>>>,
+ pub image_cache_task: ImageCacheTask,
+ pub active_timers: Traceable<RefCell<HashMap<TimerId, TimerHandle>>>,
+ next_timer_handle: Traceable<Cell<i32>>,
+ compositor: Untraceable<Box<ScriptListener>>,
+ pub browser_context: Traceable<RefCell<Option<BrowserContext>>>,
+ pub page: Rc<Page>,
+ performance: Cell<Option<JS<Performance>>>,
+ pub navigationStart: u64,
+ pub navigationStartPrecise: f64,
+ screen: Cell<Option<JS<Screen>>>,
+}
+
+impl Window {
+ pub fn get_cx(&self) -> *mut JSContext {
+ let js_info = self.page().js_info();
+ (**js_info.get_ref().js_context).ptr
+ }
+
+ pub fn page<'a>(&'a self) -> &'a Page {
+ &*self.page
+ }
+ pub fn get_url(&self) -> Url {
+ self.page().get_url()
+ }
+}
+
+#[unsafe_destructor]
+impl Drop for Window {
+ fn drop(&mut self) {
+ for (_, timer_handle) in self.active_timers.borrow_mut().mut_iter() {
+ timer_handle.cancel();
+ }
+ }
+}
+
+// Holder for the various JS values associated with setTimeout
+// (ie. function value to invoke and all arguments to pass
+// to the function when calling it)
+#[deriving(Encodable)]
+pub struct TimerData {
+ pub is_interval: bool,
+ pub funval: Traceable<JSVal>,
+}
+
+impl<'a> WindowMethods for JSRef<'a, Window> {
+ fn Alert(&self, s: DOMString) {
+ // Right now, just print to the console
+ println!("ALERT: {:s}", s);
+ }
+
+ fn Close(&self) {
+ let ScriptChan(ref chan) = self.script_chan;
+ chan.send(ExitWindowMsg(self.page.id.clone()));
+ }
+
+ fn Document(&self) -> Temporary<Document> {
+ let frame = self.page().frame();
+ Temporary::new(frame.get_ref().document.clone())
+ }
+
+ fn Location(&self) -> Temporary<Location> {
+ if self.location.get().is_none() {
+ let page = self.deref().page.clone();
+ let location = Location::new(self, page);
+ self.location.assign(Some(location));
+ }
+ Temporary::new(self.location.get().get_ref().clone())
+ }
+
+ fn Console(&self) -> Temporary<Console> {
+ if self.console.get().is_none() {
+ let console = Console::new(&global::Window(*self));
+ self.console.assign(Some(console));
+ }
+ Temporary::new(self.console.get().get_ref().clone())
+ }
+
+ fn Navigator(&self) -> Temporary<Navigator> {
+ if self.navigator.get().is_none() {
+ let navigator = Navigator::new(self);
+ self.navigator.assign(Some(navigator));
+ }
+ Temporary::new(self.navigator.get().get_ref().clone())
+ }
+
+ fn SetTimeout(&self, _cx: *mut JSContext, callback: JSVal, timeout: i32) -> i32 {
+ self.set_timeout_or_interval(callback, timeout, false)
+ }
+
+ fn ClearTimeout(&self, handle: i32) {
+ let mut timers = self.active_timers.deref().borrow_mut();
+ let mut timer_handle = timers.pop(&TimerId(handle));
+ match timer_handle {
+ Some(ref mut handle) => handle.cancel(),
+ None => { }
+ }
+ timers.remove(&TimerId(handle));
+ }
+
+ fn SetInterval(&self, _cx: *mut JSContext, callback: JSVal, timeout: i32) -> i32 {
+ self.set_timeout_or_interval(callback, timeout, true)
+ }
+
+ fn ClearInterval(&self, handle: i32) {
+ self.ClearTimeout(handle);
+ }
+
+ fn Window(&self) -> Temporary<Window> {
+ Temporary::from_rooted(self)
+ }
+
+ fn Self(&self) -> Temporary<Window> {
+ self.Window()
+ }
+
+ // http://www.whatwg.org/html/#dom-frames
+ fn Frames(&self) -> Temporary<Window> {
+ self.Window()
+ }
+
+ fn Parent(&self) -> Temporary<Window> {
+ //TODO - Once we support iframes correctly this needs to return the parent frame
+ self.Window()
+ }
+
+ fn Performance(&self) -> Temporary<Performance> {
+ if self.performance.get().is_none() {
+ let performance = Performance::new(self);
+ self.performance.assign(Some(performance));
+ }
+ Temporary::new(self.performance.get().get_ref().clone())
+ }
+
+ fn GetOnclick(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("click")
+ }
+
+ fn SetOnclick(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("click", listener)
+ }
+
+ fn GetOnload(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("load")
+ }
+
+ fn SetOnload(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("load", listener)
+ }
+
+ fn GetOnunload(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("unload")
+ }
+
+ fn SetOnunload(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("unload", listener)
+ }
+
+ fn GetOnerror(&self) -> Option<OnErrorEventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("error")
+ }
+
+ fn SetOnerror(&self, listener: Option<OnErrorEventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("error", listener)
+ }
+
+ fn Screen(&self) -> Temporary<Screen> {
+ if self.screen.get().is_none() {
+ let screen = Screen::new(self);
+ self.screen.assign(Some(screen));
+ }
+ Temporary::new(self.screen.get().get_ref().clone())
+ }
+
+ fn Debug(&self, message: DOMString) {
+ debug!("{:s}", message);
+ }
+
+ fn Gc(&self) {
+ unsafe {
+ JS_GC(JS_GetRuntime(self.get_cx()));
+ }
+ }
+
+ // http://www.whatwg.org/html/#atob
+ fn Btoa(&self, btoa: DOMString) -> Fallible<DOMString> {
+ let input = btoa.as_slice();
+ // "The btoa() method must throw an InvalidCharacterError exception if
+ // the method's first argument contains any character whose code point
+ // is greater than U+00FF."
+ if input.chars().any(|c: char| c > '\u00FF') {
+ Err(InvalidCharacter)
+ } else {
+ // "Otherwise, the user agent must convert that argument to a
+ // sequence of octets whose nth octet is the eight-bit
+ // representation of the code point of the nth character of
+ // the argument,"
+ let octets = input.chars().map(|c: char| c as u8).collect::<Vec<u8>>();
+
+ // "and then must apply the base64 algorithm to that sequence of
+ // octets, and return the result. [RFC4648]"
+ Ok(octets.as_slice().to_base64(STANDARD))
+ }
+ }
+
+ // http://www.whatwg.org/html/#atob
+ fn Atob(&self, atob: DOMString) -> Fallible<DOMString> {
+ // "Let input be the string being parsed."
+ let mut input = atob.as_slice();
+
+ // "Remove all space characters from input."
+ // serialize::base64::from_base64 ignores \r and \n,
+ // but it treats the other space characters as
+ // invalid input.
+ fn is_html_space(c: char) -> bool {
+ HTML_SPACE_CHARACTERS.iter().any(|&m| m == c)
+ }
+ let without_spaces = input.chars()
+ .filter(|&c| ! is_html_space(c))
+ .collect::<String>();
+ input = without_spaces.as_slice();
+
+ // "If the length of input divides by 4 leaving no remainder, then:
+ // if input ends with one or two U+003D EQUALS SIGN (=) characters,
+ // remove them from input."
+ if input.len() % 4 == 0 {
+ if input.ends_with("==") {
+ input = input.slice_to(input.len() - 2)
+ } else if input.ends_with("=") {
+ input = input.slice_to(input.len() - 1)
+ }
+ }
+
+ // "If the length of input divides by 4 leaving a remainder of 1,
+ // throw an InvalidCharacterError exception and abort these steps."
+ if input.len() % 4 == 1 {
+ return Err(InvalidCharacter)
+ }
+
+ // "If input contains a character that is not in the following list of
+ // characters and character ranges, throw an InvalidCharacterError
+ // exception and abort these steps:
+ //
+ // U+002B PLUS SIGN (+)
+ // U+002F SOLIDUS (/)
+ // Alphanumeric ASCII characters"
+ if input.chars()
+ .find(|&c| !(c == '+' || c == '/' || c.is_alphanumeric()))
+ .is_some() {
+ return Err(InvalidCharacter)
+ }
+
+ match input.from_base64() {
+ Ok(data) => Ok(data.iter().map(|&b| b as char).collect::<String>()),
+ Err(..) => Err(InvalidCharacter)
+ }
+ }
+}
+
+impl Reflectable for Window {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
+
+pub trait WindowHelpers {
+ fn damage_and_reflow(&self, damage: DocumentDamageLevel);
+ fn wait_until_safe_to_modify_dom(&self);
+ fn init_browser_context(&self, doc: &JSRef<Document>);
+ fn load_url(&self, href: DOMString);
+ fn handle_fire_timer(&self, timer_id: TimerId, cx: *mut JSContext);
+}
+
+trait PrivateWindowHelpers {
+ fn set_timeout_or_interval(&self, callback: JSVal, timeout: i32, is_interval: bool) -> i32;
+}
+
+impl<'a> WindowHelpers for JSRef<'a, Window> {
+ fn damage_and_reflow(&self, damage: DocumentDamageLevel) {
+ // FIXME This should probably be ReflowForQuery, not Display. All queries currently
+ // currently rely on the display list, which means we can't destroy it by
+ // doing a query reflow.
+ self.page().damage(damage);
+ self.page().reflow(ReflowForDisplay, self.control_chan.clone(), *self.compositor);
+ }
+
+ fn wait_until_safe_to_modify_dom(&self) {
+ // FIXME: This disables concurrent layout while we are modifying the DOM, since
+ // our current architecture is entirely unsafe in the presence of races.
+ self.page().join_layout();
+ }
+
+ fn init_browser_context(&self, doc: &JSRef<Document>) {
+ *self.browser_context.deref().borrow_mut() = Some(BrowserContext::new(doc));
+ }
+
+ /// Commence a new URL load which will either replace this window or scroll to a fragment.
+ fn load_url(&self, href: DOMString) {
+ let base_url = self.page().get_url();
+ debug!("current page url is {:?}", base_url);
+ let url = UrlParser::new().base_url(&base_url).parse(href.as_slice());
+ // FIXME: handle URL parse errors more gracefully.
+ let url = url.unwrap();
+ let ScriptChan(ref script_chan) = self.script_chan;
+ if href.as_slice().starts_with("#") {
+ script_chan.send(TriggerFragmentMsg(self.page.id, url));
+ } else {
+ script_chan.send(TriggerLoadMsg(self.page.id, url));
+ }
+ }
+
+ fn handle_fire_timer(&self, timer_id: TimerId, cx: *mut JSContext) {
+ let this_value = self.reflector().get_jsobject();
+
+ let data = match self.active_timers.deref().borrow().find(&timer_id) {
+ None => return,
+ Some(timer_handle) => timer_handle.data,
+ };
+
+ // TODO: Support extra arguments. This requires passing a `*JSVal` array as `argv`.
+ with_compartment(cx, this_value, || {
+ let mut rval = NullValue();
+ unsafe {
+ JS_CallFunctionValue(cx, this_value, *data.funval,
+ 0, ptr::mut_null(), &mut rval);
+ }
+ });
+
+ if !data.is_interval {
+ self.active_timers.deref().borrow_mut().remove(&timer_id);
+ }
+ }
+}
+
+impl<'a> PrivateWindowHelpers for JSRef<'a, Window> {
+ fn set_timeout_or_interval(&self, callback: JSVal, timeout: i32, is_interval: bool) -> i32 {
+ let timeout = cmp::max(0, timeout) as u64;
+ let handle = self.next_timer_handle.deref().get();
+ self.next_timer_handle.deref().set(handle + 1);
+
+ // Post a delayed message to the per-window timer task; it will dispatch it
+ // to the relevant script handler that will deal with it.
+ let tm = Timer::new().unwrap();
+ let (cancel_chan, cancel_port) = channel();
+ let chan = self.script_chan.clone();
+ let page_id = self.page.id.clone();
+ let spawn_name = if is_interval {
+ "Window:SetInterval"
+ } else {
+ "Window:SetTimeout"
+ };
+ spawn_named(spawn_name, proc() {
+ let mut tm = tm;
+ let timeout_port = if is_interval {
+ tm.periodic(timeout)
+ } else {
+ tm.oneshot(timeout)
+ };
+ let cancel_port = cancel_port;
+
+ let select = Select::new();
+ let mut timeout_handle = select.handle(&timeout_port);
+ unsafe { timeout_handle.add() };
+ let mut cancel_handle = select.handle(&cancel_port);
+ unsafe { cancel_handle.add() };
+
+ loop {
+ let id = select.wait();
+ if id == timeout_handle.id() {
+ timeout_port.recv();
+ let ScriptChan(ref chan) = chan;
+ chan.send(FireTimerMsg(page_id, TimerId(handle)));
+ if !is_interval {
+ break;
+ }
+ } else if id == cancel_handle.id() {
+ break;
+ }
+ }
+ });
+ let timer_id = TimerId(handle);
+ let timer = TimerHandle {
+ handle: timer_id,
+ cancel_chan: Untraceable::new(Some(cancel_chan)),
+ data: TimerData {
+ is_interval: is_interval,
+ funval: Traceable::new(callback),
+ }
+ };
+ self.active_timers.deref().borrow_mut().insert(timer_id, timer);
+ handle
+ }
+}
+
+impl Window {
+ pub fn new(cx: *mut JSContext,
+ page: Rc<Page>,
+ script_chan: ScriptChan,
+ control_chan: ScriptControlChan,
+ compositor: Box<ScriptListener>,
+ image_cache_task: ImageCacheTask)
+ -> Temporary<Window> {
+ let win = box Window {
+ eventtarget: EventTarget::new_inherited(WindowTypeId),
+ script_chan: script_chan,
+ control_chan: control_chan,
+ console: Cell::new(None),
+ compositor: Untraceable::new(compositor),
+ page: page,
+ location: Cell::new(None),
+ navigator: Cell::new(None),
+ image_cache_task: image_cache_task,
+ active_timers: Traceable::new(RefCell::new(HashMap::new())),
+ next_timer_handle: Traceable::new(Cell::new(0)),
+ browser_context: Traceable::new(RefCell::new(None)),
+ performance: Cell::new(None),
+ navigationStart: time::get_time().sec as u64,
+ navigationStartPrecise: time::precise_time_s(),
+ screen: Cell::new(None),
+ };
+
+ WindowBinding::Wrap(cx, win)
+ }
+}
diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs
new file mode 100644
index 00000000000..3119b4c96f2
--- /dev/null
+++ b/components/script/dom/worker.rs
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::WorkerBinding;
+use dom::bindings::codegen::Bindings::WorkerBinding::WorkerMethods;
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::error::{Fallible, Syntax};
+use dom::bindings::global::{GlobalRef, GlobalField};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
+use dom::eventtarget::{EventTarget, EventTargetHelpers, WorkerTypeId};
+use dom::messageevent::MessageEvent;
+use script_task::{ScriptChan, DOMMessage};
+
+use servo_util::str::DOMString;
+
+use js::glue::JS_STRUCTURED_CLONE_VERSION;
+use js::jsapi::{JSContext, JS_AddObjectRoot, JS_RemoveObjectRoot};
+use js::jsapi::{JS_ReadStructuredClone, JS_WriteStructuredClone};
+use js::jsval::{JSVal, UndefinedValue};
+use url::UrlParser;
+
+use libc::{c_void, size_t};
+use std::cell::Cell;
+use std::ptr;
+
+pub struct TrustedWorkerAddress(pub *const c_void);
+
+#[deriving(Encodable)]
+pub struct Worker {
+ eventtarget: EventTarget,
+ refcount: Cell<uint>,
+ global: GlobalField,
+ /// Sender to the Receiver associated with the DedicatedWorkerGlobalScope
+ /// this Worker created.
+ sender: ScriptChan,
+}
+
+impl Worker {
+ pub fn new_inherited(global: &GlobalRef, sender: ScriptChan) -> Worker {
+ Worker {
+ eventtarget: EventTarget::new_inherited(WorkerTypeId),
+ refcount: Cell::new(0),
+ global: GlobalField::from_rooted(global),
+ sender: sender,
+ }
+ }
+
+ pub fn new(global: &GlobalRef, sender: ScriptChan) -> Temporary<Worker> {
+ reflect_dom_object(box Worker::new_inherited(global, sender),
+ global,
+ WorkerBinding::Wrap)
+ }
+
+ // http://www.whatwg.org/html/#dom-worker
+ pub fn Constructor(global: &GlobalRef, scriptURL: DOMString) -> Fallible<Temporary<Worker>> {
+ // Step 2-4.
+ let worker_url = match UrlParser::new().base_url(&global.get_url())
+ .parse(scriptURL.as_slice()) {
+ Ok(url) => url,
+ Err(_) => return Err(Syntax),
+ };
+
+ let resource_task = global.resource_task();
+ let (receiver, sender) = ScriptChan::new();
+
+ let worker = Worker::new(global, sender.clone()).root();
+ let worker_ref = worker.addref();
+
+ DedicatedWorkerGlobalScope::run_worker_scope(
+ worker_url, worker_ref, resource_task, global.script_chan().clone(),
+ sender, receiver);
+
+ Ok(Temporary::from_rooted(&*worker))
+ }
+
+ pub fn handle_message(address: TrustedWorkerAddress,
+ data: *mut u64, nbytes: size_t) {
+ let worker = unsafe { JS::from_trusted_worker_address(address).root() };
+
+ let global = worker.global.root();
+
+ let mut message = UndefinedValue();
+ unsafe {
+ assert!(JS_ReadStructuredClone(
+ global.root_ref().get_cx(), data as *const u64, nbytes,
+ JS_STRUCTURED_CLONE_VERSION, &mut message,
+ ptr::null(), ptr::mut_null()) != 0);
+ }
+
+ let target: &JSRef<EventTarget> = EventTargetCast::from_ref(&*worker);
+ MessageEvent::dispatch_jsval(target, &global.root_ref(), message);
+ }
+}
+
+impl Worker {
+ // Creates a trusted address to the object, and roots it. Always pair this with a release()
+ pub fn addref(&self) -> TrustedWorkerAddress {
+ let refcount = self.refcount.get();
+ if refcount == 0 {
+ let cx = self.global.root().root_ref().get_cx();
+ unsafe {
+ JS_AddObjectRoot(cx, self.reflector().rootable());
+ }
+ }
+ self.refcount.set(refcount + 1);
+ TrustedWorkerAddress(self as *const Worker as *const c_void)
+ }
+
+ pub fn release(&self) {
+ let refcount = self.refcount.get();
+ assert!(refcount > 0)
+ self.refcount.set(refcount - 1);
+ if refcount == 1 {
+ let cx = self.global.root().root_ref().get_cx();
+ unsafe {
+ JS_RemoveObjectRoot(cx, self.reflector().rootable());
+ }
+ }
+ }
+
+ pub fn handle_release(address: TrustedWorkerAddress) {
+ let worker = unsafe { JS::from_trusted_worker_address(address).root() };
+ worker.release();
+ }
+}
+
+impl<'a> WorkerMethods for JSRef<'a, Worker> {
+ fn PostMessage(&self, cx: *mut JSContext, message: JSVal) {
+ let mut data = ptr::mut_null();
+ let mut nbytes = 0;
+ unsafe {
+ assert!(JS_WriteStructuredClone(cx, message, &mut data, &mut nbytes,
+ ptr::null(), ptr::mut_null()) != 0);
+ }
+
+ self.addref();
+ let ScriptChan(ref sender) = self.sender;
+ sender.send(DOMMessage(data, nbytes));
+ }
+
+ fn GetOnmessage(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("message")
+ }
+
+ fn SetOnmessage(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("message", listener)
+ }
+}
+
+impl Reflectable for Worker {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs
new file mode 100644
index 00000000000..dcf205cf3a9
--- /dev/null
+++ b/components/script/dom/workerglobalscope.rs
@@ -0,0 +1,145 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
+use dom::bindings::error::{ErrorResult, Syntax, Network, FailureUnknown};
+use dom::bindings::trace::Untraceable;
+use dom::bindings::global;
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalSettable};
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::console::Console;
+use dom::eventtarget::{EventTarget, WorkerGlobalScopeTypeId};
+use dom::workerlocation::WorkerLocation;
+use dom::workernavigator::WorkerNavigator;
+use script_task::ScriptChan;
+
+use servo_net::resource_task::{ResourceTask, load_whole_resource};
+use servo_util::str::DOMString;
+
+use js::jsapi::JSContext;
+use js::rust::Cx;
+
+use std::cell::Cell;
+use std::rc::Rc;
+use url::{Url, UrlParser};
+
+#[deriving(PartialEq,Encodable)]
+pub enum WorkerGlobalScopeId {
+ DedicatedGlobalScope,
+}
+
+#[deriving(Encodable)]
+pub struct WorkerGlobalScope {
+ pub eventtarget: EventTarget,
+ worker_url: Untraceable<Url>,
+ js_context: Untraceable<Rc<Cx>>,
+ resource_task: Untraceable<ResourceTask>,
+ script_chan: ScriptChan,
+ location: Cell<Option<JS<WorkerLocation>>>,
+ navigator: Cell<Option<JS<WorkerNavigator>>>,
+ console: Cell<Option<JS<Console>>>,
+}
+
+impl WorkerGlobalScope {
+ pub fn new_inherited(type_id: WorkerGlobalScopeId,
+ worker_url: Url,
+ cx: Rc<Cx>,
+ resource_task: ResourceTask,
+ script_chan: ScriptChan) -> WorkerGlobalScope {
+ WorkerGlobalScope {
+ eventtarget: EventTarget::new_inherited(WorkerGlobalScopeTypeId(type_id)),
+ worker_url: Untraceable::new(worker_url),
+ js_context: Untraceable::new(cx),
+ resource_task: Untraceable::new(resource_task),
+ script_chan: script_chan,
+ location: Cell::new(None),
+ navigator: Cell::new(None),
+ console: Cell::new(None),
+ }
+ }
+
+ pub fn get_cx(&self) -> *mut JSContext {
+ self.js_context.ptr
+ }
+
+ pub fn resource_task<'a>(&'a self) -> &'a ResourceTask {
+ &*self.resource_task
+ }
+
+ pub fn get_url<'a>(&'a self) -> &'a Url {
+ &*self.worker_url
+ }
+
+ pub fn script_chan<'a>(&'a self) -> &'a ScriptChan {
+ &self.script_chan
+ }
+}
+
+impl<'a> WorkerGlobalScopeMethods for JSRef<'a, WorkerGlobalScope> {
+ fn Self(&self) -> Temporary<WorkerGlobalScope> {
+ Temporary::from_rooted(self)
+ }
+
+ fn Location(&self) -> Temporary<WorkerLocation> {
+ if self.location.get().is_none() {
+ let location = WorkerLocation::new(self, self.worker_url.clone());
+ self.location.assign(Some(location));
+ }
+ Temporary::new(self.location.get().get_ref().clone())
+ }
+
+ fn ImportScripts(&self, url_strings: Vec<DOMString>) -> ErrorResult {
+ let mut urls = Vec::with_capacity(url_strings.len());
+ for url in url_strings.move_iter() {
+ let url = UrlParser::new().base_url(&*self.worker_url)
+ .parse(url.as_slice());
+ match url {
+ Ok(url) => urls.push(url),
+ Err(_) => return Err(Syntax),
+ };
+ }
+
+ for url in urls.move_iter() {
+ let (url, source) = match load_whole_resource(&*self.resource_task, url) {
+ Err(_) => return Err(Network),
+ Ok((metadata, bytes)) => {
+ (metadata.final_url, String::from_utf8(bytes).unwrap())
+ }
+ };
+
+ match self.js_context.evaluate_script(
+ self.reflector().get_jsobject(), source, url.serialize(), 1) {
+ Ok(_) => (),
+ Err(_) => {
+ println!("evaluate_script failed");
+ return Err(FailureUnknown);
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ fn Navigator(&self) -> Temporary<WorkerNavigator> {
+ if self.navigator.get().is_none() {
+ let navigator = WorkerNavigator::new(self);
+ self.navigator.assign(Some(navigator));
+ }
+ Temporary::new(self.navigator.get().get_ref().clone())
+ }
+
+ fn Console(&self) -> Temporary<Console> {
+ if self.console.get().is_none() {
+ let console = Console::new(&global::Worker(*self));
+ self.console.assign(Some(console));
+ }
+ Temporary::new(self.console.get().get_ref().clone())
+ }
+}
+
+impl Reflectable for WorkerGlobalScope {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
diff --git a/components/script/dom/workerlocation.rs b/components/script/dom/workerlocation.rs
new file mode 100644
index 00000000000..0d32c211554
--- /dev/null
+++ b/components/script/dom/workerlocation.rs
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::WorkerLocationBinding;
+use dom::bindings::codegen::Bindings::WorkerLocationBinding::WorkerLocationMethods;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::global::Worker;
+use dom::bindings::trace::Untraceable;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::workerglobalscope::WorkerGlobalScope;
+
+use servo_util::str::DOMString;
+
+use url::Url;
+
+#[deriving(Encodable)]
+pub struct WorkerLocation {
+ reflector_: Reflector,
+ url: Untraceable<Url>,
+}
+
+impl WorkerLocation {
+ pub fn new_inherited(url: Url) -> WorkerLocation {
+ WorkerLocation {
+ reflector_: Reflector::new(),
+ url: Untraceable::new(url),
+ }
+ }
+
+ pub fn new(global: &JSRef<WorkerGlobalScope>, url: Url) -> Temporary<WorkerLocation> {
+ reflect_dom_object(box WorkerLocation::new_inherited(url),
+ &Worker(*global),
+ WorkerLocationBinding::Wrap)
+ }
+}
+
+impl<'a> WorkerLocationMethods for JSRef<'a, WorkerLocation> {
+ fn Href(&self) -> DOMString {
+ self.url.serialize()
+ }
+
+ fn Search(&self) -> DOMString {
+ match self.url.query {
+ None => "".to_string(),
+ Some(ref query) if query.as_slice() == "" => "".to_string(),
+ Some(ref query) => "?".to_string().append(query.as_slice())
+ }
+ }
+
+ fn Hash(&self) -> DOMString {
+ match self.url.fragment {
+ None => "".to_string(),
+ Some(ref hash) if hash.as_slice() == "" => "".to_string(),
+ Some(ref hash) => "#".to_string().append(hash.as_slice())
+ }
+ }
+}
+
+impl Reflectable for WorkerLocation {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/workernavigator.rs b/components/script/dom/workernavigator.rs
new file mode 100644
index 00000000000..e732696617d
--- /dev/null
+++ b/components/script/dom/workernavigator.rs
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::WorkerNavigatorBinding;
+use dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods;
+use dom::bindings::global::Worker;
+use dom::bindings::js::{JSRef, Temporary};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::workerglobalscope::WorkerGlobalScope;
+use servo_util::str::DOMString;
+
+#[deriving(Encodable)]
+pub struct WorkerNavigator {
+ reflector_: Reflector,
+}
+
+impl WorkerNavigator {
+ pub fn new_inherited() -> WorkerNavigator {
+ WorkerNavigator {
+ reflector_: Reflector::new(),
+ }
+ }
+
+ pub fn new(global: &JSRef<WorkerGlobalScope>) -> Temporary<WorkerNavigator> {
+ reflect_dom_object(box WorkerNavigator::new_inherited(),
+ &Worker(*global),
+ WorkerNavigatorBinding::Wrap)
+ }
+}
+
+impl<'a> WorkerNavigatorMethods for JSRef<'a, WorkerNavigator> {
+ fn Product(&self) -> DOMString {
+ "Gecko".to_string()
+ }
+
+ fn TaintEnabled(&self) -> bool {
+ false
+ }
+
+ fn AppName(&self) -> DOMString {
+ "Netscape".to_string() // Like Gecko/Webkit
+ }
+
+ fn AppCodeName(&self) -> DOMString {
+ "Mozilla".to_string()
+ }
+
+ fn Platform(&self) -> DOMString {
+ "".to_string()
+ }
+}
+
+impl Reflectable for WorkerNavigator {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ &self.reflector_
+ }
+}
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
new file mode 100644
index 00000000000..b2bfd64eb4e
--- /dev/null
+++ b/components/script/dom/xmlhttprequest.rs
@@ -0,0 +1,970 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::XMLHttpRequestBinding;
+use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods;
+use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
+use dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseTypeValues::{_empty, Document, Json, Text};
+use dom::bindings::codegen::InheritTypes::{EventCast, EventTargetCast, XMLHttpRequestDerived};
+use dom::bindings::conversions::ToJSValConvertible;
+use dom::bindings::error::{Error, ErrorResult, Fallible, InvalidState, InvalidAccess};
+use dom::bindings::error::{Network, Syntax, Security, Abort, Timeout};
+use dom::bindings::global::{GlobalField, GlobalRef, WorkerField};
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootedRootable};
+use dom::bindings::str::ByteString;
+use dom::bindings::trace::{Traceable, Untraceable};
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::document::Document;
+use dom::event::Event;
+use dom::eventtarget::{EventTarget, EventTargetHelpers, XMLHttpRequestTargetTypeId};
+use dom::progressevent::ProgressEvent;
+use dom::urlsearchparams::URLSearchParamsHelpers;
+use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
+use dom::xmlhttprequestupload::XMLHttpRequestUpload;
+
+use encoding::all::UTF_8;
+use encoding::label::encoding_from_whatwg_label;
+use encoding::types::{DecodeReplace, Encoding, EncodingRef, EncodeReplace};
+
+use ResponseHeaderCollection = http::headers::response::HeaderCollection;
+use RequestHeaderCollection = http::headers::request::HeaderCollection;
+use http::headers::content_type::MediaType;
+use http::headers::{HeaderEnum, HeaderValueByteIterator};
+use http::headers::request::Header;
+use http::method::{Method, Get, Head, Connect, Trace, ExtensionMethod};
+use http::status::Status;
+
+use js::jsapi::{JS_AddObjectRoot, JS_ParseJSON, JS_RemoveObjectRoot, JSContext};
+use js::jsapi::JS_ClearPendingException;
+use js::jsval::{JSVal, NullValue, UndefinedValue};
+
+use libc;
+use libc::c_void;
+
+use net::resource_task::{ResourceTask, ResourceCORSData, Load, LoadData, Payload, Done};
+use cors::{allow_cross_origin_request, CORSRequest, CORSMode, ForcedPreflightMode};
+use script_task::{ScriptChan, XHRProgressMsg};
+use servo_util::str::DOMString;
+use servo_util::task::spawn_named;
+
+use std::ascii::StrAsciiExt;
+use std::cell::{Cell, RefCell};
+use std::comm::{Sender, Receiver, channel};
+use std::io::{BufReader, MemWriter, Timer};
+use std::from_str::FromStr;
+use std::path::BytesContainer;
+use std::task::TaskBuilder;
+use time;
+use url::{Url, UrlParser};
+
+use dom::bindings::codegen::UnionTypes::StringOrURLSearchParams::{eString, eURLSearchParams, StringOrURLSearchParams};
+pub type SendParam = StringOrURLSearchParams;
+
+
+#[deriving(PartialEq,Encodable)]
+pub enum XMLHttpRequestId {
+ XMLHttpRequestTypeId,
+ XMLHttpRequestUploadTypeId
+}
+
+#[deriving(PartialEq, Encodable)]
+enum XMLHttpRequestState {
+ Unsent = 0,
+ Opened = 1,
+ HeadersReceived = 2,
+ Loading = 3,
+ XHRDone = 4, // So as not to conflict with the ProgressMsg `Done`
+}
+
+pub enum XHRProgress {
+ /// Notify that headers have been received
+ HeadersReceivedMsg(Option<ResponseHeaderCollection>, Status),
+ /// Partial progress (after receiving headers), containing portion of the response
+ LoadingMsg(ByteString),
+ /// Loading is done
+ DoneMsg,
+ /// There was an error (Abort or Timeout). For a network or other error, just pass None
+ ErroredMsg(Option<Error>),
+ /// Timeout was reached
+ TimeoutMsg
+}
+
+enum SyncOrAsync<'a, 'b> {
+ Sync(&'b JSRef<'a, XMLHttpRequest>),
+ Async(TrustedXHRAddress, ScriptChan)
+}
+
+
+#[deriving(Encodable)]
+pub struct XMLHttpRequest {
+ eventtarget: XMLHttpRequestEventTarget,
+ ready_state: Traceable<Cell<XMLHttpRequestState>>,
+ timeout: Traceable<Cell<u32>>,
+ with_credentials: Traceable<Cell<bool>>,
+ upload: JS<XMLHttpRequestUpload>,
+ response_url: DOMString,
+ status: Traceable<Cell<u16>>,
+ status_text: Traceable<RefCell<ByteString>>,
+ response: Traceable<RefCell<ByteString>>,
+ response_type: Traceable<Cell<XMLHttpRequestResponseType>>,
+ response_xml: Cell<Option<JS<Document>>>,
+ response_headers: Untraceable<RefCell<ResponseHeaderCollection>>,
+
+ // Associated concepts
+ request_method: Untraceable<RefCell<Method>>,
+ request_url: Untraceable<RefCell<Option<Url>>>,
+ request_headers: Untraceable<RefCell<RequestHeaderCollection>>,
+ request_body_len: Traceable<Cell<uint>>,
+ sync: Traceable<Cell<bool>>,
+ upload_complete: Traceable<Cell<bool>>,
+ upload_events: Traceable<Cell<bool>>,
+ send_flag: Traceable<Cell<bool>>,
+
+ global: GlobalField,
+ pinned_count: Traceable<Cell<uint>>,
+ timer: Untraceable<RefCell<Timer>>,
+ fetch_time: Traceable<Cell<i64>>,
+ timeout_pinned: Traceable<Cell<bool>>,
+ terminate_sender: Untraceable<RefCell<Option<Sender<Error>>>>,
+}
+
+impl XMLHttpRequest {
+ pub fn new_inherited(global: &GlobalRef) -> XMLHttpRequest {
+ let xhr = XMLHttpRequest {
+ eventtarget: XMLHttpRequestEventTarget::new_inherited(XMLHttpRequestTypeId),
+ ready_state: Traceable::new(Cell::new(Unsent)),
+ timeout: Traceable::new(Cell::new(0u32)),
+ with_credentials: Traceable::new(Cell::new(false)),
+ upload: JS::from_rooted(&XMLHttpRequestUpload::new(global)),
+ response_url: "".to_string(),
+ status: Traceable::new(Cell::new(0)),
+ status_text: Traceable::new(RefCell::new(ByteString::new(vec!()))),
+ response: Traceable::new(RefCell::new(ByteString::new(vec!()))),
+ response_type: Traceable::new(Cell::new(_empty)),
+ response_xml: Cell::new(None),
+ response_headers: Untraceable::new(RefCell::new(ResponseHeaderCollection::new())),
+
+ request_method: Untraceable::new(RefCell::new(Get)),
+ request_url: Untraceable::new(RefCell::new(None)),
+ request_headers: Untraceable::new(RefCell::new(RequestHeaderCollection::new())),
+ request_body_len: Traceable::new(Cell::new(0)),
+ sync: Traceable::new(Cell::new(false)),
+ send_flag: Traceable::new(Cell::new(false)),
+
+ upload_complete: Traceable::new(Cell::new(false)),
+ upload_events: Traceable::new(Cell::new(false)),
+
+ global: GlobalField::from_rooted(global),
+ pinned_count: Traceable::new(Cell::new(0)),
+ timer: Untraceable::new(RefCell::new(Timer::new().unwrap())),
+ fetch_time: Traceable::new(Cell::new(0)),
+ timeout_pinned: Traceable::new(Cell::new(false)),
+ terminate_sender: Untraceable::new(RefCell::new(None)),
+ };
+ xhr
+ }
+ pub fn new(global: &GlobalRef) -> Temporary<XMLHttpRequest> {
+ reflect_dom_object(box XMLHttpRequest::new_inherited(global),
+ global,
+ XMLHttpRequestBinding::Wrap)
+ }
+ pub fn Constructor(global: &GlobalRef) -> Fallible<Temporary<XMLHttpRequest>> {
+ Ok(XMLHttpRequest::new(global))
+ }
+
+ pub fn handle_xhr_progress(addr: TrustedXHRAddress, progress: XHRProgress) {
+ unsafe {
+ let xhr = JS::from_trusted_xhr_address(addr).root();
+ xhr.deref().process_partial_response(progress);
+ }
+ }
+
+ fn fetch(fetch_type: &SyncOrAsync, resource_task: ResourceTask,
+ mut load_data: LoadData, terminate_receiver: Receiver<Error>,
+ cors_request: Result<Option<CORSRequest>,()>) -> ErrorResult {
+ fn notify_partial_progress(fetch_type: &SyncOrAsync, msg: XHRProgress) {
+ match *fetch_type {
+ Sync(ref xhr) => {
+ xhr.process_partial_response(msg);
+ },
+ Async(addr, ref script_chan) => {
+ let ScriptChan(ref chan) = *script_chan;
+ chan.send(XHRProgressMsg(addr, msg));
+ }
+ }
+ }
+
+ match cors_request {
+ Err(_) => return Err(Network), // Happens in case of cross-origin non-http URIs
+ Ok(Some(ref req)) => {
+ let response = req.http_fetch();
+ if response.network_error {
+ return Err(Network)
+ } else {
+ load_data.cors = Some(ResourceCORSData {
+ preflight: req.preflight_flag,
+ origin: req.origin.clone()
+ })
+ }
+ },
+ _ => {}
+ }
+
+ // Step 10, 13
+ let (start_chan, start_port) = channel();
+ resource_task.send(Load(load_data, start_chan));
+ let response = start_port.recv();
+ match terminate_receiver.try_recv() {
+ Ok(e) => return Err(e),
+ _ => {}
+ }
+ match cors_request {
+ Ok(Some(ref req)) => {
+ match response.metadata.headers {
+ Some(ref h) if allow_cross_origin_request(req, h) => {},
+ _ => return Err(Network)
+ }
+ },
+ _ => {}
+ }
+ // XXXManishearth Clear cache entries in case of a network error
+
+ notify_partial_progress(fetch_type, HeadersReceivedMsg(
+ response.metadata.headers.clone(), response.metadata.status.clone()));
+ let mut buf = vec!();
+ loop {
+ let progress = response.progress_port.recv();
+ match terminate_receiver.try_recv() {
+ Ok(e) => return Err(e),
+ _ => {}
+ }
+ match progress {
+ Payload(data) => {
+ buf.push_all(data.as_slice());
+ notify_partial_progress(fetch_type, LoadingMsg(ByteString::new(buf.clone())));
+ },
+ Done(Ok(())) => {
+ notify_partial_progress(fetch_type, DoneMsg);
+ return Ok(());
+ },
+ Done(Err(_)) => {
+ notify_partial_progress(fetch_type, ErroredMsg(None));
+ return Err(Network)
+ }
+ }
+ }
+ }
+}
+
+impl<'a> XMLHttpRequestMethods for JSRef<'a, XMLHttpRequest> {
+ fn GetOnreadystatechange(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("readystatechange")
+ }
+
+ fn SetOnreadystatechange(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("readystatechange", listener)
+ }
+
+ fn ReadyState(&self) -> u16 {
+ self.ready_state.deref().get() as u16
+ }
+
+ fn Open(&self, method: ByteString, url: DOMString) -> ErrorResult {
+ // Clean up from previous requests, if any:
+ self.cancel_timeout();
+ let uppercase_method = method.as_str().map(|s| {
+ let upper = s.to_ascii_upper();
+ match upper.as_slice() {
+ "DELETE" | "GET" | "HEAD" | "OPTIONS" |
+ "POST" | "PUT" | "CONNECT" | "TRACE" |
+ "TRACK" => upper,
+ _ => s.to_string()
+ }
+ });
+ let maybe_method: Option<Method> = uppercase_method.and_then(|s| {
+ // Note: rust-http tests against the uppercase versions
+ // Since we want to pass methods not belonging to the short list above
+ // without changing capitalization, this will actually sidestep rust-http's type system
+ // since methods like "patch" or "PaTcH" will be considered extension methods
+ // despite the there being a rust-http method variant for them
+ Method::from_str_or_new(s.as_slice())
+ });
+ // Step 2
+ match maybe_method {
+ // Step 4
+ Some(Connect) | Some(Trace) => Err(Security),
+ Some(ExtensionMethod(ref t)) if t.as_slice() == "TRACK" => Err(Security),
+ Some(_) if method.is_token() => {
+
+ *self.request_method.deref().borrow_mut() = maybe_method.unwrap();
+
+ // Step 6
+ let base = self.global.root().root_ref().get_url();
+ let parsed_url = match UrlParser::new().base_url(&base).parse(url.as_slice()) {
+ Ok(parsed) => parsed,
+ Err(_) => return Err(Syntax) // Step 7
+ };
+ // XXXManishearth Do some handling of username/passwords
+ if self.sync.deref().get() {
+ // FIXME: This should only happen if the global environment is a document environment
+ if self.timeout.deref().get() != 0 || self.with_credentials.deref().get() || self.response_type.deref().get() != _empty {
+ return Err(InvalidAccess)
+ }
+ }
+ // XXXManishearth abort existing requests
+ // Step 12
+ *self.request_url.deref().borrow_mut() = Some(parsed_url);
+ *self.request_headers.deref().borrow_mut() = RequestHeaderCollection::new();
+ self.send_flag.deref().set(false);
+ *self.status_text.deref().borrow_mut() = ByteString::new(vec!());
+ self.status.deref().set(0);
+
+ // Step 13
+ if self.ready_state.deref().get() != Opened {
+ self.change_ready_state(Opened);
+ }
+ Ok(())
+ },
+ // This includes cases where as_str() returns None, and when is_token() returns false,
+ // both of which indicate invalid extension method names
+ _ => Err(Syntax), // Step 3
+ }
+ }
+ fn Open_(&self, method: ByteString, url: DOMString, async: bool,
+ _username: Option<DOMString>, _password: Option<DOMString>) -> ErrorResult {
+ self.sync.deref().set(!async);
+ self.Open(method, url)
+ }
+ fn SetRequestHeader(&self, name: ByteString, mut value: ByteString) -> ErrorResult {
+ if self.ready_state.deref().get() != Opened || self.send_flag.deref().get() {
+ return Err(InvalidState); // Step 1, 2
+ }
+ if !name.is_token() || !value.is_field_value() {
+ return Err(Syntax); // Step 3, 4
+ }
+ let name_str = match name.to_lower().as_str() {
+ Some(s) => {
+ match s {
+ // Disallowed headers
+ "accept-charset" | "accept-encoding" |
+ "access-control-request-headers" |
+ "access-control-request-method" |
+ "connection" | "content-length" |
+ "cookie" | "cookie2" | "date" |"dnt" |
+ "expect" | "host" | "keep-alive" | "origin" |
+ "referer" | "te" | "trailer" | "transfer-encoding" |
+ "upgrade" | "user-agent" | "via" => {
+ return Ok(()); // Step 5
+ },
+ _ => String::from_str(s)
+ }
+ },
+ None => return Err(Syntax)
+ };
+ let mut collection = self.request_headers.deref().borrow_mut();
+
+
+ // Steps 6,7
+ let old_header = collection.iter().find(|ref h| -> bool {
+ // XXXManishearth following line waiting on the rust upgrade:
+ ByteString::new(h.header_name().into_bytes()).eq_ignore_case(&value)
+ });
+ match old_header {
+ Some(h) => {
+ unsafe {
+ // By step 4, the value is a subset of valid utf8
+ // So this unsafe block should never fail
+
+ let mut buf = h.header_value();
+ buf.push_bytes(&[0x2C, 0x20]);
+ buf.push_bytes(value.as_slice());
+ value = ByteString::new(buf.container_into_owned_bytes());
+
+ }
+ },
+ None => {}
+ }
+
+ let mut reader = BufReader::new(value.as_slice());
+ let maybe_header: Option<Header> = HeaderEnum::value_from_stream(
+ name_str,
+ &mut HeaderValueByteIterator::new(&mut reader));
+ match maybe_header {
+ Some(h) => {
+ // Overwrites existing headers, which we want since we have
+ // prepended the new header value with the old one already
+ collection.insert(h);
+ Ok(())
+ },
+ None => Err(Syntax)
+ }
+ }
+ fn Timeout(&self) -> u32 {
+ self.timeout.deref().get()
+ }
+ fn SetTimeout(&self, timeout: u32) -> ErrorResult {
+ if self.sync.deref().get() {
+ // FIXME: Not valid for a worker environment
+ Err(InvalidState)
+ } else {
+ self.timeout.deref().set(timeout);
+ if self.send_flag.deref().get() {
+ if timeout == 0 {
+ self.cancel_timeout();
+ return Ok(());
+ }
+ let progress = time::now().to_timespec().sec - self.fetch_time.deref().get();
+ if timeout > (progress * 1000) as u32 {
+ self.set_timeout(timeout - (progress * 1000) as u32);
+ } else {
+ // Immediately execute the timeout steps
+ self.set_timeout(0);
+ }
+ }
+ Ok(())
+ }
+ }
+ fn WithCredentials(&self) -> bool {
+ self.with_credentials.deref().get()
+ }
+ fn SetWithCredentials(&self, with_credentials: bool) {
+ self.with_credentials.deref().set(with_credentials);
+ }
+ fn Upload(&self) -> Temporary<XMLHttpRequestUpload> {
+ Temporary::new(self.upload)
+ }
+ fn Send(&self, data: Option<SendParam>) -> ErrorResult {
+ if self.ready_state.deref().get() != Opened || self.send_flag.deref().get() {
+ return Err(InvalidState); // Step 1, 2
+ }
+
+ let data = match *self.request_method.deref().borrow() {
+ Get | Head => None, // Step 3
+ _ => data
+ };
+ let extracted = data.map(|d| d.extract());
+ self.request_body_len.set(extracted.as_ref().map(|e| e.len()).unwrap_or(0));
+
+ // Step 6
+ self.upload_events.deref().set(false);
+ // Step 7
+ self.upload_complete.deref().set(match extracted {
+ None => true,
+ Some (ref v) if v.len() == 0 => true,
+ _ => false
+ });
+ let mut addr = None;
+ if !self.sync.deref().get() {
+ // If one of the event handlers below aborts the fetch,
+ // the assertion in release_once() will fail since we haven't pinned it yet.
+ // Pin early to avoid dealing with this
+ unsafe {
+ addr = Some(self.to_trusted());
+ }
+
+ // Step 8
+ let upload_target = &*self.upload.root();
+ let event_target: &JSRef<EventTarget> = EventTargetCast::from_ref(upload_target);
+ if event_target.has_handlers() {
+ self.upload_events.deref().set(true);
+ }
+
+ // Step 9
+ self.send_flag.deref().set(true);
+ self.dispatch_response_progress_event("loadstart".to_string());
+ if !self.upload_complete.deref().get() {
+ self.dispatch_upload_progress_event("loadstart".to_string(), Some(0));
+ }
+ }
+
+ if self.ready_state.deref().get() == Unsent {
+ // The progress events above might have run abort(), in which case we terminate the fetch.
+ return Ok(());
+ }
+
+ let global = self.global.root();
+ let resource_task = global.root_ref().resource_task();
+ let mut load_data = LoadData::new(self.request_url.deref().borrow().clone().unwrap());
+ load_data.data = extracted;
+
+ // Default headers
+ let request_headers = self.request_headers.deref();
+ if request_headers.borrow().content_type.is_none() {
+ let parameters = vec!((String::from_str("charset"), String::from_str("UTF-8")));
+ request_headers.borrow_mut().content_type = match data {
+ Some(eString(_)) =>
+ Some(MediaType {
+ type_: String::from_str("text"),
+ subtype: String::from_str("plain"),
+ parameters: parameters
+ }),
+ Some(eURLSearchParams(_)) =>
+ Some(MediaType {
+ type_: String::from_str("application"),
+ subtype: String::from_str("x-www-form-urlencoded"),
+ parameters: parameters
+ }),
+ None => None
+ }
+ }
+
+ if request_headers.borrow().accept.is_none() {
+ request_headers.borrow_mut().accept = Some(String::from_str("*/*"))
+ }
+
+ load_data.headers = (*self.request_headers.deref().borrow()).clone();
+ load_data.method = (*self.request_method.deref().borrow()).clone();
+ let (terminate_sender, terminate_receiver) = channel();
+ *self.terminate_sender.deref().borrow_mut() = Some(terminate_sender);
+
+ // CORS stuff
+ let referer_url = self.global.root().root_ref().get_url();
+ let mode = if self.upload_events.deref().get() {
+ ForcedPreflightMode
+ } else {
+ CORSMode
+ };
+ let cors_request = CORSRequest::maybe_new(referer_url.clone(), load_data.url.clone(), mode,
+ load_data.method.clone(), load_data.headers.clone());
+ match cors_request {
+ Ok(None) => {
+ let mut buf = String::new();
+ buf.push_str(referer_url.scheme.as_slice());
+ buf.push_str("://".as_slice());
+ referer_url.serialize_host().map(|ref h| buf.push_str(h.as_slice()));
+ referer_url.port().as_ref().map(|&p| {
+ buf.push_str(":".as_slice());
+ buf.push_str(p);
+ });
+ referer_url.serialize_path().map(|ref h| buf.push_str(h.as_slice()));
+ self.request_headers.deref().borrow_mut().referer = Some(buf);
+ },
+ Ok(Some(ref req)) => self.insert_trusted_header("origin".to_string(),
+ format!("{}", req.origin)),
+ _ => {}
+ }
+
+ if self.sync.deref().get() {
+ return XMLHttpRequest::fetch(&mut Sync(self), resource_task, load_data,
+ terminate_receiver, cors_request);
+ } else {
+ let builder = TaskBuilder::new().named("XHRTask");
+ self.fetch_time.deref().set(time::now().to_timespec().sec);
+ let script_chan = global.root_ref().script_chan().clone();
+ builder.spawn(proc() {
+ let _ = XMLHttpRequest::fetch(&mut Async(addr.unwrap(), script_chan),
+ resource_task, load_data, terminate_receiver, cors_request);
+ });
+ let timeout = self.timeout.deref().get();
+ if timeout > 0 {
+ self.set_timeout(timeout);
+ }
+ }
+ Ok(())
+ }
+ fn Abort(&self) {
+ self.terminate_sender.deref().borrow().as_ref().map(|s| s.send_opt(Abort));
+ match self.ready_state.deref().get() {
+ Opened if self.send_flag.deref().get() => self.process_partial_response(ErroredMsg(Some(Abort))),
+ HeadersReceived | Loading => self.process_partial_response(ErroredMsg(Some(Abort))),
+ _ => {}
+ };
+ self.ready_state.deref().set(Unsent);
+ }
+ fn ResponseURL(&self) -> DOMString {
+ self.response_url.clone()
+ }
+ fn Status(&self) -> u16 {
+ self.status.deref().get()
+ }
+ fn StatusText(&self) -> ByteString {
+ self.status_text.deref().borrow().clone()
+ }
+ fn GetResponseHeader(&self, name: ByteString) -> Option<ByteString> {
+ self.filter_response_headers().iter().find(|h| {
+ name.eq_ignore_case(&FromStr::from_str(h.header_name().as_slice()).unwrap())
+ }).map(|h| {
+ // rust-http doesn't decode properly, we'll convert it back to bytes here
+ ByteString::new(h.header_value().as_slice().chars().map(|c| { assert!(c <= '\u00FF'); c as u8 }).collect())
+ })
+ }
+ fn GetAllResponseHeaders(&self) -> ByteString {
+ let mut writer = MemWriter::new();
+ self.filter_response_headers().write_all(&mut writer).ok().expect("Writing response headers failed");
+ let mut vec = writer.unwrap();
+
+ // rust-http appends an extra "\r\n" when using write_all
+ vec.pop();
+ vec.pop();
+
+ ByteString::new(vec)
+ }
+ fn ResponseType(&self) -> XMLHttpRequestResponseType {
+ self.response_type.deref().get()
+ }
+ fn SetResponseType(&self, response_type: XMLHttpRequestResponseType) -> ErrorResult {
+ match self.global {
+ WorkerField(_) if response_type == Document => return Ok(()),
+ _ => {}
+ }
+ match self.ready_state.deref().get() {
+ Loading | XHRDone => Err(InvalidState),
+ _ if self.sync.deref().get() => Err(InvalidAccess),
+ _ => {
+ self.response_type.deref().set(response_type);
+ Ok(())
+ }
+ }
+ }
+ fn Response(&self, cx: *mut JSContext) -> JSVal {
+ match self.response_type.deref().get() {
+ _empty | Text => {
+ let ready_state = self.ready_state.deref().get();
+ if ready_state == XHRDone || ready_state == Loading {
+ self.text_response().to_jsval(cx)
+ } else {
+ "".to_string().to_jsval(cx)
+ }
+ },
+ _ if self.ready_state.deref().get() != XHRDone => NullValue(),
+ Json => {
+ let decoded = UTF_8.decode(self.response.deref().borrow().as_slice(), DecodeReplace).unwrap().to_string();
+ let decoded: Vec<u16> = decoded.as_slice().utf16_units().collect();
+ let mut vp = UndefinedValue();
+ unsafe {
+ if JS_ParseJSON(cx, decoded.as_ptr(), decoded.len() as u32, &mut vp) == 0 {
+ JS_ClearPendingException(cx);
+ return NullValue();
+ }
+ }
+ vp
+ }
+ _ => {
+ // XXXManishearth handle other response types
+ self.response.deref().borrow().to_jsval(cx)
+ }
+ }
+ }
+ fn GetResponseText(&self) -> Fallible<DOMString> {
+ match self.response_type.deref().get() {
+ _empty | Text => {
+ match self.ready_state.deref().get() {
+ Loading | XHRDone => Ok(self.text_response()),
+ _ => Ok("".to_string())
+ }
+ },
+ _ => Err(InvalidState)
+ }
+ }
+ fn GetResponseXML(&self) -> Option<Temporary<Document>> {
+ self.response_xml.get().map(|response| Temporary::new(response))
+ }
+}
+
+impl Reflectable for XMLHttpRequest {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
+
+impl XMLHttpRequestDerived for EventTarget {
+ fn is_xmlhttprequest(&self) -> bool {
+ match self.type_id {
+ XMLHttpRequestTargetTypeId(XMLHttpRequestTypeId) => true,
+ _ => false
+ }
+ }
+}
+
+pub struct TrustedXHRAddress(pub *const c_void);
+
+impl TrustedXHRAddress {
+ pub fn release_once(self) {
+ unsafe {
+ JS::from_trusted_xhr_address(self).root().release_once();
+ }
+ }
+}
+
+
+trait PrivateXMLHttpRequestHelpers {
+ unsafe fn to_trusted(&self) -> TrustedXHRAddress;
+ fn release_once(&self);
+ fn change_ready_state(&self, XMLHttpRequestState);
+ fn process_partial_response(&self, progress: XHRProgress);
+ fn insert_trusted_header(&self, name: String, value: String);
+ fn dispatch_progress_event(&self, upload: bool, type_: DOMString, loaded: u64, total: Option<u64>);
+ fn dispatch_upload_progress_event(&self, type_: DOMString, partial_load: Option<u64>);
+ fn dispatch_response_progress_event(&self, type_: DOMString);
+ fn text_response(&self) -> DOMString;
+ fn set_timeout(&self, timeout:u32);
+ fn cancel_timeout(&self);
+ fn filter_response_headers(&self) -> ResponseHeaderCollection;
+}
+
+impl<'a> PrivateXMLHttpRequestHelpers for JSRef<'a, XMLHttpRequest> {
+ // Creates a trusted address to the object, and roots it. Always pair this with a release()
+ unsafe fn to_trusted(&self) -> TrustedXHRAddress {
+ if self.pinned_count.deref().get() == 0 {
+ JS_AddObjectRoot(self.global.root().root_ref().get_cx(), self.reflector().rootable());
+ }
+ let pinned_count = self.pinned_count.deref().get();
+ self.pinned_count.deref().set(pinned_count + 1);
+ TrustedXHRAddress(self.deref() as *const XMLHttpRequest as *const libc::c_void)
+ }
+
+ fn release_once(&self) {
+ if self.sync.deref().get() {
+ // Lets us call this at various termination cases without having to
+ // check self.sync every time, since the pinning mechanism only is
+ // meaningful during an async fetch
+ return;
+ }
+ assert!(self.pinned_count.deref().get() > 0)
+ let pinned_count = self.pinned_count.deref().get();
+ self.pinned_count.deref().set(pinned_count - 1);
+ if self.pinned_count.deref().get() == 0 {
+ unsafe {
+ JS_RemoveObjectRoot(self.global.root().root_ref().get_cx(), self.reflector().rootable());
+ }
+ }
+ }
+
+ fn change_ready_state(&self, rs: XMLHttpRequestState) {
+ assert!(self.ready_state.deref().get() != rs)
+ self.ready_state.deref().set(rs);
+ let global = self.global.root();
+ let event = Event::new(&global.root_ref(),
+ "readystatechange".to_string(),
+ false, true).root();
+ let target: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ target.dispatch_event_with_target(None, &*event).ok();
+ }
+
+ fn process_partial_response(&self, progress: XHRProgress) {
+ match progress {
+ HeadersReceivedMsg(headers, status) => {
+ // For synchronous requests, this should not fire any events, and just store data
+ // XXXManishearth Find a way to track partial progress of the send (onprogresss for XHRUpload)
+
+ // Part of step 13, send() (processing request end of file)
+ // Substep 1
+ self.upload_complete.deref().set(true);
+ // Substeps 2-4
+ if !self.sync.deref().get() {
+ self.dispatch_upload_progress_event("progress".to_string(), None);
+ self.dispatch_upload_progress_event("load".to_string(), None);
+ self.dispatch_upload_progress_event("loadend".to_string(), None);
+ }
+ // Part of step 13, send() (processing response)
+ // XXXManishearth handle errors, if any (substep 1)
+ // Substep 2
+ *self.status_text.deref().borrow_mut() = ByteString::new(status.reason().container_into_owned_bytes());
+ self.status.deref().set(status.code());
+ match headers {
+ Some(ref h) => {
+ *self.response_headers.deref().borrow_mut() = h.clone();
+ }
+ None => {}
+ };
+ // Substep 3
+ if self.ready_state.deref().get() == Opened && !self.sync.deref().get() {
+ self.change_ready_state(HeadersReceived);
+ }
+ },
+ LoadingMsg(partial_response) => {
+ // For synchronous requests, this should not fire any events, and just store data
+ // Part of step 13, send() (processing response body)
+ // XXXManishearth handle errors, if any (substep 1)
+
+ // Substep 2
+ if self.ready_state.deref().get() == HeadersReceived && !self.sync.deref().get() {
+ self.change_ready_state(Loading);
+ }
+ // Substep 3
+ *self.response.deref().borrow_mut() = partial_response;
+ // Substep 4
+ if !self.sync.deref().get() {
+ self.dispatch_response_progress_event("progress".to_string());
+ }
+ },
+ DoneMsg => {
+ // Part of step 13, send() (processing response end of file)
+ // XXXManishearth handle errors, if any (substep 1)
+
+ // Substep 3
+ if self.ready_state.deref().get() == Loading || self.sync.deref().get() {
+ // Subsubsteps 2-4
+ self.send_flag.deref().set(false);
+ self.change_ready_state(XHRDone);
+
+ // Subsubsteps 5-7
+ self.dispatch_response_progress_event("progress".to_string());
+ self.dispatch_response_progress_event("load".to_string());
+ self.dispatch_response_progress_event("loadend".to_string());
+ }
+ self.cancel_timeout();
+ self.release_once();
+ },
+ ErroredMsg(e) => {
+ self.send_flag.deref().set(false);
+ // XXXManishearth set response to NetworkError
+ self.change_ready_state(XHRDone);
+ let errormsg = match e {
+ Some(Abort) => "abort",
+ Some(Timeout) => "timeout",
+ None => "error",
+ _ => unreachable!()
+ };
+
+ let upload_complete: &Cell<bool> = self.upload_complete.deref();
+ if !upload_complete.get() {
+ upload_complete.set(true);
+ self.dispatch_upload_progress_event("progress".to_string(), None);
+ self.dispatch_upload_progress_event(errormsg.to_string(), None);
+ self.dispatch_upload_progress_event("loadend".to_string(), None);
+ }
+ self.dispatch_response_progress_event("progress".to_string());
+ self.dispatch_response_progress_event(errormsg.to_string());
+ self.dispatch_response_progress_event("loadend".to_string());
+
+ self.cancel_timeout();
+ self.release_once();
+ },
+ TimeoutMsg => {
+ match self.ready_state.deref().get() {
+ Opened if self.send_flag.deref().get() => self.process_partial_response(ErroredMsg(Some(Timeout))),
+ Loading | HeadersReceived => self.process_partial_response(ErroredMsg(Some(Timeout))),
+ _ => self.release_once()
+ };
+ }
+ }
+ }
+
+ fn insert_trusted_header(&self, name: String, value: String) {
+ // Insert a header without checking spec-compliance
+ // Use for hardcoded headers
+ let mut collection = self.request_headers.deref().borrow_mut();
+ let value_bytes = value.into_bytes();
+ let mut reader = BufReader::new(value_bytes.as_slice());
+ let maybe_header: Option<Header> = HeaderEnum::value_from_stream(
+ String::from_str(name.as_slice()),
+ &mut HeaderValueByteIterator::new(&mut reader));
+ collection.insert(maybe_header.unwrap());
+ }
+
+ fn dispatch_progress_event(&self, upload: bool, type_: DOMString, loaded: u64, total: Option<u64>) {
+ let global = self.global.root();
+ let upload_target = &*self.upload.root();
+ let progressevent = ProgressEvent::new(&global.root_ref(),
+ type_, false, false,
+ total.is_some(), loaded,
+ total.unwrap_or(0)).root();
+ let target: &JSRef<EventTarget> = if upload {
+ EventTargetCast::from_ref(upload_target)
+ } else {
+ EventTargetCast::from_ref(self)
+ };
+ let event: &JSRef<Event> = EventCast::from_ref(&*progressevent);
+ target.dispatch_event_with_target(None, event).ok();
+ }
+
+ fn dispatch_upload_progress_event(&self, type_: DOMString, partial_load: Option<u64>) {
+ // If partial_load is None, loading has completed and we can just use the value from the request body
+
+ let total = self.request_body_len.get() as u64;
+ self.dispatch_progress_event(true, type_, partial_load.unwrap_or(total), Some(total));
+ }
+
+ fn dispatch_response_progress_event(&self, type_: DOMString) {
+ let len = self.response.deref().borrow().len() as u64;
+ let total = self.response_headers.deref().borrow().content_length.map(|x| {x as u64});
+ self.dispatch_progress_event(false, type_, len, total);
+ }
+ fn set_timeout(&self, timeout: u32) {
+ // Sets up the object to timeout in a given number of milliseconds
+ // This will cancel all previous timeouts
+ let oneshot = self.timer.deref().borrow_mut().oneshot(timeout as u64);
+ let addr = unsafe {
+ self.to_trusted() // This will increment the pin counter by one
+ };
+ if self.timeout_pinned.deref().get() {
+ // Already pinned due to a timeout, no need to pin it again since the old timeout was cancelled above
+ self.release_once();
+ }
+ self.timeout_pinned.deref().set(true);
+ let global = self.global.root();
+ let script_chan = global.root_ref().script_chan().clone();
+ let terminate_sender = (*self.terminate_sender.deref().borrow()).clone();
+ spawn_named("XHR:Timer", proc () {
+ match oneshot.recv_opt() {
+ Ok(_) => {
+ let ScriptChan(ref chan) = script_chan;
+ terminate_sender.map(|s| s.send_opt(Timeout));
+ chan.send(XHRProgressMsg(addr, TimeoutMsg));
+ },
+ Err(_) => {
+ // This occurs if xhr.timeout (the sender) goes out of scope (i.e, xhr went out of scope)
+ // or if the oneshot timer was overwritten. The former case should not happen due to pinning.
+ debug!("XHR timeout was overwritten or canceled")
+ }
+ }
+ }
+ );
+ }
+ fn cancel_timeout(&self) {
+ // Cancels timeouts on the object, if any
+ if self.timeout_pinned.deref().get() {
+ self.timeout_pinned.deref().set(false);
+ self.release_once();
+ }
+ // oneshot() closes the previous channel, canceling the timeout
+ self.timer.deref().borrow_mut().oneshot(0);
+ }
+ fn text_response(&self) -> DOMString {
+ let mut encoding = UTF_8 as EncodingRef;
+ match self.response_headers.deref().borrow().content_type {
+ Some(ref x) => {
+ for &(ref name, ref value) in x.parameters.iter() {
+ if name.as_slice().eq_ignore_ascii_case("charset") {
+ encoding = encoding_from_whatwg_label(value.as_slice()).unwrap_or(encoding);
+ }
+ }
+ },
+ None => {}
+ }
+ // According to Simon, decode() should never return an error, so unwrap()ing
+ // the result should be fine. XXXManishearth have a closer look at this later
+ encoding.decode(self.response.deref().borrow().as_slice(), DecodeReplace).unwrap().to_string()
+ }
+ fn filter_response_headers(&self) -> ResponseHeaderCollection {
+ // http://fetch.spec.whatwg.org/#concept-response-header-list
+ let mut headers = ResponseHeaderCollection::new();
+ for header in self.response_headers.deref().borrow().iter() {
+ match header.header_name().as_slice().to_ascii_lower().as_slice() {
+ "set-cookie" | "set-cookie2" => {},
+ // XXXManishearth additional CORS filtering goes here
+ _ => headers.insert(header)
+ };
+ }
+ headers
+ }
+}
+
+trait Extractable {
+ fn extract(&self) -> Vec<u8>;
+}
+impl Extractable for SendParam {
+ fn extract(&self) -> Vec<u8> {
+ // http://fetch.spec.whatwg.org/#concept-fetchbodyinit-extract
+ let encoding = UTF_8 as EncodingRef;
+ match *self {
+ eString(ref s) => encoding.encode(s.as_slice(), EncodeReplace).unwrap(),
+ eURLSearchParams(ref usp) => usp.root().serialize(None) // Default encoding is UTF8
+ }
+ }
+}
diff --git a/components/script/dom/xmlhttprequesteventtarget.rs b/components/script/dom/xmlhttprequesteventtarget.rs
new file mode 100644
index 00000000000..06c42fbdc4e
--- /dev/null
+++ b/components/script/dom/xmlhttprequesteventtarget.rs
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
+use dom::bindings::codegen::Bindings::XMLHttpRequestEventTargetBinding::XMLHttpRequestEventTargetMethods;
+use dom::bindings::codegen::InheritTypes::EventTargetCast;
+use dom::bindings::codegen::InheritTypes::XMLHttpRequestEventTargetDerived;
+use dom::bindings::js::JSRef;
+use dom::bindings::utils::{Reflectable, Reflector};
+use dom::eventtarget::{EventTarget, EventTargetHelpers, XMLHttpRequestTargetTypeId};
+use dom::xmlhttprequest::XMLHttpRequestId;
+
+#[deriving(Encodable)]
+pub struct XMLHttpRequestEventTarget {
+ pub eventtarget: EventTarget,
+}
+
+impl XMLHttpRequestEventTarget {
+ pub fn new_inherited(type_id: XMLHttpRequestId) -> XMLHttpRequestEventTarget {
+ XMLHttpRequestEventTarget {
+ eventtarget: EventTarget::new_inherited(XMLHttpRequestTargetTypeId(type_id))
+ }
+ }
+}
+impl XMLHttpRequestEventTargetDerived for EventTarget {
+ fn is_xmlhttprequesteventtarget(&self) -> bool {
+ match self.type_id {
+ XMLHttpRequestTargetTypeId(_) => true,
+ _ => false
+ }
+ }
+
+}
+
+impl Reflectable for XMLHttpRequestEventTarget {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
+
+impl<'a> XMLHttpRequestEventTargetMethods for JSRef<'a, XMLHttpRequestEventTarget> {
+ fn GetOnloadstart(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("loadstart")
+ }
+
+ fn SetOnloadstart(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("loadstart", listener)
+ }
+
+ fn GetOnprogress(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("progress")
+ }
+
+ fn SetOnprogress(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("progress", listener)
+ }
+
+ fn GetOnabort(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("abort")
+ }
+
+ fn SetOnabort(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("abort", listener)
+ }
+
+ fn GetOnerror(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("error")
+ }
+
+ fn SetOnerror(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("error", listener)
+ }
+
+ fn GetOnload(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("load")
+ }
+
+ fn SetOnload(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("load", listener)
+ }
+
+ fn GetOntimeout(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("timeout")
+ }
+
+ fn SetOntimeout(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("timeout", listener)
+ }
+
+ fn GetOnloadend(&self) -> Option<EventHandlerNonNull> {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.get_event_handler_common("loadend")
+ }
+
+ fn SetOnloadend(&self, listener: Option<EventHandlerNonNull>) {
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(self);
+ eventtarget.set_event_handler_common("loadend", listener)
+ }
+}
diff --git a/components/script/dom/xmlhttprequestupload.rs b/components/script/dom/xmlhttprequestupload.rs
new file mode 100644
index 00000000000..477d382bffe
--- /dev/null
+++ b/components/script/dom/xmlhttprequestupload.rs
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::InheritTypes::XMLHttpRequestUploadDerived;
+use dom::bindings::codegen::Bindings::XMLHttpRequestUploadBinding;
+use dom::bindings::global::GlobalRef;
+use dom::bindings::js::Temporary;
+use dom::bindings::utils::{Reflectable, Reflector, reflect_dom_object};
+use dom::eventtarget::{EventTarget, XMLHttpRequestTargetTypeId};
+use dom::xmlhttprequest::{XMLHttpRequestUploadTypeId};
+use dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
+
+#[deriving(Encodable)]
+pub struct XMLHttpRequestUpload {
+ eventtarget: XMLHttpRequestEventTarget
+}
+
+impl XMLHttpRequestUpload {
+ pub fn new_inherited() -> XMLHttpRequestUpload {
+ XMLHttpRequestUpload {
+ eventtarget:XMLHttpRequestEventTarget::new_inherited(XMLHttpRequestUploadTypeId)
+ }
+ }
+ pub fn new(global: &GlobalRef) -> Temporary<XMLHttpRequestUpload> {
+ reflect_dom_object(box XMLHttpRequestUpload::new_inherited(),
+ global,
+ XMLHttpRequestUploadBinding::Wrap)
+ }
+}
+impl Reflectable for XMLHttpRequestUpload {
+ fn reflector<'a>(&'a self) -> &'a Reflector {
+ self.eventtarget.reflector()
+ }
+}
+
+impl XMLHttpRequestUploadDerived for EventTarget {
+ fn is_xmlhttprequestupload(&self) -> bool {
+ self.type_id == XMLHttpRequestTargetTypeId(XMLHttpRequestUploadTypeId)
+ }
+}
diff --git a/components/script/html/cssparse.rs b/components/script/html/cssparse.rs
new file mode 100644
index 00000000000..473b64c7d76
--- /dev/null
+++ b/components/script/html/cssparse.rs
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Some little helpers for hooking up the HTML parser with the CSS parser.
+
+use std::comm::{channel, Receiver};
+use encoding::EncodingRef;
+use encoding::all::UTF_8;
+use style::Stylesheet;
+use servo_net::resource_task::{Load, LoadData, LoadResponse, ProgressMsg, Payload, Done, ResourceTask};
+use servo_util::task::spawn_named;
+use url::Url;
+
+/// Where a style sheet comes from.
+pub enum StylesheetProvenance {
+ UrlProvenance(Url, ResourceTask),
+ InlineProvenance(Url, String),
+}
+
+// Parses the style data and returns the stylesheet
+pub fn parse_inline_css(url: Url, data: String) -> Stylesheet {
+ parse_css(InlineProvenance(url, data))
+}
+
+fn parse_css(provenance: StylesheetProvenance) -> Stylesheet {
+ // TODO: Get the actual value. http://dev.w3.org/csswg/css-syntax/#environment-encoding
+ let environment_encoding = UTF_8 as EncodingRef;
+
+ match provenance {
+ UrlProvenance(url, resource_task) => {
+ debug!("cssparse: loading style sheet at {:s}", url.serialize());
+ let (input_chan, input_port) = channel();
+ resource_task.send(Load(LoadData::new(url), input_chan));
+ let LoadResponse { metadata: metadata, progress_port: progress_port , ..}
+ = input_port.recv();
+ let final_url = &metadata.final_url;
+ let protocol_encoding_label = metadata.charset.as_ref().map(|s| s.as_slice());
+ let iter = ProgressMsgPortIterator { progress_port: progress_port };
+ Stylesheet::from_bytes_iter(
+ iter, final_url.clone(),
+ protocol_encoding_label, Some(environment_encoding))
+ }
+ InlineProvenance(base_url, data) => {
+ debug!("cssparse: loading inline stylesheet {:s}", data);
+ Stylesheet::from_str(data.as_slice(), base_url)
+ }
+ }
+}
+
+pub fn spawn_css_parser(provenance: StylesheetProvenance) -> Receiver<Stylesheet> {
+ let (result_chan, result_port) = channel();
+
+ spawn_named("cssparser", proc() {
+ result_chan.send(parse_css(provenance));
+ });
+
+ return result_port;
+}
+
+struct ProgressMsgPortIterator {
+ progress_port: Receiver<ProgressMsg>
+}
+
+impl Iterator<Vec<u8>> for ProgressMsgPortIterator {
+ fn next(&mut self) -> Option<Vec<u8>> {
+ match self.progress_port.recv() {
+ Payload(data) => Some(data),
+ Done(..) => None
+ }
+ }
+}
diff --git a/components/script/html/hubbub_html_parser.rs b/components/script/html/hubbub_html_parser.rs
new file mode 100644
index 00000000000..8b49a2bae03
--- /dev/null
+++ b/components/script/html/hubbub_html_parser.rs
@@ -0,0 +1,615 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
+use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use dom::bindings::codegen::InheritTypes::{NodeBase, NodeCast, TextCast};
+use dom::bindings::codegen::InheritTypes::{ElementCast, HTMLScriptElementCast};
+use dom::bindings::js::{JS, JSRef, Temporary, OptionalRootable, Root};
+use dom::bindings::utils::Reflectable;
+use dom::document::{Document, DocumentHelpers};
+use dom::element::{AttributeHandlers, HTMLLinkElementTypeId};
+use dom::htmlelement::HTMLElement;
+use dom::htmlheadingelement::{Heading1, Heading2, Heading3, Heading4, Heading5, Heading6};
+use dom::htmlformelement::HTMLFormElement;
+use dom::htmlscriptelement::HTMLScriptElementHelpers;
+use dom::node::{ElementNodeTypeId, NodeHelpers};
+use dom::types::*;
+use html::cssparse::{StylesheetProvenance, UrlProvenance, spawn_css_parser};
+use page::Page;
+
+use hubbub::hubbub;
+use hubbub::hubbub::{NullNs, HtmlNs, MathMlNs, SvgNs, XLinkNs, XmlNs, XmlNsNs};
+use servo_net::resource_task::{Load, LoadData, Payload, Done, ResourceTask, load_whole_resource};
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::namespace::{Namespace, Null};
+use servo_util::str::{DOMString, HTML_SPACE_CHARACTERS};
+use servo_util::task::spawn_named;
+use std::ascii::StrAsciiExt;
+use std::mem;
+use std::cell::RefCell;
+use std::comm::{channel, Sender, Receiver};
+use style::Stylesheet;
+use url::{Url, UrlParser};
+use http::headers::HeaderEnum;
+use time;
+
+macro_rules! handle_element(
+ ($document: expr,
+ $localName: expr,
+ $string: expr,
+ $ctor: ident
+ $(, $arg:expr )*) => (
+ if $string == $localName.as_slice() {
+ return ElementCast::from_temporary($ctor::new($localName, $document $(, $arg)*));
+ }
+ )
+)
+
+
+pub struct JSFile {
+ pub data: String,
+ pub url: Url
+}
+
+pub type JSResult = Vec<JSFile>;
+
+enum CSSMessage {
+ CSSTaskNewFile(StylesheetProvenance),
+ CSSTaskExit
+}
+
+enum JSMessage {
+ JSTaskNewFile(Url),
+ JSTaskNewInlineScript(String, Url),
+ JSTaskExit
+}
+
+/// Messages generated by the HTML parser upon discovery of additional resources
+pub enum HtmlDiscoveryMessage {
+ HtmlDiscoveredStyle(Stylesheet),
+ HtmlDiscoveredScript(JSResult)
+}
+
+pub struct HtmlParserResult {
+ pub discovery_port: Receiver<HtmlDiscoveryMessage>,
+}
+
+trait NodeWrapping<T> {
+ unsafe fn to_hubbub_node(&self) -> hubbub::NodeDataPtr;
+}
+
+impl<'a, T: NodeBase+Reflectable> NodeWrapping<T> for JSRef<'a, T> {
+ unsafe fn to_hubbub_node(&self) -> hubbub::NodeDataPtr {
+ mem::transmute(self.deref())
+ }
+}
+
+unsafe fn from_hubbub_node<T: Reflectable>(n: hubbub::NodeDataPtr) -> Temporary<T> {
+ Temporary::new(JS::from_raw(mem::transmute(n)))
+}
+
+/**
+Runs a task that coordinates parsing links to css stylesheets.
+
+This function should be spawned in a separate task and spins waiting
+for the html builder to find links to css stylesheets and sends off
+tasks to parse each link. When the html process finishes, it notifies
+the listener, who then collects the css rules from each task it
+spawned, collates them, and sends them to the given result channel.
+
+# Arguments
+
+* `to_parent` - A channel on which to send back the full set of rules.
+* `from_parent` - A port on which to receive new links.
+
+*/
+fn css_link_listener(to_parent: Sender<HtmlDiscoveryMessage>,
+ from_parent: Receiver<CSSMessage>) {
+ let mut result_vec = vec!();
+
+ loop {
+ match from_parent.recv_opt() {
+ Ok(CSSTaskNewFile(provenance)) => {
+ result_vec.push(spawn_css_parser(provenance));
+ }
+ Ok(CSSTaskExit) | Err(()) => {
+ break;
+ }
+ }
+ }
+
+ // Send the sheets back in order
+ // FIXME: Shouldn't wait until after we've recieved CSSTaskExit to start sending these
+ for port in result_vec.iter() {
+ assert!(to_parent.send_opt(HtmlDiscoveredStyle(port.recv())).is_ok());
+ }
+}
+
+fn js_script_listener(to_parent: Sender<HtmlDiscoveryMessage>,
+ from_parent: Receiver<JSMessage>,
+ resource_task: ResourceTask) {
+ let mut result_vec = vec!();
+
+ loop {
+ match from_parent.recv_opt() {
+ Ok(JSTaskNewFile(url)) => {
+ match load_whole_resource(&resource_task, url.clone()) {
+ Err(_) => {
+ error!("error loading script {:s}", url.serialize());
+ }
+ Ok((metadata, bytes)) => {
+ result_vec.push(JSFile {
+ data: String::from_utf8(bytes).unwrap().to_string(),
+ url: metadata.final_url,
+ });
+ }
+ }
+ }
+ Ok(JSTaskNewInlineScript(data, url)) => {
+ result_vec.push(JSFile { data: data, url: url });
+ }
+ Ok(JSTaskExit) | Err(()) => {
+ break;
+ }
+ }
+ }
+
+ assert!(to_parent.send_opt(HtmlDiscoveredScript(result_vec)).is_ok());
+}
+
+// Parses an RFC 2616 compliant date/time string, and returns a localized
+// date/time string in a format suitable for document.lastModified.
+fn parse_last_modified(timestamp: &str) -> String {
+ let format = "%m/%d/%Y %H:%M:%S";
+
+ // RFC 822, updated by RFC 1123
+ match time::strptime(timestamp, "%a, %d %b %Y %T %Z") {
+ Ok(t) => return t.to_local().strftime(format),
+ Err(_) => ()
+ }
+
+ // RFC 850, obsoleted by RFC 1036
+ match time::strptime(timestamp, "%A, %d-%b-%y %T %Z") {
+ Ok(t) => return t.to_local().strftime(format),
+ Err(_) => ()
+ }
+
+ // ANSI C's asctime() format
+ match time::strptime(timestamp, "%c") {
+ Ok(t) => t.to_local().strftime(format),
+ Err(_) => String::from_str("")
+ }
+}
+
+// Silly macros to handle constructing DOM nodes. This produces bad code and should be optimized
+// via atomization (issue #85).
+
+pub fn build_element_from_tag(tag: DOMString, ns: Namespace, document: &JSRef<Document>) -> Temporary<Element> {
+ if ns != namespace::HTML {
+ return Element::new(tag, ns, None, document);
+ }
+
+ // TODO (Issue #85): use atoms
+ handle_element!(document, tag, "a", HTMLAnchorElement);
+ handle_element!(document, tag, "abbr", HTMLElement);
+ handle_element!(document, tag, "acronym", HTMLElement);
+ handle_element!(document, tag, "address", HTMLElement);
+ handle_element!(document, tag, "applet", HTMLAppletElement);
+ handle_element!(document, tag, "area", HTMLAreaElement);
+ handle_element!(document, tag, "article", HTMLElement);
+ handle_element!(document, tag, "aside", HTMLElement);
+ handle_element!(document, tag, "audio", HTMLAudioElement);
+ handle_element!(document, tag, "b", HTMLElement);
+ handle_element!(document, tag, "base", HTMLBaseElement);
+ handle_element!(document, tag, "bdi", HTMLElement);
+ handle_element!(document, tag, "bdo", HTMLElement);
+ handle_element!(document, tag, "bgsound", HTMLElement);
+ handle_element!(document, tag, "big", HTMLElement);
+ handle_element!(document, tag, "blockquote",HTMLElement);
+ handle_element!(document, tag, "body", HTMLBodyElement);
+ handle_element!(document, tag, "br", HTMLBRElement);
+ handle_element!(document, tag, "button", HTMLButtonElement);
+ handle_element!(document, tag, "canvas", HTMLCanvasElement);
+ handle_element!(document, tag, "caption", HTMLTableCaptionElement);
+ handle_element!(document, tag, "center", HTMLElement);
+ handle_element!(document, tag, "cite", HTMLElement);
+ handle_element!(document, tag, "code", HTMLElement);
+ handle_element!(document, tag, "col", HTMLTableColElement);
+ handle_element!(document, tag, "colgroup", HTMLTableColElement);
+ handle_element!(document, tag, "data", HTMLDataElement);
+ handle_element!(document, tag, "datalist", HTMLDataListElement);
+ handle_element!(document, tag, "dd", HTMLElement);
+ handle_element!(document, tag, "del", HTMLModElement);
+ handle_element!(document, tag, "details", HTMLElement);
+ handle_element!(document, tag, "dfn", HTMLElement);
+ handle_element!(document, tag, "dir", HTMLDirectoryElement);
+ handle_element!(document, tag, "div", HTMLDivElement);
+ handle_element!(document, tag, "dl", HTMLDListElement);
+ handle_element!(document, tag, "dt", HTMLElement);
+ handle_element!(document, tag, "em", HTMLElement);
+ handle_element!(document, tag, "embed", HTMLEmbedElement);
+ handle_element!(document, tag, "fieldset", HTMLFieldSetElement);
+ handle_element!(document, tag, "figcaption",HTMLElement);
+ handle_element!(document, tag, "figure", HTMLElement);
+ handle_element!(document, tag, "font", HTMLFontElement);
+ handle_element!(document, tag, "footer", HTMLElement);
+ handle_element!(document, tag, "form", HTMLFormElement);
+ handle_element!(document, tag, "frame", HTMLFrameElement);
+ handle_element!(document, tag, "frameset", HTMLFrameSetElement);
+ handle_element!(document, tag, "h1", HTMLHeadingElement, Heading1);
+ handle_element!(document, tag, "h2", HTMLHeadingElement, Heading2);
+ handle_element!(document, tag, "h3", HTMLHeadingElement, Heading3);
+ handle_element!(document, tag, "h4", HTMLHeadingElement, Heading4);
+ handle_element!(document, tag, "h5", HTMLHeadingElement, Heading5);
+ handle_element!(document, tag, "h6", HTMLHeadingElement, Heading6);
+ handle_element!(document, tag, "head", HTMLHeadElement);
+ handle_element!(document, tag, "header", HTMLElement);
+ handle_element!(document, tag, "hgroup", HTMLElement);
+ handle_element!(document, tag, "hr", HTMLHRElement);
+ handle_element!(document, tag, "html", HTMLHtmlElement);
+ handle_element!(document, tag, "i", HTMLElement);
+ handle_element!(document, tag, "iframe", HTMLIFrameElement);
+ handle_element!(document, tag, "img", HTMLImageElement);
+ handle_element!(document, tag, "input", HTMLInputElement);
+ handle_element!(document, tag, "ins", HTMLModElement);
+ handle_element!(document, tag, "isindex", HTMLElement);
+ handle_element!(document, tag, "kbd", HTMLElement);
+ handle_element!(document, tag, "label", HTMLLabelElement);
+ handle_element!(document, tag, "legend", HTMLLegendElement);
+ handle_element!(document, tag, "li", HTMLLIElement);
+ handle_element!(document, tag, "link", HTMLLinkElement);
+ handle_element!(document, tag, "main", HTMLElement);
+ handle_element!(document, tag, "map", HTMLMapElement);
+ handle_element!(document, tag, "mark", HTMLElement);
+ handle_element!(document, tag, "marquee", HTMLElement);
+ handle_element!(document, tag, "meta", HTMLMetaElement);
+ handle_element!(document, tag, "meter", HTMLMeterElement);
+ handle_element!(document, tag, "nav", HTMLElement);
+ handle_element!(document, tag, "nobr", HTMLElement);
+ handle_element!(document, tag, "noframes", HTMLElement);
+ handle_element!(document, tag, "noscript", HTMLElement);
+ handle_element!(document, tag, "object", HTMLObjectElement);
+ handle_element!(document, tag, "ol", HTMLOListElement);
+ handle_element!(document, tag, "optgroup", HTMLOptGroupElement);
+ handle_element!(document, tag, "option", HTMLOptionElement);
+ handle_element!(document, tag, "output", HTMLOutputElement);
+ handle_element!(document, tag, "p", HTMLParagraphElement);
+ handle_element!(document, tag, "param", HTMLParamElement);
+ handle_element!(document, tag, "pre", HTMLPreElement);
+ handle_element!(document, tag, "progress", HTMLProgressElement);
+ handle_element!(document, tag, "q", HTMLQuoteElement);
+ handle_element!(document, tag, "rp", HTMLElement);
+ handle_element!(document, tag, "rt", HTMLElement);
+ handle_element!(document, tag, "ruby", HTMLElement);
+ handle_element!(document, tag, "s", HTMLElement);
+ handle_element!(document, tag, "samp", HTMLElement);
+ handle_element!(document, tag, "script", HTMLScriptElement);
+ handle_element!(document, tag, "section", HTMLElement);
+ handle_element!(document, tag, "select", HTMLSelectElement);
+ handle_element!(document, tag, "small", HTMLElement);
+ handle_element!(document, tag, "source", HTMLSourceElement);
+ handle_element!(document, tag, "spacer", HTMLElement);
+ handle_element!(document, tag, "span", HTMLSpanElement);
+ handle_element!(document, tag, "strike", HTMLElement);
+ handle_element!(document, tag, "strong", HTMLElement);
+ handle_element!(document, tag, "style", HTMLStyleElement);
+ handle_element!(document, tag, "sub", HTMLElement);
+ handle_element!(document, tag, "summary", HTMLElement);
+ handle_element!(document, tag, "sup", HTMLElement);
+ handle_element!(document, tag, "table", HTMLTableElement);
+ handle_element!(document, tag, "tbody", HTMLTableSectionElement);
+ handle_element!(document, tag, "td", HTMLTableDataCellElement);
+ handle_element!(document, tag, "template", HTMLTemplateElement);
+ handle_element!(document, tag, "textarea", HTMLTextAreaElement);
+ handle_element!(document, tag, "th", HTMLTableHeaderCellElement);
+ handle_element!(document, tag, "time", HTMLTimeElement);
+ handle_element!(document, tag, "title", HTMLTitleElement);
+ handle_element!(document, tag, "tr", HTMLTableRowElement);
+ handle_element!(document, tag, "tt", HTMLElement);
+ handle_element!(document, tag, "track", HTMLTrackElement);
+ handle_element!(document, tag, "u", HTMLElement);
+ handle_element!(document, tag, "ul", HTMLUListElement);
+ handle_element!(document, tag, "var", HTMLElement);
+ handle_element!(document, tag, "video", HTMLVideoElement);
+ handle_element!(document, tag, "wbr", HTMLElement);
+
+ return ElementCast::from_temporary(HTMLUnknownElement::new(tag, document));
+}
+
+pub fn parse_html(page: &Page,
+ document: &JSRef<Document>,
+ url: Url,
+ resource_task: ResourceTask)
+ -> HtmlParserResult {
+ debug!("Hubbub: parsing {:?}", url);
+ // Spawn a CSS parser to receive links to CSS style sheets.
+
+ let (discovery_chan, discovery_port) = channel();
+ let stylesheet_chan = discovery_chan.clone();
+ let (css_chan, css_msg_port) = channel();
+ spawn_named("parse_html:css", proc() {
+ css_link_listener(stylesheet_chan, css_msg_port);
+ });
+
+ // Spawn a JS parser to receive JavaScript.
+ let resource_task2 = resource_task.clone();
+ let js_result_chan = discovery_chan.clone();
+ let (js_chan, js_msg_port) = channel();
+ spawn_named("parse_html:js", proc() {
+ js_script_listener(js_result_chan, js_msg_port, resource_task2.clone());
+ });
+
+ // Wait for the LoadResponse so that the parser knows the final URL.
+ let (input_chan, input_port) = channel();
+ resource_task.send(Load(LoadData::new(url.clone()), input_chan));
+ let load_response = input_port.recv();
+
+ debug!("Fetched page; metadata is {:?}", load_response.metadata);
+
+ load_response.metadata.headers.map(|headers| {
+ let header = headers.iter().find(|h|
+ h.header_name().as_slice().to_ascii_lower() == "last-modified".to_string()
+ );
+
+ match header {
+ Some(h) => document.set_last_modified(
+ parse_last_modified(h.header_value().as_slice())),
+ None => {},
+ };
+ });
+
+ let base_url = &load_response.metadata.final_url;
+
+ {
+ // Store the final URL before we start parsing, so that DOM routines
+ // (e.g. HTMLImageElement::update_image) can resolve relative URLs
+ // correctly.
+ *page.mut_url() = Some((base_url.clone(), true));
+ }
+
+ let mut parser = build_parser(unsafe { document.to_hubbub_node() });
+ debug!("created parser");
+
+ let (css_chan2, js_chan2) = (css_chan.clone(), js_chan.clone());
+
+ let doc_cell = RefCell::new(document);
+
+ let mut tree_handler = hubbub::TreeHandler {
+ create_comment: |data: String| {
+ debug!("create comment");
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow();
+ let tmp = &*tmp_borrow;
+ let comment = Comment::new(data, *tmp).root();
+ let comment: &JSRef<Node> = NodeCast::from_ref(&*comment);
+ unsafe { comment.to_hubbub_node() }
+ },
+ create_doctype: |box hubbub::Doctype { name: name, public_id: public_id, system_id: system_id, ..}: Box<hubbub::Doctype>| {
+ debug!("create doctype");
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow();
+ let tmp = &*tmp_borrow;
+ let doctype_node = DocumentType::new(name, public_id, system_id, *tmp).root();
+ unsafe {
+ doctype_node.deref().to_hubbub_node()
+ }
+ },
+ create_element: |tag: Box<hubbub::Tag>| {
+ debug!("create element {}", tag.name);
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow();
+ let tmp = &*tmp_borrow;
+ let namespace = match tag.ns {
+ HtmlNs => namespace::HTML,
+ MathMlNs => namespace::MathML,
+ SvgNs => namespace::SVG,
+ ns => fail!("Not expecting namespace {:?}", ns),
+ };
+ let element: Root<Element> = build_element_from_tag(tag.name.clone(), namespace, *tmp).root();
+
+ debug!("-- attach attrs");
+ for attr in tag.attributes.iter() {
+ let (namespace, prefix) = match attr.ns {
+ NullNs => (namespace::Null, None),
+ XLinkNs => (namespace::XLink, Some("xlink")),
+ XmlNs => (namespace::XML, Some("xml")),
+ XmlNsNs => (namespace::XMLNS, Some("xmlns")),
+ ns => fail!("Not expecting namespace {:?}", ns),
+ };
+ element.set_attribute_from_parser(Atom::from_slice(attr.name.as_slice()),
+ attr.value.clone(),
+ namespace,
+ prefix.map(|p| p.to_string()));
+ }
+
+ //FIXME: workaround for https://github.com/mozilla/rust/issues/13246;
+ // we get unrooting order failures if these are inside the match.
+ let rel = {
+ let rel = element.deref().get_attribute(Null, "rel").root();
+ rel.map(|a| a.deref().Value())
+ };
+ let href = {
+ let href= element.deref().get_attribute(Null, "href").root();
+ href.map(|a| a.deref().Value())
+ };
+
+ // Spawn additional parsing, network loads, etc. from tag and attrs
+ let type_id = {
+ let node: &JSRef<Node> = NodeCast::from_ref(&*element);
+ node.type_id()
+ };
+ match type_id {
+ // Handle CSS style sheets from <link> elements
+ ElementNodeTypeId(HTMLLinkElementTypeId) => {
+ match (rel, href) {
+ (Some(ref rel), Some(ref href)) => {
+ if rel.as_slice()
+ .split(HTML_SPACE_CHARACTERS.as_slice())
+ .any(|s| {
+ s.as_slice().eq_ignore_ascii_case("stylesheet")
+ }) {
+ debug!("found CSS stylesheet: {:s}", *href);
+ match UrlParser::new().base_url(base_url).parse(href.as_slice()) {
+ Ok(url) => css_chan2.send(CSSTaskNewFile(
+ UrlProvenance(url, resource_task.clone()))),
+ Err(e) => debug!("Parsing url {:s} failed: {:?}", *href, e)
+ };
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {}
+ }
+
+ unsafe { element.deref().to_hubbub_node() }
+ },
+ create_text: |data: String| {
+ debug!("create text");
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow();
+ let tmp = &*tmp_borrow;
+ let text = Text::new(data, *tmp).root();
+ unsafe { text.deref().to_hubbub_node() }
+ },
+ ref_node: |_| {},
+ unref_node: |_| {},
+ append_child: |parent: hubbub::NodeDataPtr, child: hubbub::NodeDataPtr| {
+ unsafe {
+ debug!("append child {:x} {:x}", parent, child);
+ let child: Root<Node> = from_hubbub_node(child).root();
+ let parent: Root<Node> = from_hubbub_node(parent).root();
+ assert!(parent.deref().AppendChild(&*child).is_ok());
+ }
+ child
+ },
+ insert_before: |_parent, _child| {
+ debug!("insert before");
+ 0u
+ },
+ remove_child: |_parent, _child| {
+ debug!("remove child");
+ 0u
+ },
+ clone_node: |_node, deep| {
+ debug!("clone node");
+ if deep { error!("-- deep clone unimplemented"); }
+ fail!("clone node unimplemented")
+ },
+ reparent_children: |_node, _new_parent| {
+ debug!("reparent children");
+ 0u
+ },
+ get_parent: |_node, _element_only| {
+ debug!("get parent");
+ 0u
+ },
+ has_children: |_node| {
+ debug!("has children");
+ false
+ },
+ form_associate: |_form, _node| {
+ debug!("form associate");
+ },
+ add_attributes: |_node, _attributes| {
+ debug!("add attributes");
+ },
+ set_quirks_mode: |mode| {
+ debug!("set quirks mode");
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow_mut();
+ let tmp = &*tmp_borrow;
+ tmp.set_quirks_mode(mode);
+ },
+ encoding_change: |encname| {
+ debug!("encoding change");
+ // NOTE: tmp vars are workaround for lifetime issues. Both required.
+ let tmp_borrow = doc_cell.borrow_mut();
+ let tmp = &*tmp_borrow;
+ tmp.set_encoding_name(encname);
+ },
+ complete_script: |script| {
+ unsafe {
+ let script = from_hubbub_node::<Node>(script).root();
+ let script: Option<&JSRef<HTMLScriptElement>> =
+ HTMLScriptElementCast::to_ref(&*script);
+ let script = match script {
+ Some(script) if script.is_javascript() => script,
+ _ => return,
+ };
+
+ let script_element: &JSRef<Element> = ElementCast::from_ref(script);
+ match script_element.get_attribute(Null, "src").root() {
+ Some(src) => {
+ debug!("found script: {:s}", src.deref().Value());
+ match UrlParser::new().base_url(base_url)
+ .parse(src.deref().value().as_slice()) {
+ Ok(new_url) => js_chan2.send(JSTaskNewFile(new_url)),
+ Err(e) => debug!("Parsing url {:s} failed: {:?}", src.deref().Value(), e)
+ };
+ }
+ None => {
+ let mut data = String::new();
+ let scriptnode: &JSRef<Node> = NodeCast::from_ref(script);
+ debug!("iterating over children {:?}", scriptnode.first_child());
+ for child in scriptnode.children() {
+ debug!("child = {:?}", child);
+ let text: &JSRef<Text> = TextCast::to_ref(&child).unwrap();
+ data.push_str(text.deref().characterdata.data.deref().borrow().as_slice());
+ }
+
+ debug!("script data = {:?}", data);
+ js_chan2.send(JSTaskNewInlineScript(data, base_url.clone()));
+ }
+ }
+ }
+ debug!("complete script");
+ },
+ complete_style: |_| {
+ // style parsing is handled in element::notify_child_list_changed.
+ },
+ };
+ parser.set_tree_handler(&mut tree_handler);
+ debug!("set tree handler");
+ debug!("loaded page");
+ match load_response.metadata.content_type {
+ Some((ref t, _)) if t.as_slice().eq_ignore_ascii_case("image") => {
+ let page = format!("<html><body><img src='{:s}' /></body></html>", base_url.serialize());
+ parser.parse_chunk(page.into_bytes().as_slice());
+ },
+ _ => loop {
+ match load_response.progress_port.recv() {
+ Payload(data) => {
+ debug!("received data");
+ parser.parse_chunk(data.as_slice());
+ }
+ Done(Err(err)) => {
+ fail!("Failed to load page URL {:s}, error: {:s}", url.serialize(), err);
+ }
+ Done(..) => {
+ break;
+ }
+ }
+ }
+ }
+
+ debug!("finished parsing");
+ css_chan.send(CSSTaskExit);
+ js_chan.send(JSTaskExit);
+
+ HtmlParserResult {
+ discovery_port: discovery_port,
+ }
+}
+
+fn build_parser<'a>(node: hubbub::NodeDataPtr) -> hubbub::Parser<'a> {
+ let mut parser = hubbub::Parser::new("UTF-8", false);
+ parser.set_document_node(node);
+ parser.enable_scripting(true);
+ parser.enable_styling(true);
+ parser
+}
+
diff --git a/components/script/layout_interface.rs b/components/script/layout_interface.rs
new file mode 100644
index 00000000000..1e5e23f9c9a
--- /dev/null
+++ b/components/script/layout_interface.rs
@@ -0,0 +1,204 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The high-level interface from script to layout. Using this abstract interface helps reduce
+/// coupling between these two components, and enables the DOM to be placed in a separate crate
+/// from layout.
+
+use dom::bindings::js::JS;
+use dom::node::{Node, LayoutDataRef};
+
+use geom::point::Point2D;
+use geom::rect::Rect;
+use libc::c_void;
+use script_traits::{ScriptControlChan, OpaqueScriptLayoutChannel};
+use servo_msg::constellation_msg::WindowSizeData;
+use servo_util::geometry::Au;
+use std::any::{Any, AnyRefExt};
+use std::cmp;
+use std::comm::{channel, Receiver, Sender};
+use std::owned::BoxAny;
+use style::Stylesheet;
+use url::Url;
+
+use serialize::{Encodable, Encoder};
+
+/// Asynchronous messages that script can send to layout.
+pub enum Msg {
+ /// Adds the given stylesheet to the document.
+ AddStylesheetMsg(Stylesheet),
+
+ /// Requests a reflow.
+ ReflowMsg(Box<Reflow>),
+
+ /// Get an RPC interface.
+ GetRPCMsg(Sender<Box<LayoutRPC + Send>>),
+
+ /// Destroys layout data associated with a DOM node.
+ ///
+ /// TODO(pcwalton): Maybe think about batching to avoid message traffic.
+ ReapLayoutDataMsg(LayoutDataRef),
+
+ /// Requests that the layout task enter a quiescent state in which no more messages are
+ /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when
+ /// this happens.
+ PrepareToExitMsg(Sender<()>),
+
+ /// Requests that the layout task immediately shut down. There must be no more nodes left after
+ /// this, or layout will crash.
+ ExitNowMsg,
+}
+
+/// Synchronous messages that script can send to layout.
+///
+/// In general, you should use messages to talk to Layout. Use the RPC interface
+/// if and only if the work is
+///
+/// 1) read-only with respect to LayoutTaskData,
+/// 2) small,
+// 3) and really needs to be fast.
+pub trait LayoutRPC {
+ /// Requests the dimensions of the content box, as in the `getBoundingClientRect()` call.
+ fn content_box(&self, node: TrustedNodeAddress) -> ContentBoxResponse;
+ /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call.
+ fn content_boxes(&self, node: TrustedNodeAddress) -> ContentBoxesResponse;
+ /// Requests the node containing the point of interest
+ fn hit_test(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<HitTestResponse, ()>;
+ fn mouse_over(&self, node: TrustedNodeAddress, point: Point2D<f32>) -> Result<MouseOverResponse, ()>;
+}
+
+/// The address of a node known to be valid. These must only be sent from content -> layout,
+/// because we do not trust layout.
+pub struct TrustedNodeAddress(pub *const c_void);
+
+impl<S: Encoder<E>, E> Encodable<S, E> for TrustedNodeAddress {
+ fn encode(&self, s: &mut S) -> Result<(), E> {
+ let TrustedNodeAddress(addr) = *self;
+ let node = addr as *const Node;
+ unsafe {
+ JS::from_raw(node).encode(s)
+ }
+ }
+}
+
+/// The address of a node. Layout sends these back. They must be validated via
+/// `from_untrusted_node_address` before they can be used, because we do not trust layout.
+pub type UntrustedNodeAddress = *const c_void;
+
+pub struct ContentBoxResponse(pub Rect<Au>);
+pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
+pub struct HitTestResponse(pub UntrustedNodeAddress);
+pub struct MouseOverResponse(pub Vec<UntrustedNodeAddress>);
+
+/// Determines which part of the
+#[deriving(PartialEq, PartialOrd, Eq, Ord, Encodable)]
+pub enum DocumentDamageLevel {
+ /// Reflow, but do not perform CSS selector matching.
+ ReflowDocumentDamage,
+ /// Perform CSS selector matching and reflow.
+ MatchSelectorsDocumentDamage,
+ /// Content changed; set full style damage and do the above.
+ ContentChangedDocumentDamage,
+}
+
+impl DocumentDamageLevel {
+ /// Sets this damage to the maximum of this damage and the given damage.
+ pub fn add(&mut self, new_damage: DocumentDamageLevel) {
+ *self = cmp::max(*self, new_damage);
+ }
+}
+
+/// What parts of the document have changed, as far as the script task can tell.
+///
+/// Note that this is fairly coarse-grained and is separate from layout's notion of the document
+#[deriving(Encodable)]
+pub struct DocumentDamage {
+ /// The topmost node in the tree that has changed.
+ pub root: TrustedNodeAddress,
+ /// The amount of damage that occurred.
+ pub level: DocumentDamageLevel,
+}
+
+/// Why we're doing reflow.
+#[deriving(PartialEq)]
+pub enum ReflowGoal {
+ /// We're reflowing in order to send a display list to the screen.
+ ReflowForDisplay,
+ /// We're reflowing in order to satisfy a script query. No display list will be created.
+ ReflowForScriptQuery,
+}
+
+/// Information needed for a reflow.
+pub struct Reflow {
+ /// The document node.
+ pub document_root: TrustedNodeAddress,
+ /// The style changes that need to be done.
+ pub damage: DocumentDamage,
+ /// The goal of reflow: either to render to the screen or to flush layout info for script.
+ pub goal: ReflowGoal,
+ /// The URL of the page.
+ pub url: Url,
+ /// The channel through which messages can be sent back to the script task.
+ pub script_chan: ScriptControlChan,
+ /// The current window size.
+ pub window_size: WindowSizeData,
+ /// The channel that we send a notification to.
+ pub script_join_chan: Sender<()>,
+ /// Unique identifier
+ pub id: uint
+}
+
+/// Encapsulates a channel to the layout task.
+#[deriving(Clone)]
+pub struct LayoutChan(pub Sender<Msg>);
+
+impl LayoutChan {
+ pub fn new() -> (Receiver<Msg>, LayoutChan) {
+ let (chan, port) = channel();
+ (port, LayoutChan(chan))
+ }
+}
+
+/// A trait to manage opaque references to script<->layout channels without needing
+/// to expose the message type to crates that don't need to know about them.
+pub trait ScriptLayoutChan {
+ fn new(sender: Sender<Msg>, receiver: Receiver<Msg>) -> Self;
+ fn sender(&self) -> Sender<Msg>;
+ fn receiver(self) -> Receiver<Msg>;
+}
+
+impl ScriptLayoutChan for OpaqueScriptLayoutChannel {
+ fn new(sender: Sender<Msg>, receiver: Receiver<Msg>) -> OpaqueScriptLayoutChannel {
+ let inner = (box sender as Box<Any+Send>, box receiver as Box<Any+Send>);
+ OpaqueScriptLayoutChannel(inner)
+ }
+
+ fn sender(&self) -> Sender<Msg> {
+ let &OpaqueScriptLayoutChannel((ref sender, _)) = self;
+ (*sender.downcast_ref::<Sender<Msg>>().unwrap()).clone()
+ }
+
+ fn receiver(self) -> Receiver<Msg> {
+ let OpaqueScriptLayoutChannel((_, receiver)) = self;
+ *receiver.downcast::<Receiver<Msg>>().unwrap()
+ }
+}
+
+#[test]
+fn test_add_damage() {
+ fn assert_add(mut a: DocumentDamageLevel, b: DocumentDamageLevel,
+ result: DocumentDamageLevel) {
+ a.add(b);
+ assert!(a == result);
+ }
+
+ assert_add(ReflowDocumentDamage, ReflowDocumentDamage, ReflowDocumentDamage);
+ assert_add(ContentChangedDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
+ assert_add(ReflowDocumentDamage, MatchSelectorsDocumentDamage, MatchSelectorsDocumentDamage);
+ assert_add(MatchSelectorsDocumentDamage, ReflowDocumentDamage, MatchSelectorsDocumentDamage);
+ assert_add(ReflowDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
+ assert_add(ContentChangedDocumentDamage, ReflowDocumentDamage, ContentChangedDocumentDamage);
+ assert_add(MatchSelectorsDocumentDamage, ContentChangedDocumentDamage, ContentChangedDocumentDamage);
+ assert_add(ContentChangedDocumentDamage, MatchSelectorsDocumentDamage, ContentChangedDocumentDamage);
+}
diff --git a/components/script/lib.rs b/components/script/lib.rs
new file mode 100644
index 00000000000..6e44b295968
--- /dev/null
+++ b/components/script/lib.rs
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+#![feature(globs, macro_rules, struct_variant, phase, unsafe_destructor)]
+
+#![feature(phase)]
+
+#![doc="The script crate contains all matters DOM."]
+
+#![allow(non_snake_case_functions)]
+
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+extern crate cssparser;
+extern crate collections;
+extern crate geom;
+extern crate hubbub;
+extern crate encoding;
+extern crate http;
+extern crate js;
+extern crate libc;
+extern crate native;
+extern crate net;
+extern crate rustrt;
+extern crate serialize;
+extern crate time;
+extern crate canvas;
+extern crate script_traits;
+#[phase(plugin)]
+extern crate servo_macros = "macros";
+extern crate servo_net = "net";
+extern crate servo_util = "util";
+extern crate style;
+extern crate sync;
+extern crate servo_msg = "msg";
+extern crate url;
+
+pub mod cors;
+
+/// The implementation of the DOM.
+pub mod dom {
+ /// The code to expose the DOM to JavaScript through IDL bindings.
+ pub mod bindings {
+ pub mod global;
+ pub mod js;
+ pub mod utils;
+ pub mod callback;
+ pub mod error;
+ pub mod conversions;
+ mod proxyhandler;
+ pub mod str;
+ pub mod trace;
+
+ /// Generated JS-Rust bindings.
+ pub mod codegen {
+ pub mod Bindings;
+ pub mod InterfaceTypes;
+ pub mod InheritTypes;
+ pub mod PrototypeList;
+ pub mod RegisterBindings;
+ pub mod UnionTypes;
+ }
+ }
+
+ #[path="bindings/codegen/InterfaceTypes.rs"]
+ pub mod types;
+ pub mod macros;
+
+ pub mod attr;
+ pub mod blob;
+ pub mod browsercontext;
+ pub mod canvasrenderingcontext2d;
+ pub mod characterdata;
+ pub mod domrect;
+ pub mod domrectlist;
+ pub mod comment;
+ pub mod console;
+ pub mod customevent;
+ pub mod dedicatedworkerglobalscope;
+ pub mod document;
+ pub mod documentfragment;
+ pub mod documenttype;
+ pub mod domexception;
+ pub mod domimplementation;
+ pub mod domparser;
+ pub mod domtokenlist;
+ pub mod element;
+ pub mod event;
+ pub mod eventdispatcher;
+ pub mod eventtarget;
+ pub mod file;
+ pub mod formdata;
+ pub mod htmlanchorelement;
+ pub mod htmlappletelement;
+ pub mod htmlareaelement;
+ pub mod htmlaudioelement;
+ pub mod htmlbaseelement;
+ pub mod htmlbodyelement;
+ pub mod htmlbrelement;
+ pub mod htmlbuttonelement;
+ pub mod htmlcanvaselement;
+ pub mod htmlcollection;
+ pub mod htmldataelement;
+ pub mod htmldatalistelement;
+ pub mod htmldirectoryelement;
+ pub mod htmldivelement;
+ pub mod htmldlistelement;
+ pub mod htmlelement;
+ pub mod htmlembedelement;
+ pub mod htmlfieldsetelement;
+ pub mod htmlfontelement;
+ pub mod htmlformelement;
+ pub mod htmlframeelement;
+ pub mod htmlframesetelement;
+ pub mod htmlheadelement;
+ pub mod htmlheadingelement;
+ pub mod htmlhrelement;
+ pub mod htmlhtmlelement;
+ pub mod htmliframeelement;
+ pub mod htmlimageelement;
+ pub mod htmlinputelement;
+ pub mod htmllabelelement;
+ pub mod htmllegendelement;
+ pub mod htmllielement;
+ pub mod htmllinkelement;
+ pub mod htmlmapelement;
+ pub mod htmlmediaelement;
+ pub mod htmlmetaelement;
+ pub mod htmlmeterelement;
+ pub mod htmlmodelement;
+ pub mod htmlobjectelement;
+ pub mod htmlolistelement;
+ pub mod htmloptgroupelement;
+ pub mod htmloptionelement;
+ pub mod htmloutputelement;
+ pub mod htmlparagraphelement;
+ pub mod htmlparamelement;
+ pub mod htmlpreelement;
+ pub mod htmlprogresselement;
+ pub mod htmlquoteelement;
+ pub mod htmlscriptelement;
+ pub mod htmlselectelement;
+ pub mod htmlserializer;
+ pub mod htmlspanelement;
+ pub mod htmlsourceelement;
+ pub mod htmlstyleelement;
+ pub mod htmltableelement;
+ pub mod htmltablecaptionelement;
+ pub mod htmltablecellelement;
+ pub mod htmltabledatacellelement;
+ pub mod htmltableheadercellelement;
+ pub mod htmltablecolelement;
+ pub mod htmltablerowelement;
+ pub mod htmltablesectionelement;
+ pub mod htmltemplateelement;
+ pub mod htmltextareaelement;
+ pub mod htmltimeelement;
+ pub mod htmltitleelement;
+ pub mod htmltrackelement;
+ pub mod htmlulistelement;
+ pub mod htmlvideoelement;
+ pub mod htmlunknownelement;
+ pub mod location;
+ pub mod messageevent;
+ pub mod mouseevent;
+ pub mod namednodemap;
+ pub mod navigator;
+ pub mod node;
+ pub mod nodeiterator;
+ pub mod nodelist;
+ pub mod processinginstruction;
+ pub mod performance;
+ pub mod performancetiming;
+ pub mod progressevent;
+ pub mod range;
+ pub mod screen;
+ pub mod text;
+ pub mod treewalker;
+ pub mod uievent;
+ pub mod urlsearchparams;
+ pub mod validitystate;
+ pub mod virtualmethods;
+ pub mod window;
+ pub mod worker;
+ pub mod workerglobalscope;
+ pub mod workerlocation;
+ pub mod workernavigator;
+ pub mod xmlhttprequest;
+ pub mod xmlhttprequesteventtarget;
+ pub mod xmlhttprequestupload;
+
+ pub mod testbinding;
+}
+
+/// Parsers for HTML and CSS.
+pub mod html {
+ pub mod cssparse;
+ pub mod hubbub_html_parser;
+}
+
+pub mod layout_interface;
+pub mod page;
+pub mod script_task;
diff --git a/components/script/makefile.cargo b/components/script/makefile.cargo
new file mode 100644
index 00000000000..652bcab9000
--- /dev/null
+++ b/components/script/makefile.cargo
@@ -0,0 +1,45 @@
+# Recursive wildcard function
+# http://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
+rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) \
+ $(filter $(subst *,%,$2),$d))
+
+PYTHON = $(shell which python2.7 2>/dev/null || echo python)
+BINDINGS_SRC = $(shell pwd)/dom/bindings/codegen
+WEBIDLS_SRC = $(shell pwd)/dom/webidls
+WEBIDLS = $(call rwildcard,$(WEBIDLS_SRC),*.webidl)
+BINDINGS = $(patsubst %.webidl,%Binding.rs,$(WEBIDLS))
+AUTOGEN_SRC = $(foreach var,$(BINDINGS),$(subst $(WEBIDLS_SRC),$(BINDINGS_SRC)/Bindings,$(var)))
+
+CACHE_DIR = $(BINDINGS_SRC)/_cache
+
+bindinggen_dependencies := $(addprefix $(BINDINGS_SRC)/,BindingGen.py Bindings.conf Configuration.py CodegenRust.py parser/WebIDL.py ParserResults.pkl Bindings/.done)
+
+globalgen_dependencies := $(addprefix $(BINDINGS_SRC)/,GlobalGen.py Bindings.conf Configuration.py CodegenRust.py parser/WebIDL.py) $(CACHE_DIR)/.done $(BINDINGS_SRC)/Bindings/.done
+
+.PHONY: all
+all: $(AUTOGEN_SRC)
+
+$(BINDINGS_SRC)/Bindings/.done:
+ mkdir -p $(BINDINGS_SRC)/Bindings
+ touch $@
+
+$(CACHE_DIR)/.done:
+ mkdir -p $(CACHE_DIR)
+ touch $@
+
+$(BINDINGS_SRC)/ParserResults.pkl: $(globalgen_dependencies) $(WEBIDLS)
+ $(PYTHON) $(BINDINGS_SRC)/pythonpath.py \
+ -I$(BINDINGS_SRC)/parser -I$(BINDINGS_SRC)/ply \
+ -D$(BINDINGS_SRC) \
+ $(BINDINGS_SRC)/GlobalGen.py $(BINDINGS_SRC)/Bindings.conf . \
+ --cachedir=$(CACHE_DIR) \
+ $(WEBIDLS)
+
+$(AUTOGEN_SRC): $(BINDINGS_SRC)/Bindings/%Binding.rs: $(bindinggen_dependencies) \
+ $(addprefix $(WEBIDLS_SRC)/,%.webidl)
+ $(PYTHON) $(BINDINGS_SRC)/pythonpath.py \
+ -I$(BINDINGS_SRC)/parser -I$(BINDINGS_SRC)/ply \
+ -D$(BINDINGS_SRC) \
+ $(BINDINGS_SRC)/BindingGen.py \
+ $(BINDINGS_SRC)/Bindings.conf Bindings/$*Binding $(addprefix $(WEBIDLS_SRC)/,$*.webidl)
+ touch $@
diff --git a/components/script/page.rs b/components/script/page.rs
new file mode 100644
index 00000000000..633a7de204b
--- /dev/null
+++ b/components/script/page.rs
@@ -0,0 +1,437 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
+use dom::bindings::codegen::InheritTypes::{NodeCast, ElementCast};
+use dom::bindings::js::{JS, JSRef, Temporary};
+use dom::bindings::js::OptionalRootable;
+use dom::bindings::trace::{Traceable, Untraceable};
+use dom::bindings::utils::GlobalStaticData;
+use dom::document::{Document, DocumentHelpers};
+use dom::element::{Element, AttributeHandlers};
+use dom::node::{Node, NodeHelpers};
+use dom::window::Window;
+use layout_interface::{DocumentDamage};
+use layout_interface::{DocumentDamageLevel, HitTestResponse, MouseOverResponse};
+use layout_interface::{GetRPCMsg, LayoutChan, LayoutRPC};
+use layout_interface::{Reflow, ReflowGoal, ReflowMsg};
+use layout_interface::UntrustedNodeAddress;
+use script_traits::ScriptControlChan;
+
+use geom::point::Point2D;
+use js::rust::Cx;
+use servo_msg::compositor_msg::PerformingLayout;
+use servo_msg::compositor_msg::ScriptListener;
+use servo_msg::constellation_msg::{ConstellationChan, WindowSizeData};
+use servo_msg::constellation_msg::{PipelineId, SubpageId};
+use servo_net::resource_task::ResourceTask;
+use servo_util::namespace::Null;
+use servo_util::str::DOMString;
+use std::cell::{Cell, RefCell, Ref, RefMut};
+use std::comm::{channel, Receiver, Empty, Disconnected};
+use std::mem::replace;
+use std::rc::Rc;
+use url::Url;
+
+use serialize::{Encoder, Encodable};
+
+/// Encapsulates a handle to a frame and its associated layout information.
+#[deriving(Encodable)]
+pub struct Page {
+ /// Pipeline id associated with this page.
+ pub id: PipelineId,
+
+ /// Subpage id associated with this page, if any.
+ pub subpage_id: Option<SubpageId>,
+
+ /// Unique id for last reflow request; used for confirming completion reply.
+ pub last_reflow_id: Traceable<Cell<uint>>,
+
+ /// The outermost frame containing the document, window, and page URL.
+ pub frame: Traceable<RefCell<Option<Frame>>>,
+
+ /// A handle for communicating messages to the layout task.
+ pub layout_chan: Untraceable<LayoutChan>,
+
+ /// A handle to perform RPC calls into the layout, quickly.
+ pub layout_rpc: Untraceable<Box<LayoutRPC>>,
+
+ /// The port that we will use to join layout. If this is `None`, then layout is not running.
+ pub layout_join_port: Untraceable<RefCell<Option<Receiver<()>>>>,
+
+ /// What parts of the document are dirty, if any.
+ damage: Traceable<RefCell<Option<DocumentDamage>>>,
+
+ /// The current size of the window, in pixels.
+ pub window_size: Traceable<Cell<WindowSizeData>>,
+
+ js_info: Traceable<RefCell<Option<JSPageInfo>>>,
+
+ /// Cached copy of the most recent url loaded by the script
+ /// TODO(tkuehn): this currently does not follow any particular caching policy
+ /// and simply caches pages forever (!). The bool indicates if reflow is required
+ /// when reloading.
+ url: Untraceable<RefCell<Option<(Url, bool)>>>,
+
+ next_subpage_id: Traceable<Cell<SubpageId>>,
+
+ /// Pending resize event, if any.
+ pub resize_event: Untraceable<Cell<Option<WindowSizeData>>>,
+
+ /// Pending scroll to fragment event, if any
+ pub fragment_node: Cell<Option<JS<Element>>>,
+
+ /// Associated resource task for use by DOM objects like XMLHttpRequest
+ pub resource_task: Untraceable<ResourceTask>,
+
+ /// A handle for communicating messages to the constellation task.
+ pub constellation_chan: Untraceable<ConstellationChan>,
+
+ // Child Pages.
+ pub children: Traceable<RefCell<Vec<Rc<Page>>>>,
+}
+
+pub struct PageIterator {
+ stack: Vec<Rc<Page>>,
+}
+
+pub trait IterablePage {
+ fn iter(&self) -> PageIterator;
+ fn find(&self, id: PipelineId) -> Option<Rc<Page>>;
+}
+
+impl IterablePage for Rc<Page> {
+ fn iter(&self) -> PageIterator {
+ PageIterator {
+ stack: vec!(self.clone()),
+ }
+ }
+ fn find(&self, id: PipelineId) -> Option<Rc<Page>> {
+ if self.id == id { return Some(self.clone()); }
+ for page in self.children.deref().borrow().iter() {
+ let found = page.find(id);
+ if found.is_some() { return found; }
+ }
+ None
+ }
+
+}
+
+impl Page {
+ pub fn new(id: PipelineId, subpage_id: Option<SubpageId>,
+ layout_chan: LayoutChan,
+ window_size: WindowSizeData,
+ resource_task: ResourceTask,
+ constellation_chan: ConstellationChan,
+ js_context: Rc<Cx>) -> Page {
+ let js_info = JSPageInfo {
+ dom_static: GlobalStaticData(),
+ js_context: Untraceable::new(js_context),
+ };
+ let layout_rpc: Box<LayoutRPC> = {
+ let (rpc_send, rpc_recv) = channel();
+ let LayoutChan(ref lchan) = layout_chan;
+ lchan.send(GetRPCMsg(rpc_send));
+ rpc_recv.recv()
+ };
+ Page {
+ id: id,
+ subpage_id: subpage_id,
+ frame: Traceable::new(RefCell::new(None)),
+ layout_chan: Untraceable::new(layout_chan),
+ layout_rpc: Untraceable::new(layout_rpc),
+ layout_join_port: Untraceable::new(RefCell::new(None)),
+ damage: Traceable::new(RefCell::new(None)),
+ window_size: Traceable::new(Cell::new(window_size)),
+ js_info: Traceable::new(RefCell::new(Some(js_info))),
+ url: Untraceable::new(RefCell::new(None)),
+ next_subpage_id: Traceable::new(Cell::new(SubpageId(0))),
+ resize_event: Untraceable::new(Cell::new(None)),
+ fragment_node: Cell::new(None),
+ last_reflow_id: Traceable::new(Cell::new(0)),
+ resource_task: Untraceable::new(resource_task),
+ constellation_chan: Untraceable::new(constellation_chan),
+ children: Traceable::new(RefCell::new(vec!())),
+ }
+ }
+
+ // must handle root case separately
+ pub fn remove(&self, id: PipelineId) -> Option<Rc<Page>> {
+ let remove_idx = {
+ self.children
+ .deref()
+ .borrow_mut()
+ .mut_iter()
+ .enumerate()
+ .find(|&(_idx, ref page_tree)| {
+ // FIXME: page_tree has a lifetime such that it's unusable for anything.
+ let page_tree_id = page_tree.id;
+ page_tree_id == id
+ })
+ .map(|(idx, _)| idx)
+ };
+ match remove_idx {
+ Some(idx) => return Some(self.children.deref().borrow_mut().remove(idx).unwrap()),
+ None => {
+ for page_tree in self.children.deref().borrow_mut().mut_iter() {
+ match page_tree.remove(id) {
+ found @ Some(_) => return found,
+ None => (), // keep going...
+ }
+ }
+ }
+ }
+ None
+ }
+}
+
+impl Iterator<Rc<Page>> for PageIterator {
+ fn next(&mut self) -> Option<Rc<Page>> {
+ if !self.stack.is_empty() {
+ let next = self.stack.pop().unwrap();
+ for child in next.children.deref().borrow().iter() {
+ self.stack.push(child.clone());
+ }
+ Some(next.clone())
+ } else {
+ None
+ }
+ }
+}
+
+impl Page {
+ pub fn mut_js_info<'a>(&'a self) -> RefMut<'a, Option<JSPageInfo>> {
+ self.js_info.deref().borrow_mut()
+ }
+
+ pub fn js_info<'a>(&'a self) -> Ref<'a, Option<JSPageInfo>> {
+ self.js_info.deref().borrow()
+ }
+
+ pub fn url<'a>(&'a self) -> Ref<'a, Option<(Url, bool)>> {
+ self.url.deref().borrow()
+ }
+
+ pub fn mut_url<'a>(&'a self) -> RefMut<'a, Option<(Url, bool)>> {
+ self.url.deref().borrow_mut()
+ }
+
+ pub fn frame<'a>(&'a self) -> Ref<'a, Option<Frame>> {
+ self.frame.deref().borrow()
+ }
+
+ pub fn mut_frame<'a>(&'a self) -> RefMut<'a, Option<Frame>> {
+ self.frame.deref().borrow_mut()
+ }
+
+ pub fn get_next_subpage_id(&self) -> SubpageId {
+ let subpage_id = self.next_subpage_id.deref().get();
+ let SubpageId(id_num) = subpage_id;
+ self.next_subpage_id.deref().set(SubpageId(id_num + 1));
+ subpage_id
+ }
+
+ /// Adds the given damage.
+ pub fn damage(&self, level: DocumentDamageLevel) {
+ let root = match *self.frame() {
+ None => return,
+ Some(ref frame) => frame.document.root().GetDocumentElement()
+ };
+ match root.root() {
+ None => {},
+ Some(root) => {
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ let mut damage = *self.damage.deref().borrow_mut();
+ match damage {
+ None => {}
+ Some(ref mut damage) => {
+ // FIXME(pcwalton): This is wrong. We should trace up to the nearest ancestor.
+ damage.root = root.to_trusted_node_address();
+ damage.level.add(level);
+ return
+ }
+ }
+
+ *self.damage.deref().borrow_mut() = Some(DocumentDamage {
+ root: root.to_trusted_node_address(),
+ level: level,
+ })
+ }
+ };
+ }
+
+ pub fn get_url(&self) -> Url {
+ self.url().get_ref().ref0().clone()
+ }
+
+ // FIXME(cgaebel): join_layout is racey. What if the compositor triggers a
+ // reflow between the "join complete" message and returning from this
+ // function?
+
+ /// Sends a ping to layout and waits for the response. The response will arrive when the
+ /// layout task has finished any pending request messages.
+ pub fn join_layout(&self) {
+ let mut layout_join_port = self.layout_join_port.deref().borrow_mut();
+ if layout_join_port.is_some() {
+ let join_port = replace(&mut *layout_join_port, None);
+ match join_port {
+ Some(ref join_port) => {
+ match join_port.try_recv() {
+ Err(Empty) => {
+ info!("script: waiting on layout");
+ join_port.recv();
+ }
+ Ok(_) => {}
+ Err(Disconnected) => {
+ fail!("Layout task failed while script was waiting for a result.");
+ }
+ }
+
+ debug!("script: layout joined")
+ }
+ None => fail!("reader forked but no join port?"),
+ }
+ }
+ }
+
+ /// Reflows the page if it's possible to do so. This method will wait until the layout task has
+ /// completed its current action, join the layout task, and then request a new layout run. It
+ /// won't wait for the new layout computation to finish.
+ ///
+ /// If there is no window size yet, the page is presumed invisible and no reflow is performed.
+ ///
+ /// This function fails if there is no root frame.
+ pub fn reflow(&self,
+ goal: ReflowGoal,
+ script_chan: ScriptControlChan,
+ compositor: &ScriptListener) {
+
+ let root = match *self.frame() {
+ None => return,
+ Some(ref frame) => {
+ frame.document.root().GetDocumentElement()
+ }
+ };
+
+ match root.root() {
+ None => {},
+ Some(root) => {
+ debug!("script: performing reflow for goal {:?}", goal);
+
+ // Now, join the layout so that they will see the latest changes we have made.
+ self.join_layout();
+
+ // Tell the user that we're performing layout.
+ compositor.set_ready_state(PerformingLayout);
+
+ // Layout will let us know when it's done.
+ let (join_chan, join_port) = channel();
+ let mut layout_join_port = self.layout_join_port.deref().borrow_mut();
+ *layout_join_port = Some(join_port);
+
+ let last_reflow_id = self.last_reflow_id.deref();
+ last_reflow_id.set(last_reflow_id.get() + 1);
+
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ let mut damage = self.damage.deref().borrow_mut();
+ let window_size = self.window_size.deref().get();
+
+ // Send new document and relevant styles to layout.
+ let reflow = box Reflow {
+ document_root: root.to_trusted_node_address(),
+ url: self.get_url(),
+ goal: goal,
+ window_size: window_size,
+ script_chan: script_chan,
+ script_join_chan: join_chan,
+ damage: replace(&mut *damage, None).unwrap(),
+ id: last_reflow_id.get(),
+ };
+
+ let LayoutChan(ref chan) = *self.layout_chan;
+ chan.send(ReflowMsg(reflow));
+
+ debug!("script: layout forked")
+ }
+ }
+ }
+
+ /// Attempt to find a named element in this page's document.
+ pub fn find_fragment_node(&self, fragid: DOMString) -> Option<Temporary<Element>> {
+ let document = self.frame().get_ref().document.root();
+ match document.deref().GetElementById(fragid.to_string()) {
+ Some(node) => Some(node),
+ None => {
+ let doc_node: &JSRef<Node> = NodeCast::from_ref(&*document);
+ let mut anchors = doc_node.traverse_preorder()
+ .filter(|node| node.is_anchor_element());
+ anchors.find(|node| {
+ let elem: &JSRef<Element> = ElementCast::to_ref(node).unwrap();
+ elem.get_attribute(Null, "name").root().map_or(false, |attr| {
+ attr.deref().value().as_slice() == fragid.as_slice()
+ })
+ }).map(|node| Temporary::from_rooted(ElementCast::to_ref(&node).unwrap()))
+ }
+ }
+ }
+
+ pub fn hit_test(&self, point: &Point2D<f32>) -> Option<UntrustedNodeAddress> {
+ let frame = self.frame();
+ let document = frame.get_ref().document.root();
+ let root = document.deref().GetDocumentElement().root();
+ if root.is_none() {
+ return None;
+ }
+ let root = root.unwrap();
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ let address = match self.layout_rpc.hit_test(root.to_trusted_node_address(), *point) {
+ Ok(HitTestResponse(node_address)) => {
+ Some(node_address)
+ }
+ Err(()) => {
+ debug!("layout query error");
+ None
+ }
+ };
+ address
+ }
+
+ pub fn get_nodes_under_mouse(&self, point: &Point2D<f32>) -> Option<Vec<UntrustedNodeAddress>> {
+ let frame = self.frame();
+ let document = frame.get_ref().document.root();
+ let root = document.deref().GetDocumentElement().root();
+ if root.is_none() {
+ return None;
+ }
+ let root = root.unwrap();
+ let root: &JSRef<Node> = NodeCast::from_ref(&*root);
+ let address = match self.layout_rpc.mouse_over(root.to_trusted_node_address(), *point) {
+ Ok(MouseOverResponse(node_address)) => {
+ Some(node_address)
+ }
+ Err(()) => {
+ None
+ }
+ };
+ address
+ }
+}
+
+/// Information for one frame in the browsing context.
+#[deriving(Encodable)]
+pub struct Frame {
+ /// The document for this frame.
+ pub document: JS<Document>,
+ /// The window object for this frame.
+ pub window: JS<Window>,
+}
+
+/// Encapsulation of the javascript information associated with each frame.
+#[deriving(Encodable)]
+pub struct JSPageInfo {
+ /// Global static data related to the DOM.
+ pub dom_static: GlobalStaticData,
+ /// The JavaScript context.
+ pub js_context: Untraceable<Rc<Cx>>,
+}
diff --git a/components/script/script_task.rs b/components/script/script_task.rs
new file mode 100644
index 00000000000..fdbcff82410
--- /dev/null
+++ b/components/script/script_task.rs
@@ -0,0 +1,933 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The script task is the task that owns the DOM in memory, runs JavaScript, and spawns parsing
+//! and layout tasks.
+
+use dom::bindings::codegen::InheritTypes::{EventTargetCast, NodeCast, EventCast};
+use dom::bindings::global::Window;
+use dom::bindings::js::{JS, JSRef, RootCollection, Temporary, OptionalSettable};
+use dom::bindings::js::OptionalRootable;
+use dom::bindings::utils::Reflectable;
+use dom::bindings::utils::{wrap_for_same_compartment, pre_wrap};
+use dom::document::{Document, HTMLDocument, DocumentHelpers};
+use dom::element::{Element, HTMLButtonElementTypeId, HTMLInputElementTypeId};
+use dom::element::{HTMLSelectElementTypeId, HTMLTextAreaElementTypeId, HTMLOptionElementTypeId};
+use dom::event::Event;
+use dom::uievent::UIEvent;
+use dom::eventtarget::{EventTarget, EventTargetHelpers};
+use dom::node;
+use dom::node::{ElementNodeTypeId, Node, NodeHelpers};
+use dom::window::{TimerId, Window, WindowHelpers};
+use dom::worker::{Worker, TrustedWorkerAddress};
+use dom::xmlhttprequest::{TrustedXHRAddress, XMLHttpRequest, XHRProgress};
+use html::hubbub_html_parser::HtmlParserResult;
+use html::hubbub_html_parser::{HtmlDiscoveredStyle, HtmlDiscoveredScript};
+use html::hubbub_html_parser;
+use layout_interface::AddStylesheetMsg;
+use layout_interface::{ScriptLayoutChan, LayoutChan, MatchSelectorsDocumentDamage};
+use layout_interface::{ReflowDocumentDamage, ReflowForDisplay};
+use layout_interface::ContentChangedDocumentDamage;
+use layout_interface;
+use page::{Page, IterablePage, Frame};
+
+use script_traits::{CompositorEvent, ResizeEvent, ReflowEvent, ClickEvent, MouseDownEvent};
+use script_traits::{MouseMoveEvent, MouseUpEvent, ConstellationControlMsg, ScriptTaskFactory};
+use script_traits::{ResizeMsg, AttachLayoutMsg, LoadMsg, SendEventMsg, ResizeInactiveMsg};
+use script_traits::{ExitPipelineMsg, NewLayoutInfo, OpaqueScriptLayoutChannel, ScriptControlChan};
+use script_traits::ReflowCompleteMsg;
+use servo_msg::compositor_msg::{FinishedLoading, LayerId, Loading};
+use servo_msg::compositor_msg::{ScriptListener};
+use servo_msg::constellation_msg::{ConstellationChan, LoadCompleteMsg, LoadUrlMsg, NavigationDirection};
+use servo_msg::constellation_msg::{PipelineId, Failure, FailureMsg, WindowSizeData};
+use servo_msg::constellation_msg;
+use servo_net::image_cache_task::ImageCacheTask;
+use servo_net::resource_task::ResourceTask;
+use servo_util::geometry::to_frac_px;
+use servo_util::task::spawn_named_with_send_on_failure;
+
+use geom::point::Point2D;
+use js::jsapi::{JS_SetWrapObjectCallbacks, JS_SetGCZeal, JS_DEFAULT_ZEAL_FREQ, JS_GC};
+use js::jsapi::{JSContext, JSRuntime};
+use js::jsapi::{JS_SetGCParameter, JSGC_MAX_BYTES};
+use js::rust::{Cx, RtUtils};
+use js::rust::with_compartment;
+use js;
+use url::Url;
+
+use libc::size_t;
+use serialize::{Encoder, Encodable};
+use std::any::{Any, AnyRefExt};
+use std::cell::RefCell;
+use std::comm::{channel, Sender, Receiver, Select};
+use std::mem::replace;
+use std::rc::Rc;
+use std::u32;
+
+local_data_key!(pub StackRoots: *const RootCollection)
+
+/// Messages used to control script event loops, such as ScriptTask and
+/// DedicatedWorkerGlobalScope.
+pub enum ScriptMsg {
+ /// Acts on a fragment URL load on the specified pipeline (only dispatched
+ /// to ScriptTask).
+ TriggerFragmentMsg(PipelineId, Url),
+ /// Begins a content-initiated load on the specified pipeline (only
+ /// dispatched to ScriptTask).
+ TriggerLoadMsg(PipelineId, Url),
+ /// Instructs the script task to send a navigate message to
+ /// the constellation (only dispatched to ScriptTask).
+ NavigateMsg(NavigationDirection),
+ /// Fires a JavaScript timeout (only dispatched to ScriptTask).
+ FireTimerMsg(PipelineId, TimerId),
+ /// Notifies the script that a window associated with a particular pipeline
+ /// should be closed (only dispatched to ScriptTask).
+ ExitWindowMsg(PipelineId),
+ /// Notifies the script of progress on a fetch (dispatched to all tasks).
+ XHRProgressMsg(TrustedXHRAddress, XHRProgress),
+ /// Message sent through Worker.postMessage (only dispatched to
+ /// DedicatedWorkerGlobalScope).
+ DOMMessage(*mut u64, size_t),
+ /// Posts a message to the Worker object (dispatched to all tasks).
+ WorkerPostMessage(TrustedWorkerAddress, *mut u64, size_t),
+ /// Releases one reference to the Worker object (dispatched to all tasks).
+ WorkerRelease(TrustedWorkerAddress),
+}
+
+/// Encapsulates internal communication within the script task.
+#[deriving(Clone)]
+pub struct ScriptChan(pub Sender<ScriptMsg>);
+
+impl<S: Encoder<E>, E> Encodable<S, E> for ScriptChan {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+impl ScriptChan {
+ /// Creates a new script chan.
+ pub fn new() -> (Receiver<ScriptMsg>, ScriptChan) {
+ let (chan, port) = channel();
+ (port, ScriptChan(chan))
+ }
+}
+
+pub struct StackRootTLS;
+
+impl StackRootTLS {
+ pub fn new(roots: &RootCollection) -> StackRootTLS {
+ StackRoots.replace(Some(roots as *const RootCollection));
+ StackRootTLS
+ }
+}
+
+impl Drop for StackRootTLS {
+ fn drop(&mut self) {
+ let _ = StackRoots.replace(None);
+ }
+}
+
+/// Information for an entire page. Pages are top-level browsing contexts and can contain multiple
+/// frames.
+///
+/// FIXME: Rename to `Page`, following WebKit?
+pub struct ScriptTask {
+ /// A handle to the information pertaining to page layout
+ page: RefCell<Rc<Page>>,
+ /// A handle to the image cache task.
+ image_cache_task: ImageCacheTask,
+ /// A handle to the resource task.
+ resource_task: ResourceTask,
+
+ /// The port on which the script task receives messages (load URL, exit, etc.)
+ port: Receiver<ScriptMsg>,
+ /// A channel to hand out to script task-based entities that need to be able to enqueue
+ /// events in the event queue.
+ chan: ScriptChan,
+
+ /// A channel to hand out to tasks that need to respond to a message from the script task.
+ control_chan: ScriptControlChan,
+
+ /// The port on which the constellation and layout tasks can communicate with the
+ /// script task.
+ control_port: Receiver<ConstellationControlMsg>,
+
+ /// For communicating load url messages to the constellation
+ constellation_chan: ConstellationChan,
+ /// A handle to the compositor for communicating ready state messages.
+ compositor: Box<ScriptListener>,
+
+ /// The JavaScript runtime.
+ js_runtime: js::rust::rt,
+ /// The JSContext.
+ js_context: RefCell<Option<Rc<Cx>>>,
+
+ mouse_over_targets: RefCell<Option<Vec<JS<Node>>>>
+}
+
+/// In the event of task failure, all data on the stack runs its destructor. However, there
+/// are no reachable, owning pointers to the DOM memory, so it never gets freed by default
+/// when the script task fails. The ScriptMemoryFailsafe uses the destructor bomb pattern
+/// to forcibly tear down the JS compartments for pages associated with the failing ScriptTask.
+struct ScriptMemoryFailsafe<'a> {
+ owner: Option<&'a ScriptTask>,
+}
+
+impl<'a> ScriptMemoryFailsafe<'a> {
+ fn neuter(&mut self) {
+ self.owner = None;
+ }
+
+ fn new(owner: &'a ScriptTask) -> ScriptMemoryFailsafe<'a> {
+ ScriptMemoryFailsafe {
+ owner: Some(owner),
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl<'a> Drop for ScriptMemoryFailsafe<'a> {
+ fn drop(&mut self) {
+ match self.owner {
+ Some(owner) => {
+ let mut page = owner.page.borrow_mut();
+ for page in page.iter() {
+ *page.mut_js_info() = None;
+ }
+ *owner.js_context.borrow_mut() = None;
+ }
+ None => (),
+ }
+ }
+}
+
+trait PrivateScriptTaskHelpers {
+ fn click_event_filter_by_disabled_state(&self) -> bool;
+}
+
+impl<'a> PrivateScriptTaskHelpers for JSRef<'a, Node> {
+ fn click_event_filter_by_disabled_state(&self) -> bool {
+ match self.type_id() {
+ ElementNodeTypeId(HTMLButtonElementTypeId) |
+ ElementNodeTypeId(HTMLInputElementTypeId) |
+ // ElementNodeTypeId(HTMLKeygenElementTypeId) |
+ ElementNodeTypeId(HTMLOptionElementTypeId) |
+ ElementNodeTypeId(HTMLSelectElementTypeId) |
+ ElementNodeTypeId(HTMLTextAreaElementTypeId) if self.get_disabled_state() => true,
+ _ => false
+ }
+ }
+}
+
+impl ScriptTaskFactory for ScriptTask {
+ fn create_layout_channel(_phantom: Option<&mut ScriptTask>) -> OpaqueScriptLayoutChannel {
+ let (chan, port) = channel();
+ ScriptLayoutChan::new(chan, port)
+ }
+
+ fn clone_layout_channel(_phantom: Option<&mut ScriptTask>, pair: &OpaqueScriptLayoutChannel) -> Box<Any+Send> {
+ box pair.sender() as Box<Any+Send>
+ }
+
+ fn create<C:ScriptListener + Send>(
+ _phantom: Option<&mut ScriptTask>,
+ id: PipelineId,
+ compositor: Box<C>,
+ layout_chan: &OpaqueScriptLayoutChannel,
+ control_chan: ScriptControlChan,
+ control_port: Receiver<ConstellationControlMsg>,
+ constellation_chan: ConstellationChan,
+ failure_msg: Failure,
+ resource_task: ResourceTask,
+ image_cache_task: ImageCacheTask,
+ window_size: WindowSizeData) {
+ let ConstellationChan(const_chan) = constellation_chan.clone();
+ let (script_chan, script_port) = channel();
+ let layout_chan = LayoutChan(layout_chan.sender());
+ spawn_named_with_send_on_failure("ScriptTask", proc() {
+ let script_task = ScriptTask::new(id,
+ compositor as Box<ScriptListener>,
+ layout_chan,
+ script_port,
+ ScriptChan(script_chan),
+ control_chan,
+ control_port,
+ constellation_chan,
+ resource_task,
+ image_cache_task,
+ window_size);
+ let mut failsafe = ScriptMemoryFailsafe::new(&*script_task);
+ script_task.start();
+
+ // This must always be the very last operation performed before the task completes
+ failsafe.neuter();
+ }, FailureMsg(failure_msg), const_chan, false);
+ }
+}
+
+impl ScriptTask {
+ /// Creates a new script task.
+ pub fn new(id: PipelineId,
+ compositor: Box<ScriptListener>,
+ layout_chan: LayoutChan,
+ port: Receiver<ScriptMsg>,
+ chan: ScriptChan,
+ control_chan: ScriptControlChan,
+ control_port: Receiver<ConstellationControlMsg>,
+ constellation_chan: ConstellationChan,
+ resource_task: ResourceTask,
+ img_cache_task: ImageCacheTask,
+ window_size: WindowSizeData)
+ -> Rc<ScriptTask> {
+ let (js_runtime, js_context) = ScriptTask::new_rt_and_cx();
+ unsafe {
+ // JS_SetWrapObjectCallbacks clobbers the existing wrap callback,
+ // and JSCompartment::wrap crashes if that happens. The only way
+ // to retrieve the default callback is as the result of
+ // JS_SetWrapObjectCallbacks, which is why we call it twice.
+ let callback = JS_SetWrapObjectCallbacks((*js_runtime).ptr,
+ None,
+ Some(wrap_for_same_compartment),
+ None);
+ JS_SetWrapObjectCallbacks((*js_runtime).ptr,
+ callback,
+ Some(wrap_for_same_compartment),
+ Some(pre_wrap));
+ }
+
+ let page = Page::new(id, None, layout_chan, window_size,
+ resource_task.clone(),
+ constellation_chan.clone(),
+ js_context.clone());
+ Rc::new(ScriptTask {
+ page: RefCell::new(Rc::new(page)),
+
+ image_cache_task: img_cache_task,
+ resource_task: resource_task,
+
+ port: port,
+ chan: chan,
+ control_chan: control_chan,
+ control_port: control_port,
+ constellation_chan: constellation_chan,
+ compositor: compositor,
+
+ js_runtime: js_runtime,
+ js_context: RefCell::new(Some(js_context)),
+ mouse_over_targets: RefCell::new(None)
+ })
+ }
+
+ pub fn new_rt_and_cx() -> (js::rust::rt, Rc<Cx>) {
+ let js_runtime = js::rust::rt();
+ assert!({
+ let ptr: *mut JSRuntime = (*js_runtime).ptr;
+ ptr.is_not_null()
+ });
+
+ // Unconstrain the runtime's threshold on nominal heap size, to avoid
+ // triggering GC too often if operating continuously near an arbitrary
+ // finite threshold. This leaves the maximum-JS_malloc-bytes threshold
+ // still in effect to cause periodical, and we hope hygienic,
+ // last-ditch GCs from within the GC's allocator.
+ unsafe {
+ JS_SetGCParameter(js_runtime.ptr, JSGC_MAX_BYTES, u32::MAX);
+ }
+
+ let js_context = js_runtime.cx();
+ assert!({
+ let ptr: *mut JSContext = (*js_context).ptr;
+ ptr.is_not_null()
+ });
+ js_context.set_default_options_and_version();
+ js_context.set_logging_error_reporter();
+ unsafe {
+ JS_SetGCZeal((*js_context).ptr, 0, JS_DEFAULT_ZEAL_FREQ);
+ }
+
+ (js_runtime, js_context)
+ }
+
+ pub fn get_cx(&self) -> *mut JSContext {
+ (**self.js_context.borrow().get_ref()).ptr
+ }
+
+ /// Starts the script task. After calling this method, the script task will loop receiving
+ /// messages on its port.
+ pub fn start(&self) {
+ while self.handle_msgs() {
+ // Go on...
+ }
+ }
+
+ /// Handle incoming control messages.
+ fn handle_msgs(&self) -> bool {
+ let roots = RootCollection::new();
+ let _stack_roots_tls = StackRootTLS::new(&roots);
+
+ // Handle pending resize events.
+ // Gather them first to avoid a double mut borrow on self.
+ let mut resizes = vec!();
+
+ {
+ let mut page = self.page.borrow_mut();
+ for page in page.iter() {
+ // Only process a resize if layout is idle.
+ let layout_join_port = page.layout_join_port.deref().borrow();
+ if layout_join_port.is_none() {
+ let mut resize_event = page.resize_event.deref().get();
+ match resize_event.take() {
+ Some(size) => resizes.push((page.id, size)),
+ None => ()
+ }
+ page.resize_event.deref().set(None);
+ }
+ }
+ }
+
+ for (id, size) in resizes.move_iter() {
+ self.handle_event(id, ResizeEvent(size));
+ }
+
+ enum MixedMessage {
+ FromConstellation(ConstellationControlMsg),
+ FromScript(ScriptMsg),
+ }
+
+ // Store new resizes, and gather all other events.
+ let mut sequential = vec!();
+
+ // Receive at least one message so we don't spinloop.
+ let mut event = {
+ let sel = Select::new();
+ let mut port1 = sel.handle(&self.port);
+ let mut port2 = sel.handle(&self.control_port);
+ unsafe {
+ port1.add();
+ port2.add();
+ }
+ let ret = sel.wait();
+ if ret == port1.id() {
+ FromScript(self.port.recv())
+ } else if ret == port2.id() {
+ FromConstellation(self.control_port.recv())
+ } else {
+ fail!("unexpected select result")
+ }
+ };
+
+ loop {
+ match event {
+ FromConstellation(ResizeMsg(id, size)) => {
+ let mut page = self.page.borrow_mut();
+ let page = page.find(id).expect("resize sent to nonexistent pipeline");
+ page.resize_event.deref().set(Some(size));
+ }
+ _ => {
+ sequential.push(event);
+ }
+ }
+
+ match self.control_port.try_recv() {
+ Err(_) => match self.port.try_recv() {
+ Err(_) => break,
+ Ok(ev) => event = FromScript(ev),
+ },
+ Ok(ev) => event = FromConstellation(ev),
+ }
+ }
+
+ // Process the gathered events.
+ for msg in sequential.move_iter() {
+ match msg {
+ // TODO(tkuehn) need to handle auxiliary layouts for iframes
+ FromConstellation(AttachLayoutMsg(new_layout_info)) =>
+ self.handle_new_layout(new_layout_info),
+ FromConstellation(LoadMsg(id, url)) => self.load(id, url),
+ FromScript(TriggerLoadMsg(id, url)) => self.trigger_load(id, url),
+ FromScript(TriggerFragmentMsg(id, url)) => self.trigger_fragment(id, url),
+ FromConstellation(SendEventMsg(id, event)) => self.handle_event(id, event),
+ FromScript(FireTimerMsg(id, timer_id)) => self.handle_fire_timer_msg(id, timer_id),
+ FromScript(NavigateMsg(direction)) => self.handle_navigate_msg(direction),
+ FromConstellation(ReflowCompleteMsg(id, reflow_id)) => self.handle_reflow_complete_msg(id, reflow_id),
+ FromConstellation(ResizeInactiveMsg(id, new_size)) => self.handle_resize_inactive_msg(id, new_size),
+ FromConstellation(ExitPipelineMsg(id)) => if self.handle_exit_pipeline_msg(id) { return false },
+ FromScript(ExitWindowMsg(id)) => self.handle_exit_window_msg(id),
+ FromConstellation(ResizeMsg(..)) => fail!("should have handled ResizeMsg already"),
+ FromScript(XHRProgressMsg(addr, progress)) => XMLHttpRequest::handle_xhr_progress(addr, progress),
+ FromScript(DOMMessage(..)) => fail!("unexpected message"),
+ FromScript(WorkerPostMessage(addr, data, nbytes)) => Worker::handle_message(addr, data, nbytes),
+ FromScript(WorkerRelease(addr)) => Worker::handle_release(addr),
+ }
+ }
+
+ true
+ }
+
+ fn handle_new_layout(&self, new_layout_info: NewLayoutInfo) {
+ debug!("Script: new layout: {:?}", new_layout_info);
+ let NewLayoutInfo {
+ old_pipeline_id,
+ new_pipeline_id,
+ subpage_id,
+ layout_chan
+ } = new_layout_info;
+
+ let mut page = self.page.borrow_mut();
+ let parent_page = page.find(old_pipeline_id).expect("ScriptTask: received a layout
+ whose parent has a PipelineId which does not correspond to a pipeline in the script
+ task's page tree. This is a bug.");
+ let new_page = {
+ let window_size = parent_page.window_size.deref().get();
+ Page::new(new_pipeline_id, Some(subpage_id),
+ LayoutChan(layout_chan.downcast_ref::<Sender<layout_interface::Msg>>().unwrap().clone()),
+ window_size,
+ parent_page.resource_task.deref().clone(),
+ self.constellation_chan.clone(),
+ self.js_context.borrow().get_ref().clone())
+ };
+ parent_page.children.deref().borrow_mut().push(Rc::new(new_page));
+ }
+
+ /// Handles a timer that fired.
+ fn handle_fire_timer_msg(&self, id: PipelineId, timer_id: TimerId) {
+ let mut page = self.page.borrow_mut();
+ let page = page.find(id).expect("ScriptTask: received fire timer msg for a
+ pipeline ID not associated with this script task. This is a bug.");
+ let frame = page.frame();
+ let window = frame.get_ref().window.root();
+ window.handle_fire_timer(timer_id, self.get_cx());
+ }
+
+ /// Handles a notification that reflow completed.
+ fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: uint) {
+ debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id);
+ let mut page = self.page.borrow_mut();
+ let page = page.find(pipeline_id).expect(
+ "ScriptTask: received a load message for a layout channel that is not associated \
+ with this script task. This is a bug.");
+ let last_reflow_id = page.last_reflow_id.deref().get();
+ if last_reflow_id == reflow_id {
+ let mut layout_join_port = page.layout_join_port.deref().borrow_mut();
+ *layout_join_port = None;
+ }
+ self.compositor.set_ready_state(FinishedLoading);
+ }
+
+ /// Handles a navigate forward or backward message.
+ /// TODO(tkuehn): is it ever possible to navigate only on a subframe?
+ fn handle_navigate_msg(&self, direction: NavigationDirection) {
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(constellation_msg::NavigateMsg(direction));
+ }
+
+ /// Window was resized, but this script was not active, so don't reflow yet
+ fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) {
+ let mut page = self.page.borrow_mut();
+ let page = page.find(id).expect("Received resize message for PipelineId not associated
+ with a page in the page tree. This is a bug.");
+ page.window_size.deref().set(new_size);
+ let mut page_url = page.mut_url();
+ let last_loaded_url = replace(&mut *page_url, None);
+ for url in last_loaded_url.iter() {
+ *page_url = Some((url.ref0().clone(), true));
+ }
+ }
+
+ /// We have gotten a window.close from script, which we pass on to the compositor.
+ /// We do not shut down the script task now, because the compositor will ask the
+ /// constellation to shut down the pipeline, which will clean everything up
+ /// normally. If we do exit, we will tear down the DOM nodes, possibly at a point
+ /// where layout is still accessing them.
+ fn handle_exit_window_msg(&self, _: PipelineId) {
+ debug!("script task handling exit window msg");
+
+ // TODO(tkuehn): currently there is only one window,
+ // so this can afford to be naive and just shut down the
+ // compositor. In the future it'll need to be smarter.
+ self.compositor.close();
+ }
+
+ /// Handles a request to exit the script task and shut down layout.
+ /// Returns true if the script task should shut down and false otherwise.
+ fn handle_exit_pipeline_msg(&self, id: PipelineId) -> bool {
+ // If root is being exited, shut down all pages
+ let mut page = self.page.borrow_mut();
+ if page.id == id {
+ debug!("shutting down layout for root page {:?}", id);
+ *self.js_context.borrow_mut() = None;
+ shut_down_layout(&*page, (*self.js_runtime).ptr);
+ return true
+ }
+
+ // otherwise find just the matching page and exit all sub-pages
+ match page.remove(id) {
+ Some(ref mut page) => {
+ shut_down_layout(&*page, (*self.js_runtime).ptr);
+ false
+ }
+ // TODO(tkuehn): pipeline closing is currently duplicated across
+ // script and constellation, which can cause this to happen. Constellation
+ // needs to be smarter about exiting pipelines.
+ None => false,
+ }
+
+ }
+
+ /// The entry point to document loading. Defines bindings, sets up the window and document
+ /// objects, parses HTML and CSS, and kicks off initial layout.
+ fn load(&self, pipeline_id: PipelineId, url: Url) {
+ debug!("ScriptTask: loading {:?} on page {:?}", url, pipeline_id);
+
+ let mut page = self.page.borrow_mut();
+ let page = page.find(pipeline_id).expect("ScriptTask: received a load
+ message for a layout channel that is not associated with this script task. This
+ is a bug.");
+
+ let last_loaded_url = replace(&mut *page.mut_url(), None);
+ match last_loaded_url {
+ Some((ref loaded, needs_reflow)) if *loaded == url => {
+ *page.mut_url() = Some((loaded.clone(), false));
+ if needs_reflow {
+ page.damage(ContentChangedDocumentDamage);
+ page.reflow(ReflowForDisplay, self.control_chan.clone(), self.compositor);
+ }
+ return;
+ },
+ _ => (),
+ }
+
+ let cx = self.js_context.borrow();
+ let cx = cx.get_ref();
+ // Create the window and document objects.
+ let window = Window::new(cx.deref().ptr,
+ page.clone(),
+ self.chan.clone(),
+ self.control_chan.clone(),
+ self.compositor.dup(),
+ self.image_cache_task.clone()).root();
+ let document = Document::new(&*window, Some(url.clone()), HTMLDocument, None).root();
+ window.deref().init_browser_context(&*document);
+
+ self.compositor.set_ready_state(Loading);
+ // Parse HTML.
+ //
+ // Note: We can parse the next document in parallel with any previous documents.
+ let html_parsing_result = hubbub_html_parser::parse_html(&*page,
+ &*document,
+ url.clone(),
+ self.resource_task.clone());
+
+ let HtmlParserResult {
+ discovery_port
+ } = html_parsing_result;
+
+ {
+ // Create the root frame.
+ let mut frame = page.mut_frame();
+ *frame = Some(Frame {
+ document: JS::from_rooted(document.deref()),
+ window: JS::from_rooted(window.deref()),
+ });
+ }
+
+ // Send style sheets over to layout.
+ //
+ // FIXME: These should be streamed to layout as they're parsed. We don't need to stop here
+ // in the script task.
+
+ let mut js_scripts = None;
+ loop {
+ match discovery_port.recv_opt() {
+ Ok(HtmlDiscoveredScript(scripts)) => {
+ assert!(js_scripts.is_none());
+ js_scripts = Some(scripts);
+ }
+ Ok(HtmlDiscoveredStyle(sheet)) => {
+ let LayoutChan(ref chan) = *page.layout_chan;
+ chan.send(AddStylesheetMsg(sheet));
+ }
+ Err(()) => break
+ }
+ }
+
+ // Kick off the initial reflow of the page.
+ document.deref().content_changed();
+
+ let fragment = url.fragment.as_ref().map(|ref fragment| fragment.to_string());
+
+ {
+ // No more reflow required
+ let mut page_url = page.mut_url();
+ *page_url = Some((url.clone(), false));
+ }
+
+ // Receive the JavaScript scripts.
+ assert!(js_scripts.is_some());
+ let js_scripts = js_scripts.take_unwrap();
+ debug!("js_scripts: {:?}", js_scripts);
+
+ with_compartment((**cx).ptr, window.reflector().get_jsobject(), || {
+ // Evaluate every script in the document.
+ for file in js_scripts.iter() {
+ let global_obj = window.reflector().get_jsobject();
+ //FIXME: this should have some kind of error handling, or explicitly
+ // drop an exception on the floor.
+ match cx.evaluate_script(global_obj, file.data.clone(), file.url.serialize(), 1) {
+ Ok(_) => (),
+ Err(_) => println!("evaluate_script failed")
+ }
+ }
+ });
+
+ // We have no concept of a document loader right now, so just dispatch the
+ // "load" event as soon as we've finished executing all scripts parsed during
+ // the initial load.
+ let event = Event::new(&Window(*window), "load".to_string(), false, false).root();
+ let doctarget: &JSRef<EventTarget> = EventTargetCast::from_ref(&*document);
+ let wintarget: &JSRef<EventTarget> = EventTargetCast::from_ref(&*window);
+ let _ = wintarget.dispatch_event_with_target(Some((*doctarget).clone()),
+ &*event);
+
+ page.fragment_node.assign(fragment.map_or(None, |fragid| page.find_fragment_node(fragid)));
+
+ let ConstellationChan(ref chan) = self.constellation_chan;
+ chan.send(LoadCompleteMsg(page.id, url));
+ }
+
+ fn scroll_fragment_point(&self, pipeline_id: PipelineId, node: &JSRef<Element>) {
+ let node: &JSRef<Node> = NodeCast::from_ref(node);
+ let rect = node.get_bounding_content_box();
+ let point = Point2D(to_frac_px(rect.origin.x).to_f32().unwrap(),
+ to_frac_px(rect.origin.y).to_f32().unwrap());
+ // FIXME(#2003, pcwalton): This is pretty bogus when multiple layers are involved.
+ // Really what needs to happen is that this needs to go through layout to ask which
+ // layer the element belongs to, and have it send the scroll message to the
+ // compositor.
+ self.compositor.scroll_fragment_point(pipeline_id, LayerId::null(), point);
+ }
+
+ /// This is the main entry point for receiving and dispatching DOM events.
+ ///
+ /// TODO: Actually perform DOM event dispatch.
+ fn handle_event(&self, pipeline_id: PipelineId, event: CompositorEvent) {
+ match event {
+ ResizeEvent(new_size) => {
+ debug!("script got resize event: {:?}", new_size);
+
+ let window = {
+ let page = get_page(&*self.page.borrow(), pipeline_id);
+ page.window_size.deref().set(new_size);
+
+ let frame = page.frame();
+ if frame.is_some() {
+ page.damage(ReflowDocumentDamage);
+ page.reflow(ReflowForDisplay, self.control_chan.clone(), self.compositor)
+ }
+
+ let mut fragment_node = page.fragment_node.get();
+ match fragment_node.take().map(|node| node.root()) {
+ Some(node) => self.scroll_fragment_point(pipeline_id, &*node),
+ None => {}
+ }
+
+ frame.as_ref().map(|frame| Temporary::new(frame.window.clone()))
+ };
+
+ match window.root() {
+ Some(window) => {
+ // http://dev.w3.org/csswg/cssom-view/#resizing-viewports
+ // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#event-type-resize
+ let uievent = UIEvent::new(&window.clone(),
+ "resize".to_string(), false,
+ false, Some(window.clone()),
+ 0i32).root();
+ let event: &JSRef<Event> = EventCast::from_ref(&*uievent);
+
+ let wintarget: &JSRef<EventTarget> = EventTargetCast::from_ref(&*window);
+ let _ = wintarget.dispatch_event_with_target(None, event);
+ }
+ None => ()
+ }
+ }
+
+ // FIXME(pcwalton): This reflows the entire document and is not incremental-y.
+ ReflowEvent => {
+ debug!("script got reflow event");
+ let page = get_page(&*self.page.borrow(), pipeline_id);
+ let frame = page.frame();
+ if frame.is_some() {
+ page.damage(MatchSelectorsDocumentDamage);
+ page.reflow(ReflowForDisplay, self.control_chan.clone(), self.compositor)
+ }
+ }
+
+ ClickEvent(_button, point) => {
+ debug!("ClickEvent: clicked at {:?}", point);
+ let page = get_page(&*self.page.borrow(), pipeline_id);
+ match page.hit_test(&point) {
+ Some(node_address) => {
+ debug!("node address is {:?}", node_address);
+
+ let temp_node =
+ node::from_untrusted_node_address(
+ self.js_runtime.deref().ptr, node_address);
+
+ let maybe_node = temp_node.root().ancestors().find(|node| node.is_element());
+ match maybe_node {
+ Some(node) => {
+ debug!("clicked on {:s}", node.debug_str());
+ // Prevent click event if form control element is disabled.
+ if node.click_event_filter_by_disabled_state() { return; }
+ match *page.frame() {
+ Some(ref frame) => {
+ let window = frame.window.root();
+ let event =
+ Event::new(&Window(*window),
+ "click".to_string(),
+ true, true).root();
+ let eventtarget: &JSRef<EventTarget> = EventTargetCast::from_ref(&node);
+ let _ = eventtarget.dispatch_event_with_target(None, &*event);
+ }
+ None => {}
+ }
+ }
+ None => {}
+ }
+ }
+
+ None => {}
+ }
+ }
+ MouseDownEvent(..) => {}
+ MouseUpEvent(..) => {}
+ MouseMoveEvent(point) => {
+ let page = get_page(&*self.page.borrow(), pipeline_id);
+ match page.get_nodes_under_mouse(&point) {
+ Some(node_address) => {
+
+ let mut target_list = vec!();
+ let mut target_compare = false;
+
+ let mouse_over_targets = &mut *self.mouse_over_targets.borrow_mut();
+ match *mouse_over_targets {
+ Some(ref mut mouse_over_targets) => {
+ for node in mouse_over_targets.mut_iter() {
+ let node = node.root();
+ node.deref().set_hover_state(false);
+ }
+ }
+ None => {}
+ }
+
+ for node_address in node_address.iter() {
+
+ let temp_node =
+ node::from_untrusted_node_address(
+ self.js_runtime.deref().ptr, *node_address);
+
+ let maybe_node = temp_node.root().ancestors().find(|node| node.is_element());
+ match maybe_node {
+ Some(node) => {
+ node.set_hover_state(true);
+
+ match *mouse_over_targets {
+ Some(ref mouse_over_targets) => {
+ if !target_compare {
+ target_compare = !mouse_over_targets.contains(&JS::from_rooted(&node));
+ }
+ }
+ None => {}
+ }
+ target_list.push(JS::from_rooted(&node));
+ }
+ None => {}
+ }
+ }
+ match *mouse_over_targets {
+ Some(ref mouse_over_targets) => {
+ if mouse_over_targets.len() != target_list.len() {
+ target_compare = true;
+ }
+ }
+ None => { target_compare = true; }
+ }
+
+ if target_compare {
+ if mouse_over_targets.is_some() {
+ page.damage(MatchSelectorsDocumentDamage);
+ page.reflow(ReflowForDisplay, self.control_chan.clone(), self.compositor);
+ }
+ *mouse_over_targets = Some(target_list);
+ }
+ }
+
+ None => {}
+ }
+ }
+ }
+ }
+
+ /// The entry point for content to notify that a new load has been requested
+ /// for the given pipeline.
+ fn trigger_load(&self, pipeline_id: PipelineId, url: Url) {
+ let ConstellationChan(ref const_chan) = self.constellation_chan;
+ const_chan.send(LoadUrlMsg(pipeline_id, url));
+ }
+
+ /// The entry point for content to notify that a fragment url has been requested
+ /// for the given pipeline.
+ fn trigger_fragment(&self, pipeline_id: PipelineId, url: Url) {
+ let page = get_page(&*self.page.borrow(), pipeline_id);
+ match page.find_fragment_node(url.fragment.unwrap()).root() {
+ Some(node) => {
+ self.scroll_fragment_point(pipeline_id, &*node);
+ }
+ None => {}
+ }
+ }
+}
+
+/// Shuts down layout for the given page tree.
+fn shut_down_layout(page_tree: &Rc<Page>, rt: *mut JSRuntime) {
+ for page in page_tree.iter() {
+ page.join_layout();
+
+ // Tell the layout task to begin shutting down, and wait until it
+ // processed this message.
+ let (response_chan, response_port) = channel();
+ let LayoutChan(ref chan) = *page.layout_chan;
+ chan.send(layout_interface::PrepareToExitMsg(response_chan));
+ response_port.recv();
+ }
+
+ // Remove our references to the DOM objects in this page tree.
+ for page in page_tree.iter() {
+ *page.mut_frame() = None;
+ }
+
+ // Drop our references to the JSContext, potentially triggering a GC.
+ for page in page_tree.iter() {
+ *page.mut_js_info() = None;
+ }
+
+ // Force a GC to make sure that our DOM reflectors are released before we tell
+ // layout to exit.
+ unsafe {
+ JS_GC(rt);
+ }
+
+ // Destroy the layout task. If there were node leaks, layout will now crash safely.
+ for page in page_tree.iter() {
+ let LayoutChan(ref chan) = *page.layout_chan;
+ chan.send(layout_interface::ExitNowMsg);
+ }
+}
+
+
+fn get_page(page: &Rc<Page>, pipeline_id: PipelineId) -> Rc<Page> {
+ page.find(pipeline_id).expect("ScriptTask: received an event \
+ message for a layout channel that is not associated with this script task.\
+ This is a bug.")
+}
diff --git a/components/script_traits/Cargo.toml b/components/script_traits/Cargo.toml
new file mode 100644
index 00000000000..3bc1beda99f
--- /dev/null
+++ b/components/script_traits/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "script_traits"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "script_traits"
+path = "lib.rs"
+
+[dependencies.msg]
+path = "../msg"
+
+[dependencies.net]
+path = "../net"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
diff --git a/components/script_traits/lib.rs b/components/script_traits/lib.rs
new file mode 100644
index 00000000000..d98c1de0e8b
--- /dev/null
+++ b/components/script_traits/lib.rs
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+extern crate geom;
+extern crate servo_msg = "msg";
+extern crate servo_net = "net";
+extern crate url;
+extern crate std;
+extern crate serialize;
+
+// This module contains traits in script used generically
+// in the rest of Servo.
+// The traits are here instead of in layout so
+// that these modules won't have to depend on script.
+
+use servo_msg::constellation_msg::{ConstellationChan, PipelineId, Failure, WindowSizeData};
+use servo_msg::constellation_msg::SubpageId;
+use servo_msg::compositor_msg::ScriptListener;
+use servo_net::image_cache_task::ImageCacheTask;
+use servo_net::resource_task::ResourceTask;
+use std::any::Any;
+use url::Url;
+
+use geom::point::Point2D;
+
+use serialize::{Encodable, Encoder};
+
+pub struct NewLayoutInfo {
+ pub old_pipeline_id: PipelineId,
+ pub new_pipeline_id: PipelineId,
+ pub subpage_id: SubpageId,
+ pub layout_chan: Box<Any+Send>, // opaque reference to a LayoutChannel
+}
+
+/// Messages sent from the constellation to the script task
+pub enum ConstellationControlMsg {
+ /// Loads a new URL on the specified pipeline.
+ LoadMsg(PipelineId, Url),
+ /// Gives a channel and ID to a layout task, as well as the ID of that layout's parent
+ AttachLayoutMsg(NewLayoutInfo),
+ /// Window resized. Sends a DOM event eventually, but first we combine events.
+ ResizeMsg(PipelineId, WindowSizeData),
+ /// Notifies script that window has been resized but to not take immediate action.
+ ResizeInactiveMsg(PipelineId, WindowSizeData),
+ /// Notifies the script that a pipeline should be closed.
+ ExitPipelineMsg(PipelineId),
+ /// Sends a DOM event.
+ SendEventMsg(PipelineId, CompositorEvent),
+ /// Notifies script that reflow is finished.
+ ReflowCompleteMsg(PipelineId, uint),
+}
+
+/// Events from the compositor that the script task needs to know about
+pub enum CompositorEvent {
+ ResizeEvent(WindowSizeData),
+ ReflowEvent,
+ ClickEvent(uint, Point2D<f32>),
+ MouseDownEvent(uint, Point2D<f32>),
+ MouseUpEvent(uint, Point2D<f32>),
+ MouseMoveEvent(Point2D<f32>)
+}
+
+/// An opaque wrapper around script<->layout channels to avoid leaking message types into
+/// crates that don't need to know about them.
+pub struct OpaqueScriptLayoutChannel(pub (Box<Any+Send>, Box<Any+Send>));
+
+/// Encapsulates external communication with the script task.
+#[deriving(Clone)]
+pub struct ScriptControlChan(pub Sender<ConstellationControlMsg>);
+
+impl<S: Encoder<E>, E> Encodable<S, E> for ScriptControlChan {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+pub trait ScriptTaskFactory {
+ fn create<C: ScriptListener + Send>(_phantom: Option<&mut Self>,
+ id: PipelineId,
+ compositor: Box<C>,
+ layout_chan: &OpaqueScriptLayoutChannel,
+ control_chan: ScriptControlChan,
+ control_port: Receiver<ConstellationControlMsg>,
+ constellation_msg: ConstellationChan,
+ failure_msg: Failure,
+ resource_task: ResourceTask,
+ image_cache_task: ImageCacheTask,
+ window_size: WindowSizeData);
+ fn create_layout_channel(_phantom: Option<&mut Self>) -> OpaqueScriptLayoutChannel;
+ fn clone_layout_channel(_phantom: Option<&mut Self>, pair: &OpaqueScriptLayoutChannel) -> Box<Any+Send>;
+}
diff --git a/components/style/.gitignore b/components/style/.gitignore
new file mode 100644
index 00000000000..1f18fa1546d
--- /dev/null
+++ b/components/style/.gitignore
@@ -0,0 +1,2 @@
+properties/mod.rs
+properties/mod.rs.tmp
diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml
new file mode 100644
index 00000000000..a8ccfc42e35
--- /dev/null
+++ b/components/style/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "style"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+build = "make -f makefile.cargo"
+
+[lib]
+name = "style"
+path = "lib.rs"
+
+doc = false
+
+[dependencies.macros]
+path = "../macros"
+
+[dependencies.util]
+path = "../util"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.url]
+git = "https://github.com/servo/rust-url"
+
+[dependencies.cssparser]
+git = "https://github.com/servo/rust-cssparser"
+
+[dependencies.encoding]
+git = "https://github.com/lifthrasiir/rust-encoding"
+
diff --git a/components/style/Mako-0.9.1.zip b/components/style/Mako-0.9.1.zip
new file mode 100644
index 00000000000..b7450e30012
--- /dev/null
+++ b/components/style/Mako-0.9.1.zip
Binary files differ
diff --git a/components/style/README.md b/components/style/README.md
new file mode 100644
index 00000000000..6a77ba5611c
--- /dev/null
+++ b/components/style/README.md
@@ -0,0 +1,6 @@
+servo-style
+===========
+
+Prototype replacement style system for Servo,
+based on [rust-cssparser](https://github.com/mozilla-servo/rust-cssparser)
+instead of [NetSurf’s libcss](https://github.com/mozilla-servo/libcss).
diff --git a/components/style/errors.rs b/components/style/errors.rs
new file mode 100644
index 00000000000..f04f4969293
--- /dev/null
+++ b/components/style/errors.rs
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+use cssparser::ast::{SyntaxError, SourceLocation};
+
+
+pub struct ErrorLoggerIterator<I>(pub I);
+
+impl<T, I: Iterator<Result<T, SyntaxError>>> Iterator<T> for ErrorLoggerIterator<I> {
+ fn next(&mut self) -> Option<T> {
+ let ErrorLoggerIterator(ref mut this) = *self;
+ loop {
+ match this.next() {
+ Some(Ok(v)) => return Some(v),
+ Some(Err(error)) => log_css_error(error.location,
+ format!("{:?}", error.reason).as_slice()),
+ None => return None,
+ }
+ }
+ }
+}
+
+
+/// Defaults to a no-op.
+/// Set a `RUST_LOG=style::errors` environment variable
+/// to log CSS parse errors to stderr.
+pub fn log_css_error(location: SourceLocation, message: &str) {
+ // TODO eventually this will got into a "web console" or something.
+ info!("{:u}:{:u} {:s}", location.line, location.column, message)
+}
diff --git a/components/style/font_face.rs b/components/style/font_face.rs
new file mode 100644
index 00000000000..81e1dadf0a8
--- /dev/null
+++ b/components/style/font_face.rs
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use cssparser::ast::*;
+use cssparser::parse_declaration_list;
+use errors::{ErrorLoggerIterator, log_css_error};
+use std::ascii::StrAsciiExt;
+use parsing_utils::{BufferedIter, ParserIter, parse_slice_comma_separated};
+use properties::longhands::font_family::parse_one_family;
+use properties::computed_values::font_family::FamilyName;
+use stylesheets::{CSSRule, CSSFontFaceRule, CSSStyleRule, CSSMediaRule};
+use media_queries::{Device, Screen};
+use url::{Url, UrlParser};
+
+
+static SUPPORTED_FORMATS: &'static [&'static str] = &["truetype", "opentype"];
+
+
+pub fn iter_font_face_rules_inner(rules: &[CSSRule], callback: |family: &str, source: &Url|) {
+ let device = &Device { media_type: Screen }; // TODO, use Print when printing
+ for rule in rules.iter() {
+ match *rule {
+ CSSStyleRule(_) => {},
+ CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
+ iter_font_face_rules_inner(rule.rules.as_slice(), |f, s| callback(f, s))
+ },
+ CSSFontFaceRule(ref rule) => {
+ for source in rule.sources.iter() {
+ if source.format_hints.is_empty() || source.format_hints.iter().any(
+ |f| SUPPORTED_FORMATS.iter().any(
+ |s| f.as_slice().eq_ignore_ascii_case(*s))) {
+ callback(rule.family.as_slice(), &source.url)
+ }
+ }
+ },
+ }
+ }
+}
+
+enum Source {
+ UrlSource(UrlSource),
+ LocalSource(String),
+}
+
+pub struct UrlSource {
+ pub url: Url,
+ pub format_hints: Vec<String>,
+}
+
+pub struct FontFaceRule {
+ pub family: String,
+ pub sources: Vec<UrlSource>, // local() is not supported yet
+}
+
+pub fn parse_font_face_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>, base_url: &Url) {
+ if rule.prelude.as_slice().skip_whitespace().next().is_some() {
+ log_css_error(rule.location, "@font-face prelude contains unexpected characters");
+ return;
+ }
+
+ let block = match rule.block {
+ Some(block) => block,
+ None => {
+ log_css_error(rule.location, "Invalid @font-face rule");
+ return
+ }
+ };
+
+ let mut maybe_family = None;
+ let mut maybe_sources = None;
+
+ for item in ErrorLoggerIterator(parse_declaration_list(block.move_iter())) {
+ match item {
+ DeclAtRule(rule) => log_css_error(
+ rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()),
+ Declaration(Declaration{ location, name, value, important }) => {
+ if important {
+ log_css_error(location, "!important is not allowed on @font-face descriptors");
+ continue
+ }
+ let name_lower = name.as_slice().to_ascii_lower();
+ match name_lower.as_slice() {
+ "font-family" => {
+ let iter = &mut BufferedIter::new(value.as_slice().skip_whitespace());
+ match parse_one_family(iter) {
+ Ok(FamilyName(name)) => {
+ maybe_family = Some(name);
+ },
+ // This also includes generic family names:
+ _ => log_css_error(location, "Invalid font-family in @font-face"),
+ }
+ },
+ "src" => {
+ match parse_slice_comma_separated(
+ value.as_slice(), |iter| parse_one_url_src(iter, base_url)) {
+ Ok(sources) => maybe_sources = Some(sources),
+ Err(()) => log_css_error(location, "Invalid src in @font-face"),
+ };
+ },
+ _ => {
+ log_css_error(location, format!("Unsupported declaration {:s}", name).as_slice());
+ }
+ }
+ }
+ }
+ }
+
+ match (maybe_family, maybe_sources) {
+ (Some(family), Some(sources)) => parent_rules.push(CSSFontFaceRule(FontFaceRule {
+ family: family,
+ sources: sources,
+ })),
+ (None, _) => log_css_error(rule.location, "@font-face without a font-family descriptor"),
+ _ => log_css_error(rule.location, "@font-face without an src descriptor"),
+ }
+}
+
+
+/// local() is not supported yet
+fn parse_one_url_src(iter: ParserIter, base_url: &Url) -> Result<UrlSource, ()> {
+ match parse_one_src(iter, base_url) {
+ Ok(UrlSource(source)) => Ok(source),
+ _ => Err(())
+ }
+}
+
+
+fn parse_one_src(iter: ParserIter, base_url: &Url) -> Result<Source, ()> {
+ let url = match iter.next() {
+ // Parsing url()
+ Some(&URL(ref url)) => {
+ UrlParser::new().base_url(base_url).parse(url.as_slice()).unwrap_or_else(
+ |_error| Url::parse("about:invalid").unwrap())
+ },
+ // Parsing local() with early return()
+ Some(&Function(ref name, ref arguments)) => {
+ if name.as_slice().eq_ignore_ascii_case("local") {
+ let iter = &mut BufferedIter::new(arguments.as_slice().skip_whitespace());
+ match parse_one_family(iter) {
+ Ok(FamilyName(name)) => return Ok(LocalSource(name)),
+ _ => return Err(())
+ }
+ }
+ return Err(())
+ },
+ _ => return Err(())
+ };
+
+ // Parsing optional format()
+ let format_hints = match iter.next() {
+ Some(&Function(ref name, ref arguments)) => {
+ if !name.as_slice().eq_ignore_ascii_case("format") {
+ return Err(())
+ }
+ try!(parse_slice_comma_separated(arguments.as_slice(), parse_one_format))
+ }
+ Some(component_value) => {
+ iter.push_back(component_value);
+ vec![]
+ }
+ None => vec![],
+ };
+
+ Ok(UrlSource(UrlSource {
+ url: url,
+ format_hints: format_hints,
+ }))
+}
+
+
+fn parse_one_format(iter: ParserIter) -> Result<String, ()> {
+ match iter.next() {
+ Some(&String(ref value)) => {
+ if iter.next().is_none() {
+ Ok(value.clone())
+ } else {
+ Err(())
+ }
+ }
+ _ => Err(())
+ }
+}
diff --git a/components/style/lib.rs b/components/style/lib.rs
new file mode 100644
index 00000000000..6bf8b79e842
--- /dev/null
+++ b/components/style/lib.rs
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![comment = "The Servo Parallel Browser Project"]
+#![license = "MPL"]
+
+#![feature(globs, macro_rules)]
+
+#![feature(phase)]
+#[phase(plugin, link)] extern crate log;
+
+extern crate debug;
+extern crate collections;
+extern crate geom;
+extern crate num;
+extern crate serialize;
+extern crate sync;
+extern crate url;
+
+extern crate cssparser;
+extern crate encoding;
+
+#[phase(plugin)]
+extern crate servo_macros = "macros";
+extern crate servo_util = "util";
+
+
+// Public API
+pub use stylesheets::{Stylesheet, iter_font_face_rules};
+pub use selector_matching::{Stylist, StylesheetOrigin, UserAgentOrigin, AuthorOrigin, UserOrigin};
+pub use selector_matching::{DeclarationBlock, matches};
+pub use properties::{cascade, cascade_anonymous};
+pub use properties::{PropertyDeclaration, ComputedValues, computed_values, style_structs};
+pub use properties::{PropertyDeclarationBlock, parse_style_attribute}; // Style attributes
+pub use properties::{CSSFloat, DeclaredValue, PropertyDeclarationParseResult};
+pub use properties::longhands;
+pub use node::{TElement, TNode};
+pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_list_from_str};
+pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
+pub use cssparser::{Color, RGBA};
+
+mod stylesheets;
+mod errors;
+mod selectors;
+mod selector_matching;
+mod properties;
+mod namespaces;
+mod node;
+mod media_queries;
+mod parsing_utils;
+mod font_face;
diff --git a/components/style/makefile.cargo b/components/style/makefile.cargo
new file mode 100644
index 00000000000..f797dee7237
--- /dev/null
+++ b/components/style/makefile.cargo
@@ -0,0 +1,8 @@
+MAKO_ZIP = Mako-0.9.1.zip
+PYTHON = $(shell which python2.7 2>/dev/null || echo python)
+
+all: properties/mod.rs
+
+properties/mod.rs: properties/mod.rs.mako
+ PYTHONPATH=$(MAKO_ZIP) $(PYTHON) -c "from mako.template import Template; print(Template(filename='$<').render())" > $@.tmp
+ mv $@.tmp $@
diff --git a/components/style/media_queries.rs b/components/style/media_queries.rs
new file mode 100644
index 00000000000..2c7b6b4b08f
--- /dev/null
+++ b/components/style/media_queries.rs
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::ascii::StrAsciiExt;
+use cssparser::parse_rule_list;
+use cssparser::ast::*;
+
+use errors::{ErrorLoggerIterator, log_css_error};
+use stylesheets::{CSSRule, CSSMediaRule, parse_style_rule, parse_nested_at_rule};
+use namespaces::NamespaceMap;
+use url::Url;
+
+
+pub struct MediaRule {
+ pub media_queries: MediaQueryList,
+ pub rules: Vec<CSSRule>,
+}
+
+
+pub struct MediaQueryList {
+ // "not all" is omitted from the list.
+ // An empty list never matches.
+ media_queries: Vec<MediaQuery>
+}
+
+// For now, this is a "Level 2 MQ", ie. a media type.
+pub struct MediaQuery {
+ media_type: MediaQueryType,
+ // TODO: Level 3 MQ expressions
+}
+
+
+pub enum MediaQueryType {
+ All, // Always true
+ MediaType(MediaType),
+}
+
+#[deriving(PartialEq)]
+pub enum MediaType {
+ Screen,
+ Print,
+}
+
+pub struct Device {
+ pub media_type: MediaType,
+ // TODO: Level 3 MQ data: viewport size, etc.
+}
+
+
+pub fn parse_media_rule(rule: AtRule, parent_rules: &mut Vec<CSSRule>,
+ namespaces: &NamespaceMap, base_url: &Url) {
+ let media_queries = parse_media_query_list(rule.prelude.as_slice());
+ let block = match rule.block {
+ Some(block) => block,
+ None => {
+ log_css_error(rule.location, "Invalid @media rule");
+ return
+ }
+ };
+ let mut rules = vec!();
+ for rule in ErrorLoggerIterator(parse_rule_list(block.move_iter())) {
+ match rule {
+ QualifiedRule(rule) => parse_style_rule(rule, &mut rules, namespaces, base_url),
+ AtRule(rule) => parse_nested_at_rule(
+ rule.name.as_slice().to_ascii_lower().as_slice(), rule, &mut rules, namespaces, base_url),
+ }
+ }
+ parent_rules.push(CSSMediaRule(MediaRule {
+ media_queries: media_queries,
+ rules: rules,
+ }))
+}
+
+
+pub fn parse_media_query_list(input: &[ComponentValue]) -> MediaQueryList {
+ let iter = &mut input.skip_whitespace();
+ let mut next = iter.next();
+ if next.is_none() {
+ return MediaQueryList{ media_queries: vec!(MediaQuery{media_type: All}) }
+ }
+ let mut queries = vec!();
+ loop {
+ let mq = match next {
+ Some(&Ident(ref value)) => {
+ match value.as_slice().to_ascii_lower().as_slice() {
+ "screen" => Some(MediaQuery{ media_type: MediaType(Screen) }),
+ "print" => Some(MediaQuery{ media_type: MediaType(Print) }),
+ "all" => Some(MediaQuery{ media_type: All }),
+ _ => None
+ }
+ },
+ _ => None
+ };
+ match iter.next() {
+ None => {
+ for mq in mq.move_iter() {
+ queries.push(mq);
+ }
+ return MediaQueryList{ media_queries: queries }
+ },
+ Some(&Comma) => {
+ for mq in mq.move_iter() {
+ queries.push(mq);
+ }
+ },
+ // Ingnore this comma-separated part
+ _ => loop {
+ match iter.next() {
+ Some(&Comma) => break,
+ None => return MediaQueryList{ media_queries: queries },
+ _ => (),
+ }
+ },
+ }
+ next = iter.next();
+ }
+}
+
+
+impl MediaQueryList {
+ pub fn evaluate(&self, device: &Device) -> bool {
+ self.media_queries.iter().any(|mq| {
+ match mq.media_type {
+ MediaType(media_type) => media_type == device.media_type,
+ All => true,
+ }
+ // TODO: match Level 3 expressions
+ })
+ }
+}
diff --git a/components/style/namespaces.rs b/components/style/namespaces.rs
new file mode 100644
index 00000000000..6ea1e4b4c3a
--- /dev/null
+++ b/components/style/namespaces.rs
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use cssparser::ast::*;
+use std::collections::hashmap::HashMap;
+use servo_util::namespace::Namespace;
+use errors::log_css_error;
+
+pub struct NamespaceMap {
+ pub default: Option<Namespace>,
+ pub prefix_map: HashMap<String, Namespace>,
+}
+
+
+impl NamespaceMap {
+ pub fn new() -> NamespaceMap {
+ NamespaceMap { default: None, prefix_map: HashMap::new() }
+ }
+}
+
+
+pub fn parse_namespace_rule(rule: AtRule, namespaces: &mut NamespaceMap) {
+ let location = rule.location;
+ macro_rules! syntax_error(
+ () => {{
+ log_css_error(location, "Invalid @namespace rule");
+ return
+ }};
+ );
+ if rule.block.is_some() { syntax_error!() }
+ let mut prefix: Option<String> = None;
+ let mut ns: Option<Namespace> = None;
+ let mut iter = rule.prelude.move_skip_whitespace();
+ for component_value in iter {
+ match component_value {
+ Ident(value) => {
+ if prefix.is_some() { syntax_error!() }
+ prefix = Some(value.into_string());
+ },
+ URL(value) | String(value) => {
+ if ns.is_some() { syntax_error!() }
+ ns = Some(Namespace::from_str(value.as_slice()));
+ break
+ },
+ _ => syntax_error!(),
+ }
+ }
+ if iter.next().is_some() { syntax_error!() }
+ match (prefix, ns) {
+ (Some(prefix), Some(ns)) => {
+ if namespaces.prefix_map.swap(prefix, ns).is_some() {
+ log_css_error(location, "Duplicate @namespace rule");
+ }
+ },
+ (None, Some(ns)) => {
+ if namespaces.default.is_some() {
+ log_css_error(location, "Duplicate @namespace rule");
+ }
+ namespaces.default = Some(ns);
+ },
+ _ => syntax_error!()
+ }
+}
diff --git a/components/style/node.rs b/components/style/node.rs
new file mode 100644
index 00000000000..85a4429e767
--- /dev/null
+++ b/components/style/node.rs
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Traits that nodes must implement. Breaks the otherwise-cyclic dependency between layout and
+//! style.
+
+use selectors::AttrSelector;
+use servo_util::atom::Atom;
+use servo_util::namespace::Namespace;
+
+
+pub trait TNode<E:TElement> : Clone {
+ fn parent_node(&self) -> Option<Self>;
+ fn prev_sibling(&self) -> Option<Self>;
+ fn next_sibling(&self) -> Option<Self>;
+ fn is_document(&self) -> bool;
+ fn is_element(&self) -> bool;
+ fn as_element(&self) -> E;
+ fn match_attr(&self, attr: &AttrSelector, test: |&str| -> bool) -> bool;
+ fn is_html_element_in_html_document(&self) -> bool;
+}
+
+pub trait TElement {
+ fn get_attr(&self, namespace: &Namespace, attr: &str) -> Option<&'static str>;
+ fn get_link(&self) -> Option<&'static str>;
+ fn get_local_name<'a>(&'a self) -> &'a Atom;
+ fn get_namespace<'a>(&'a self) -> &'a Namespace;
+ fn get_hover_state(&self) -> bool;
+ fn get_id(&self) -> Option<Atom>;
+ fn get_disabled_state(&self) -> bool;
+ fn get_enabled_state(&self) -> bool;
+}
+
diff --git a/components/style/parsing_utils.rs b/components/style/parsing_utils.rs
new file mode 100644
index 00000000000..3afd7ba0353
--- /dev/null
+++ b/components/style/parsing_utils.rs
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+use std::ascii::StrAsciiExt;
+use cssparser::ast::{ComponentValue, Ident, Comma, SkipWhitespaceIterable, SkipWhitespaceIterator};
+
+
+pub fn one_component_value<'a>(input: &'a [ComponentValue]) -> Result<&'a ComponentValue, ()> {
+ let mut iter = input.skip_whitespace();
+ match iter.next() {
+ Some(value) => if iter.next().is_none() { Ok(value) } else { Err(()) },
+ None => Err(())
+ }
+}
+
+
+pub fn get_ident_lower(component_value: &ComponentValue) -> Result<String, ()> {
+ match component_value {
+ &Ident(ref value) => Ok(value.as_slice().to_ascii_lower()),
+ _ => Err(()),
+ }
+}
+
+
+pub struct BufferedIter<E, I> {
+ iter: I,
+ buffer: Option<E>,
+}
+
+impl<E, I: Iterator<E>> BufferedIter<E, I> {
+ pub fn new(iter: I) -> BufferedIter<E, I> {
+ BufferedIter {
+ iter: iter,
+ buffer: None,
+ }
+ }
+
+ #[inline]
+ pub fn push_back(&mut self, value: E) {
+ assert!(self.buffer.is_none());
+ self.buffer = Some(value);
+ }
+}
+
+impl<E, I: Iterator<E>> Iterator<E> for BufferedIter<E, I> {
+ #[inline]
+ fn next(&mut self) -> Option<E> {
+ if self.buffer.is_some() {
+ self.buffer.take()
+ }
+ else {
+ self.iter.next()
+ }
+ }
+}
+
+pub type ParserIter<'a, 'b> = &'a mut BufferedIter<&'b ComponentValue, SkipWhitespaceIterator<'b>>;
+
+
+#[inline]
+pub fn parse_slice_comma_separated<T>(input: &[ComponentValue],
+ parse_one: |ParserIter| -> Result<T, ()>)
+ -> Result<Vec<T>, ()> {
+ parse_comma_separated(&mut BufferedIter::new(input.skip_whitespace()), parse_one)
+}
+
+#[inline]
+pub fn parse_comma_separated<T>(iter: ParserIter,
+ parse_one: |ParserIter| -> Result<T, ()>)
+ -> Result<Vec<T>, ()> {
+ let mut values = vec![try!(parse_one(iter))];
+ for component_value in iter {
+ match component_value {
+ &Comma => values.push(try!(parse_one(iter))),
+ _ => return Err(())
+ }
+ }
+ Ok(values)
+}
diff --git a/components/style/properties/common_types.rs b/components/style/properties/common_types.rs
new file mode 100644
index 00000000000..fc30a4036fe
--- /dev/null
+++ b/components/style/properties/common_types.rs
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(non_camel_case_types)]
+
+use url::{Url, UrlParser};
+
+pub use servo_util::geometry::Au;
+
+pub type CSSFloat = f64;
+
+pub static DEFAULT_LINE_HEIGHT: CSSFloat = 1.14;
+
+pub mod specified {
+ use std::ascii::StrAsciiExt;
+ use cssparser::ast;
+ use cssparser::ast::*;
+ use super::{Au, CSSFloat};
+ pub use CSSColor = cssparser::Color;
+
+ #[deriving(Clone)]
+ pub enum Length {
+ Au_(Au), // application units
+ Em(CSSFloat),
+ Ex(CSSFloat),
+ // XXX uncomment when supported:
+// Ch(CSSFloat),
+// Rem(CSSFloat),
+// Vw(CSSFloat),
+// Vh(CSSFloat),
+// Vmin(CSSFloat),
+// Vmax(CSSFloat),
+ }
+ static AU_PER_PX: CSSFloat = 60.;
+ static AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
+ static AU_PER_CM: CSSFloat = AU_PER_IN / 2.54;
+ static AU_PER_MM: CSSFloat = AU_PER_IN / 25.4;
+ static AU_PER_PT: CSSFloat = AU_PER_IN / 72.;
+ static AU_PER_PC: CSSFloat = AU_PER_PT * 12.;
+ impl Length {
+ #[inline]
+ fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Result<Length, ()> {
+ match input {
+ &Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
+ => Length::parse_dimension(value.value, unit.as_slice()),
+ &Number(ref value) if value.value == 0. => Ok(Au_(Au(0))),
+ _ => Err(())
+ }
+ }
+ #[allow(dead_code)]
+ pub fn parse(input: &ComponentValue) -> Result<Length, ()> {
+ Length::parse_internal(input, /* negative_ok = */ true)
+ }
+ pub fn parse_non_negative(input: &ComponentValue) -> Result<Length, ()> {
+ Length::parse_internal(input, /* negative_ok = */ false)
+ }
+ pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Length, ()> {
+ match unit.to_ascii_lower().as_slice() {
+ "px" => Ok(Length::from_px(value)),
+ "in" => Ok(Au_(Au((value * AU_PER_IN) as i32))),
+ "cm" => Ok(Au_(Au((value * AU_PER_CM) as i32))),
+ "mm" => Ok(Au_(Au((value * AU_PER_MM) as i32))),
+ "pt" => Ok(Au_(Au((value * AU_PER_PT) as i32))),
+ "pc" => Ok(Au_(Au((value * AU_PER_PC) as i32))),
+ "em" => Ok(Em(value)),
+ "ex" => Ok(Ex(value)),
+ _ => Err(())
+ }
+ }
+ #[inline]
+ pub fn from_px(px_value: CSSFloat) -> Length {
+ Au_(Au((px_value * AU_PER_PX) as i32))
+ }
+ }
+
+ #[deriving(Clone)]
+ pub enum LengthOrPercentage {
+ LP_Length(Length),
+ LP_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
+ }
+ impl LengthOrPercentage {
+ fn parse_internal(input: &ComponentValue, negative_ok: bool)
+ -> Result<LengthOrPercentage, ()> {
+ match input {
+ &Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
+ => Length::parse_dimension(value.value, unit.as_slice()).map(LP_Length),
+ &ast::Percentage(ref value) if negative_ok || value.value >= 0.
+ => Ok(LP_Percentage(value.value / 100.)),
+ &Number(ref value) if value.value == 0. => Ok(LP_Length(Au_(Au(0)))),
+ _ => Err(())
+ }
+ }
+ #[allow(dead_code)]
+ #[inline]
+ pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
+ LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
+ }
+ #[inline]
+ pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentage, ()> {
+ LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
+ }
+ }
+
+ #[deriving(Clone)]
+ pub enum LengthOrPercentageOrAuto {
+ LPA_Length(Length),
+ LPA_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
+ LPA_Auto,
+ }
+ impl LengthOrPercentageOrAuto {
+ fn parse_internal(input: &ComponentValue, negative_ok: bool)
+ -> Result<LengthOrPercentageOrAuto, ()> {
+ match input {
+ &Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
+ => Length::parse_dimension(value.value, unit.as_slice()).map(LPA_Length),
+ &ast::Percentage(ref value) if negative_ok || value.value >= 0.
+ => Ok(LPA_Percentage(value.value / 100.)),
+ &Number(ref value) if value.value == 0. => Ok(LPA_Length(Au_(Au(0)))),
+ &Ident(ref value) if value.as_slice().eq_ignore_ascii_case("auto") => Ok(LPA_Auto),
+ _ => Err(())
+ }
+ }
+ #[inline]
+ pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
+ LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
+ }
+ #[inline]
+ pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrAuto, ()> {
+ LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
+ }
+ }
+
+ #[deriving(Clone)]
+ pub enum LengthOrPercentageOrNone {
+ LPN_Length(Length),
+ LPN_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
+ LPN_None,
+ }
+ impl LengthOrPercentageOrNone {
+ fn parse_internal(input: &ComponentValue, negative_ok: bool)
+ -> Result<LengthOrPercentageOrNone, ()> {
+ match input {
+ &Dimension(ref value, ref unit) if negative_ok || value.value >= 0.
+ => Length::parse_dimension(value.value, unit.as_slice()).map(LPN_Length),
+ &ast::Percentage(ref value) if negative_ok || value.value >= 0.
+ => Ok(LPN_Percentage(value.value / 100.)),
+ &Number(ref value) if value.value == 0. => Ok(LPN_Length(Au_(Au(0)))),
+ &Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => Ok(LPN_None),
+ _ => Err(())
+ }
+ }
+ #[allow(dead_code)]
+ #[inline]
+ pub fn parse(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
+ LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ true)
+ }
+ #[inline]
+ pub fn parse_non_negative(input: &ComponentValue) -> Result<LengthOrPercentageOrNone, ()> {
+ LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
+ }
+ }
+}
+
+pub mod computed {
+ pub use CSSColor = cssparser::Color;
+ pub use compute_CSSColor = super::super::longhands::computed_as_specified;
+ use super::*;
+ use super::super::longhands;
+ pub use servo_util::geometry::Au;
+
+ pub struct Context {
+ pub inherited_font_weight: longhands::font_weight::computed_value::T,
+ pub inherited_font_size: longhands::font_size::computed_value::T,
+ pub inherited_minimum_line_height: longhands::_servo_minimum_line_height::T,
+ pub inherited_text_decorations_in_effect: longhands::_servo_text_decorations_in_effect::T,
+ pub inherited_height: longhands::height::T,
+ pub color: longhands::color::computed_value::T,
+ pub text_decoration: longhands::text_decoration::computed_value::T,
+ pub font_size: longhands::font_size::computed_value::T,
+ pub display: longhands::display::computed_value::T,
+ pub positioned: bool,
+ pub floated: bool,
+ pub border_top_present: bool,
+ pub border_right_present: bool,
+ pub border_bottom_present: bool,
+ pub border_left_present: bool,
+ pub is_root_element: bool,
+ // TODO, as needed: root font size, viewport size, etc.
+ }
+
+ #[allow(non_snake_case_functions)]
+ #[inline]
+ pub fn compute_Au(value: specified::Length, context: &Context) -> Au {
+ compute_Au_with_font_size(value, context.font_size)
+ }
+
+ /// A special version of `compute_Au` used for `font-size`.
+ #[allow(non_snake_case_functions)]
+ #[inline]
+ pub fn compute_Au_with_font_size(value: specified::Length, reference_font_size: Au) -> Au {
+ match value {
+ specified::Au_(value) => value,
+ specified::Em(value) => reference_font_size.scale_by(value),
+ specified::Ex(value) => {
+ let x_height = 0.5; // TODO: find that from the font
+ reference_font_size.scale_by(value * x_height)
+ },
+ }
+ }
+
+ #[deriving(PartialEq, Clone)]
+ pub enum LengthOrPercentage {
+ LP_Length(Au),
+ LP_Percentage(CSSFloat),
+ }
+ #[allow(non_snake_case_functions)]
+ pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context)
+ -> LengthOrPercentage {
+ match value {
+ specified::LP_Length(value) => LP_Length(compute_Au(value, context)),
+ specified::LP_Percentage(value) => LP_Percentage(value),
+ }
+ }
+
+ #[deriving(PartialEq, Clone)]
+ pub enum LengthOrPercentageOrAuto {
+ LPA_Length(Au),
+ LPA_Percentage(CSSFloat),
+ LPA_Auto,
+ }
+ #[allow(non_snake_case_functions)]
+ pub fn compute_LengthOrPercentageOrAuto(value: specified::LengthOrPercentageOrAuto,
+ context: &Context) -> LengthOrPercentageOrAuto {
+ match value {
+ specified::LPA_Length(value) => LPA_Length(compute_Au(value, context)),
+ specified::LPA_Percentage(value) => LPA_Percentage(value),
+ specified::LPA_Auto => LPA_Auto,
+ }
+ }
+
+ #[deriving(PartialEq, Clone)]
+ pub enum LengthOrPercentageOrNone {
+ LPN_Length(Au),
+ LPN_Percentage(CSSFloat),
+ LPN_None,
+ }
+ #[allow(non_snake_case_functions)]
+ pub fn compute_LengthOrPercentageOrNone(value: specified::LengthOrPercentageOrNone,
+ context: &Context) -> LengthOrPercentageOrNone {
+ match value {
+ specified::LPN_Length(value) => LPN_Length(compute_Au(value, context)),
+ specified::LPN_Percentage(value) => LPN_Percentage(value),
+ specified::LPN_None => LPN_None,
+ }
+ }
+}
+
+pub fn parse_url(input: &str, base_url: &Url) -> Url {
+ UrlParser::new().base_url(base_url).parse(input)
+ .unwrap_or_else(|_| Url::parse("about:invalid").unwrap())
+}
diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako
new file mode 100644
index 00000000000..e699c392a77
--- /dev/null
+++ b/components/style/properties/mod.rs.mako
@@ -0,0 +1,2143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is a Mako template: http://www.makotemplates.org/
+
+pub use std::ascii::StrAsciiExt;
+use serialize::{Encodable, Encoder};
+
+use servo_util::logical_geometry::{WritingMode, LogicalMargin};
+use sync::Arc;
+pub use url::Url;
+
+pub use cssparser::*;
+pub use cssparser::ast::*;
+pub use geom::SideOffsets2D;
+
+use errors::{ErrorLoggerIterator, log_css_error};
+pub use parsing_utils::*;
+pub use self::common_types::*;
+use selector_matching::DeclarationBlock;
+
+
+pub use self::property_bit_field::PropertyBitField;
+pub mod common_types;
+
+
+<%!
+
+import re
+
+def to_rust_ident(name):
+ name = name.replace("-", "_")
+ if name in ["static", "super", "box"]: # Rust keywords
+ name += "_"
+ return name
+
+class Longhand(object):
+ def __init__(self, name, derived_from=None, experimental=False):
+ self.name = name
+ self.ident = to_rust_ident(name)
+ self.camel_case, _ = re.subn(
+ "_([a-z])",
+ lambda m: m.group(1).upper(),
+ self.ident.strip("_").capitalize())
+ self.style_struct = THIS_STYLE_STRUCT
+ self.experimental = experimental
+ if derived_from is None:
+ self.derived_from = None
+ else:
+ self.derived_from = [ to_rust_ident(name) for name in derived_from ]
+
+class Shorthand(object):
+ def __init__(self, name, sub_properties):
+ self.name = name
+ self.ident = to_rust_ident(name)
+ self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties]
+
+class StyleStruct(object):
+ def __init__(self, name, inherited):
+ self.name = name
+ self.ident = to_rust_ident(name.lower())
+ self.longhands = []
+ self.inherited = inherited
+
+STYLE_STRUCTS = []
+THIS_STYLE_STRUCT = None
+LONGHANDS = []
+LONGHANDS_BY_NAME = {}
+DERIVED_LONGHANDS = {}
+SHORTHANDS = []
+
+def new_style_struct(name, is_inherited):
+ global THIS_STYLE_STRUCT
+
+ style_struct = StyleStruct(name, is_inherited)
+ STYLE_STRUCTS.append(style_struct)
+ THIS_STYLE_STRUCT = style_struct
+ return ""
+
+def switch_to_style_struct(name):
+ global THIS_STYLE_STRUCT
+
+ for style_struct in STYLE_STRUCTS:
+ if style_struct.name == name:
+ THIS_STYLE_STRUCT = style_struct
+ return ""
+ fail()
+%>
+
+pub mod longhands {
+ pub use super::*;
+ pub use std;
+
+ pub fn computed_as_specified<T>(value: T, _context: &computed::Context) -> T {
+ value
+ }
+
+ <%def name="raw_longhand(name, no_super=False, derived_from=None, experimental=False)">
+ <%
+ if derived_from is not None:
+ derived_from = derived_from.split()
+
+ property = Longhand(name, derived_from=derived_from, experimental=experimental)
+ THIS_STYLE_STRUCT.longhands.append(property)
+ LONGHANDS.append(property)
+ LONGHANDS_BY_NAME[name] = property
+
+ if derived_from is not None:
+ for name in derived_from:
+ DERIVED_LONGHANDS.setdefault(name, []).append(property)
+ %>
+ pub mod ${property.ident} {
+ % if not no_super:
+ use super::*;
+ % endif
+ pub use self::computed_value::*;
+ ${caller.body()}
+ % if derived_from is None:
+ pub fn parse_declared(input: &[ComponentValue], base_url: &Url)
+ -> Result<DeclaredValue<SpecifiedValue>, ()> {
+ match CSSWideKeyword::parse(input) {
+ Ok(InheritKeyword) => Ok(Inherit),
+ Ok(InitialKeyword) => Ok(Initial),
+ Ok(UnsetKeyword) => Ok(${
+ "Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"}),
+ Err(()) => parse_specified(input, base_url),
+ }
+ }
+ % endif
+ }
+ </%def>
+
+ <%def name="longhand(name, no_super=False, derived_from=None, experimental=False)">
+ <%self:raw_longhand name="${name}" derived_from="${derived_from}"
+ experimental="${experimental}">
+ ${caller.body()}
+ % if derived_from is None:
+ pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url)
+ -> Result<DeclaredValue<SpecifiedValue>, ()> {
+ parse(_input, _base_url).map(super::SpecifiedValue)
+ }
+ % endif
+ </%self:raw_longhand>
+ </%def>
+
+ <%def name="single_component_value(name, derived_from=None, experimental=False)">
+ <%self:longhand name="${name}" derived_from="${derived_from}"
+ experimental="${experimental}">
+ ${caller.body()}
+ pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
+ one_component_value(input).and_then(|c| from_component_value(c, base_url))
+ }
+ </%self:longhand>
+ </%def>
+
+ <%def name="single_keyword_computed(name, values, experimental=False)">
+ <%self:single_component_value name="${name}" experimental="${experimental}">
+ ${caller.body()}
+ pub mod computed_value {
+ #[allow(non_camel_case_types)]
+ #[deriving(PartialEq, Clone, FromPrimitive)]
+ pub enum T {
+ % for value in values.split():
+ ${to_rust_ident(value)},
+ % endfor
+ }
+ }
+ pub type SpecifiedValue = computed_value::T;
+ #[inline] pub fn get_initial_value() -> computed_value::T {
+ ${to_rust_ident(values.split()[0])}
+ }
+ pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ get_ident_lower(v).and_then(|keyword| {
+ match keyword.as_slice() {
+ % for value in values.split():
+ "${value}" => Ok(${to_rust_ident(value)}),
+ % endfor
+ _ => Err(()),
+ }
+ })
+ }
+ </%self:single_component_value>
+ </%def>
+
+ <%def name="single_keyword(name, values, experimental=False)">
+ <%self:single_keyword_computed name="${name}"
+ values="${values}"
+ experimental="${experimental}">
+ // The computed value is the same as the specified value.
+ pub use to_computed_value = super::computed_as_specified;
+ </%self:single_keyword_computed>
+ </%def>
+
+ <%def name="predefined_type(name, type, initial_value, parse_method='parse')">
+ <%self:single_component_value name="${name}">
+ pub use to_computed_value = super::super::common_types::computed::compute_${type};
+ pub type SpecifiedValue = specified::${type};
+ pub mod computed_value {
+ pub type T = super::super::computed::${type};
+ }
+ #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} }
+ #[inline] pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ specified::${type}::${parse_method}(v)
+ }
+ </%self:single_component_value>
+ </%def>
+
+
+ // CSS 2.1, Section 8 - Box model
+
+ ${new_style_struct("Margin", is_inherited=False)}
+
+ % for side in ["top", "right", "bottom", "left"]:
+ ${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
+ "computed::LPA_Length(Au(0))")}
+ % endfor
+
+ ${new_style_struct("Padding", is_inherited=False)}
+
+ % for side in ["top", "right", "bottom", "left"]:
+ ${predefined_type("padding-" + side, "LengthOrPercentage",
+ "computed::LP_Length(Au(0))",
+ "parse_non_negative")}
+ % endfor
+
+ ${new_style_struct("Border", is_inherited=False)}
+
+ % for side in ["top", "right", "bottom", "left"]:
+ ${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
+ % endfor
+
+ ${single_keyword("border-top-style", values="none solid double dotted dashed hidden groove ridge inset outset")}
+
+ % for side in ["right", "bottom", "left"]:
+ <%self:longhand name="border-${side}-style", no_super="True">
+ pub use super::border_top_style::{get_initial_value, parse, to_computed_value};
+ pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
+ pub mod computed_value {
+ pub type T = super::super::border_top_style::computed_value::T;
+ }
+ </%self:longhand>
+ % endfor
+
+ pub fn parse_border_width(component_value: &ComponentValue, _base_url: &Url)
+ -> Result<specified::Length, ()> {
+ match component_value {
+ &Ident(ref value) => {
+ match value.as_slice().to_ascii_lower().as_slice() {
+ "thin" => Ok(specified::Length::from_px(1.)),
+ "medium" => Ok(specified::Length::from_px(3.)),
+ "thick" => Ok(specified::Length::from_px(5.)),
+ _ => Err(())
+ }
+ },
+ _ => specified::Length::parse_non_negative(component_value)
+ }
+ }
+ % for side in ["top", "right", "bottom", "left"]:
+ <%self:longhand name="border-${side}-width">
+ pub type SpecifiedValue = specified::Length;
+ pub mod computed_value {
+ use super::super::Au;
+ pub type T = Au;
+ }
+ #[inline] pub fn get_initial_value() -> computed_value::T {
+ Au::from_px(3) // medium
+ }
+ pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
+ one_component_value(input).and_then(|c| parse_border_width(c, base_url))
+ }
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ if !context.border_${side}_present {
+ Au(0)
+ } else {
+ computed::compute_Au(value, context)
+ }
+ }
+ </%self:longhand>
+ % endfor
+
+ ${new_style_struct("PositionOffsets", is_inherited=False)}
+
+ % for side in ["top", "right", "bottom", "left"]:
+ ${predefined_type(side, "LengthOrPercentageOrAuto",
+ "computed::LPA_Auto")}
+ % endfor
+
+ // CSS 2.1, Section 9 - Visual formatting model
+
+ ${new_style_struct("Box", is_inherited=False)}
+
+ // TODO: don't parse values we don't support
+ <%self:single_keyword_computed name="display"
+ values="inline block inline-block
+ table inline-table table-row-group table-header-group table-footer-group
+ table-row table-column-group table-column table-cell table-caption
+ list-item
+ none">
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+// if context.is_root_element && value == list_item {
+// return block
+// }
+ if context.positioned || context.floated || context.is_root_element {
+ match value {
+ inline_table => table,
+ inline | inline_block
+ | table_row_group | table_column | table_column_group
+ | table_header_group | table_footer_group | table_row
+ | table_cell | table_caption
+ => block,
+ _ => value,
+ }
+ } else {
+ value
+ }
+ }
+ </%self:single_keyword_computed>
+
+ ${single_keyword("position", "static absolute relative fixed")}
+ ${single_keyword("float", "none left right")}
+ ${single_keyword("clear", "none left right both")}
+
+ ${new_style_struct("InheritedBox", is_inherited=True)}
+
+ ${single_keyword("direction", "ltr rtl", experimental=True)}
+
+ // CSS 2.1, Section 10 - Visual formatting model details
+
+ ${switch_to_style_struct("Box")}
+
+ ${predefined_type("width", "LengthOrPercentageOrAuto",
+ "computed::LPA_Auto",
+ "parse_non_negative")}
+ <%self:single_component_value name="height">
+ pub type SpecifiedValue = specified::LengthOrPercentageOrAuto;
+ pub mod computed_value {
+ pub type T = super::super::computed::LengthOrPercentageOrAuto;
+ }
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T { computed::LPA_Auto }
+ #[inline]
+ pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ specified::LengthOrPercentageOrAuto::parse_non_negative(v)
+ }
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ match (value, context.inherited_height) {
+ (specified::LPA_Percentage(_), computed::LPA_Auto)
+ if !context.is_root_element && !context.positioned => {
+ computed::LPA_Auto
+ },
+ _ => computed::compute_LengthOrPercentageOrAuto(value, context)
+ }
+ }
+ </%self:single_component_value>
+
+ ${predefined_type("min-width", "LengthOrPercentage",
+ "computed::LP_Length(Au(0))",
+ "parse_non_negative")}
+ ${predefined_type("max-width", "LengthOrPercentageOrNone",
+ "computed::LPN_None",
+ "parse_non_negative")}
+
+ ${predefined_type("min-height", "LengthOrPercentage",
+ "computed::LP_Length(Au(0))",
+ "parse_non_negative")}
+ ${predefined_type("max-height", "LengthOrPercentageOrNone",
+ "computed::LPN_None",
+ "parse_non_negative")}
+
+ ${switch_to_style_struct("InheritedBox")}
+
+ <%self:single_component_value name="line-height">
+ #[deriving(Clone)]
+ pub enum SpecifiedValue {
+ SpecifiedNormal,
+ SpecifiedLength(specified::Length),
+ SpecifiedNumber(CSSFloat),
+ // percentage are the same as em.
+ }
+ /// normal | <number> | <length> | <percentage>
+ pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ match input {
+ &ast::Number(ref value) if value.value >= 0.
+ => Ok(SpecifiedNumber(value.value)),
+ &ast::Percentage(ref value) if value.value >= 0.
+ => Ok(SpecifiedLength(specified::Em(value.value / 100.))),
+ &Dimension(ref value, ref unit) if value.value >= 0.
+ => specified::Length::parse_dimension(value.value, unit.as_slice())
+ .map(SpecifiedLength),
+ &Ident(ref value) if value.as_slice().eq_ignore_ascii_case("normal")
+ => Ok(SpecifiedNormal),
+ _ => Err(()),
+ }
+ }
+ pub mod computed_value {
+ use super::super::{Au, CSSFloat};
+ #[deriving(PartialEq, Clone)]
+ pub enum T {
+ Normal,
+ Length(Au),
+ Number(CSSFloat),
+ }
+ }
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T { Normal }
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ match value {
+ SpecifiedNormal => Normal,
+ SpecifiedLength(value) => Length(computed::compute_Au(value, context)),
+ SpecifiedNumber(value) => Number(value),
+ }
+ }
+ </%self:single_component_value>
+
+ <%self:longhand name="-servo-minimum-line-height" derived_from="line-height">
+ use super::Au;
+ use super::super::common_types::DEFAULT_LINE_HEIGHT;
+ use super::super::longhands::display;
+ use super::super::longhands::line_height;
+
+ pub use to_computed_value = super::computed_as_specified;
+
+ pub type SpecifiedValue = line_height::SpecifiedValue;
+
+ pub mod computed_value {
+ pub type T = super::super::Au;
+ }
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ Au(0)
+ }
+
+ #[inline]
+ pub fn derive_from_line_height(value: line_height::computed_value::T,
+ context: &computed::Context)
+ -> Au {
+ if context.display != display::computed_value::inline {
+ match value {
+ line_height::Normal => context.font_size.scale_by(DEFAULT_LINE_HEIGHT),
+ line_height::Number(percentage) => context.font_size.scale_by(percentage),
+ line_height::Length(length) => length,
+ }
+ } else {
+ context.inherited_minimum_line_height
+ }
+ }
+ </%self:longhand>
+
+ ${switch_to_style_struct("Box")}
+
+ <%self:single_component_value name="vertical-align">
+ <% vertical_align_keywords = (
+ "baseline sub super top text-top middle bottom text-bottom".split()) %>
+ #[allow(non_camel_case_types)]
+ #[deriving(Clone)]
+ pub enum SpecifiedValue {
+ % for keyword in vertical_align_keywords:
+ Specified_${to_rust_ident(keyword)},
+ % endfor
+ SpecifiedLengthOrPercentage(specified::LengthOrPercentage),
+ }
+ /// baseline | sub | super | top | text-top | middle | bottom | text-bottom
+ /// | <percentage> | <length>
+ pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ match input {
+ &Ident(ref value) => {
+ match value.as_slice().to_ascii_lower().as_slice() {
+ % for keyword in vertical_align_keywords:
+ "${keyword}" => Ok(Specified_${to_rust_ident(keyword)}),
+ % endfor
+ _ => Err(()),
+ }
+ },
+ _ => specified::LengthOrPercentage::parse_non_negative(input)
+ .map(SpecifiedLengthOrPercentage)
+ }
+ }
+ pub mod computed_value {
+ use super::super::{Au, CSSFloat};
+ #[allow(non_camel_case_types)]
+ #[deriving(PartialEq, Clone)]
+ pub enum T {
+ % for keyword in vertical_align_keywords:
+ ${to_rust_ident(keyword)},
+ % endfor
+ Length(Au),
+ Percentage(CSSFloat),
+ }
+ }
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T { baseline }
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ match value {
+ % for keyword in vertical_align_keywords:
+ Specified_${to_rust_ident(keyword)} => ${to_rust_ident(keyword)},
+ % endfor
+ SpecifiedLengthOrPercentage(value)
+ => match computed::compute_LengthOrPercentage(value, context) {
+ computed::LP_Length(value) => Length(value),
+ computed::LP_Percentage(value) => Percentage(value)
+ }
+ }
+ }
+ </%self:single_component_value>
+
+
+ // CSS 2.1, Section 11 - Visual effects
+ // FIXME: Implement scrolling for `scroll` and `auto` (#2742).
+ ${single_keyword("overflow", "visible hidden scroll auto")}
+
+ ${switch_to_style_struct("InheritedBox")}
+
+ // TODO: collapse. Well, do tables first.
+ ${single_keyword("visibility", "visible hidden")}
+
+ // CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
+
+ ${switch_to_style_struct("Box")}
+
+ <%self:longhand name="content">
+ pub use to_computed_value = super::computed_as_specified;
+ pub mod computed_value {
+ #[deriving(PartialEq, Clone)]
+ pub enum Content {
+ StringContent(String),
+ }
+ #[allow(non_camel_case_types)]
+ #[deriving(PartialEq, Clone)]
+ pub enum T {
+ normal,
+ none,
+ Content(Vec<Content>),
+ }
+ }
+ pub type SpecifiedValue = computed_value::T;
+ #[inline] pub fn get_initial_value() -> computed_value::T { normal }
+
+ // normal | none | [ <string> ]+
+ // TODO: <uri>, <counter>, attr(<identifier>), open-quote, close-quote, no-open-quote, no-close-quote
+ pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
+ match one_component_value(input) {
+ Ok(&Ident(ref keyword)) => {
+ match keyword.as_slice().to_ascii_lower().as_slice() {
+ "normal" => return Ok(normal),
+ "none" => return Ok(none),
+ _ => ()
+ }
+ },
+ _ => ()
+ }
+ let mut content = vec!();
+ for component_value in input.skip_whitespace() {
+ match component_value {
+ &String(ref value)
+ => content.push(StringContent(value.clone())),
+ _ => return Err(()) // invalid/unsupported value
+ }
+ }
+ Ok(Content(content))
+ }
+ </%self:longhand>
+ // CSS 2.1, Section 13 - Paged media
+
+ // CSS 2.1, Section 14 - Colors and Backgrounds
+
+ ${new_style_struct("Background", is_inherited=False)}
+ ${predefined_type("background-color", "CSSColor",
+ "RGBA(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
+
+ <%self:single_component_value name="background-image">
+ // The computed value is the same as the specified value.
+ pub use to_computed_value = super::computed_as_specified;
+ pub mod computed_value {
+ pub use url::Url;
+ pub type T = Option<Url>;
+ }
+ pub type SpecifiedValue = computed_value::T;
+ #[inline] pub fn get_initial_value() -> SpecifiedValue {
+ None
+ }
+ pub fn from_component_value(component_value: &ComponentValue, base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ match component_value {
+ &ast::URL(ref url) => {
+ let image_url = parse_url(url.as_slice(), base_url);
+ Ok(Some(image_url))
+ },
+ &ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none")
+ => Ok(None),
+ _ => Err(()),
+ }
+ }
+ </%self:single_component_value>
+
+ <%self:longhand name="background-position">
+ use super::super::common_types::specified;
+
+ pub mod computed_value {
+ use super::super::super::common_types::computed::LengthOrPercentage;
+
+ #[deriving(PartialEq, Clone)]
+ pub struct T {
+ pub horizontal: LengthOrPercentage,
+ pub vertical: LengthOrPercentage,
+ }
+ }
+
+ #[deriving(Clone)]
+ pub struct SpecifiedValue {
+ pub horizontal: specified::LengthOrPercentage,
+ pub vertical: specified::LengthOrPercentage,
+ }
+
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ computed_value::T {
+ horizontal: computed::compute_LengthOrPercentage(value.horizontal, context),
+ vertical: computed::compute_LengthOrPercentage(value.vertical, context),
+ }
+ }
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ computed_value::T {
+ horizontal: computed::LP_Percentage(0.0),
+ vertical: computed::LP_Percentage(0.0),
+ }
+ }
+
+ // FIXME(#1997, pcwalton): Support complete CSS2 syntax.
+ pub fn parse_horizontal_and_vertical(horiz: &ComponentValue, vert: &ComponentValue)
+ -> Result<SpecifiedValue, ()> {
+ let horiz = try!(specified::LengthOrPercentage::parse_non_negative(horiz));
+ let vert = try!(specified::LengthOrPercentage::parse_non_negative(vert));
+
+ Ok(SpecifiedValue {
+ horizontal: horiz,
+ vertical: vert,
+ })
+ }
+
+ pub fn parse(input: &[ComponentValue], _: &Url) -> Result<SpecifiedValue, ()> {
+ let mut input_iter = input.skip_whitespace();
+ let horizontal = input_iter.next();
+ let vertical = input_iter.next();
+ if input_iter.next().is_some() {
+ return Err(())
+ }
+
+ match (horizontal, vertical) {
+ (Some(horizontal), Some(vertical)) => {
+ parse_horizontal_and_vertical(horizontal, vertical)
+ }
+ _ => Err(())
+ }
+ }
+ </%self:longhand>
+
+ ${single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")}
+
+ ${single_keyword("background-attachment", "scroll fixed")}
+
+ ${new_style_struct("Color", is_inherited=True)}
+
+ <%self:raw_longhand name="color">
+ pub use to_computed_value = super::computed_as_specified;
+ pub type SpecifiedValue = RGBA;
+ pub mod computed_value {
+ pub type T = super::SpecifiedValue;
+ }
+ #[inline] pub fn get_initial_value() -> computed_value::T {
+ RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */
+ }
+ pub fn parse_specified(input: &[ComponentValue], _base_url: &Url)
+ -> Result<DeclaredValue<SpecifiedValue>, ()> {
+ match one_component_value(input).and_then(Color::parse) {
+ Ok(RGBA(rgba)) => Ok(SpecifiedValue(rgba)),
+ Ok(CurrentColor) => Ok(Inherit),
+ Err(()) => Err(()),
+ }
+ }
+ </%self:raw_longhand>
+
+ // CSS 2.1, Section 15 - Fonts
+
+ ${new_style_struct("Font", is_inherited=True)}
+
+ <%self:longhand name="font-family">
+ pub use to_computed_value = super::computed_as_specified;
+ pub mod computed_value {
+ #[deriving(PartialEq, Clone)]
+ pub enum FontFamily {
+ FamilyName(String),
+ // Generic
+// Serif,
+// SansSerif,
+// Cursive,
+// Fantasy,
+// Monospace,
+ }
+ pub type T = Vec<FontFamily>;
+ }
+ pub type SpecifiedValue = computed_value::T;
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ vec![FamilyName("serif".to_string())]
+ }
+ /// <familiy-name>#
+ /// <familiy-name> = <string> | [ <ident>+ ]
+ /// TODO: <generic-familiy>
+ pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
+ parse_slice_comma_separated(input, parse_one_family)
+ }
+ pub fn parse_one_family<'a>(iter: ParserIter) -> Result<FontFamily, ()> {
+ // TODO: avoid copying strings?
+ let mut idents = match iter.next() {
+ Some(&String(ref value)) => return Ok(FamilyName(value.clone())),
+ Some(&Ident(ref value)) => {
+// match value.as_slice().to_ascii_lower().as_slice() {
+// "serif" => return Ok(Serif),
+// "sans-serif" => return Ok(SansSerif),
+// "cursive" => return Ok(Cursive),
+// "fantasy" => return Ok(Fantasy),
+// "monospace" => return Ok(Monospace),
+// _ => {
+ vec![value.as_slice()]
+// }
+// }
+ }
+ _ => return Err(())
+ };
+ for component_value in iter {
+ match component_value {
+ &Ident(ref value) => {
+ idents.push(value.as_slice());
+ iter.next();
+ },
+ _ => {
+ iter.push_back(component_value);
+ break
+ }
+ }
+ }
+ Ok(FamilyName(idents.connect(" ")))
+ }
+ </%self:longhand>
+
+
+ ${single_keyword("font-style", "normal italic oblique")}
+ ${single_keyword("font-variant", "normal")} // Add small-caps when supported
+
+ <%self:single_component_value name="font-weight">
+ #[deriving(Clone)]
+ pub enum SpecifiedValue {
+ Bolder,
+ Lighter,
+ % for weight in range(100, 901, 100):
+ SpecifiedWeight${weight},
+ % endfor
+ }
+ /// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
+ pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ match input {
+ &Ident(ref value) => {
+ match value.as_slice().to_ascii_lower().as_slice() {
+ "bold" => Ok(SpecifiedWeight700),
+ "normal" => Ok(SpecifiedWeight400),
+ "bolder" => Ok(Bolder),
+ "lighter" => Ok(Lighter),
+ _ => Err(()),
+ }
+ },
+ &Number(ref value) => match value.int_value {
+ Some(100) => Ok(SpecifiedWeight100),
+ Some(200) => Ok(SpecifiedWeight200),
+ Some(300) => Ok(SpecifiedWeight300),
+ Some(400) => Ok(SpecifiedWeight400),
+ Some(500) => Ok(SpecifiedWeight500),
+ Some(600) => Ok(SpecifiedWeight600),
+ Some(700) => Ok(SpecifiedWeight700),
+ Some(800) => Ok(SpecifiedWeight800),
+ Some(900) => Ok(SpecifiedWeight900),
+ _ => Err(()),
+ },
+ _ => Err(())
+ }
+ }
+ pub mod computed_value {
+ #[deriving(PartialEq, Clone)]
+ pub enum T {
+ % for weight in range(100, 901, 100):
+ Weight${weight},
+ % endfor
+ }
+ impl T {
+ pub fn is_bold(self) -> bool {
+ match self {
+ Weight900 | Weight800 | Weight700 | Weight600 => true,
+ _ => false
+ }
+ }
+ }
+ }
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T { Weight400 } // normal
+ #[inline]
+ pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ match value {
+ % for weight in range(100, 901, 100):
+ SpecifiedWeight${weight} => Weight${weight},
+ % endfor
+ Bolder => match context.inherited_font_weight {
+ Weight100 => Weight400,
+ Weight200 => Weight400,
+ Weight300 => Weight400,
+ Weight400 => Weight700,
+ Weight500 => Weight700,
+ Weight600 => Weight900,
+ Weight700 => Weight900,
+ Weight800 => Weight900,
+ Weight900 => Weight900,
+ },
+ Lighter => match context.inherited_font_weight {
+ Weight100 => Weight100,
+ Weight200 => Weight100,
+ Weight300 => Weight100,
+ Weight400 => Weight100,
+ Weight500 => Weight100,
+ Weight600 => Weight400,
+ Weight700 => Weight400,
+ Weight800 => Weight700,
+ Weight900 => Weight700,
+ },
+ }
+ }
+ </%self:single_component_value>
+
+ <%self:single_component_value name="font-size">
+ pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
+ pub mod computed_value {
+ use super::super::Au;
+ pub type T = Au;
+ }
+ #[inline] pub fn get_initial_value() -> computed_value::T {
+ Au::from_px(16) // medium
+ }
+ #[inline]
+ pub fn to_computed_value(_value: SpecifiedValue, context: &computed::Context)
+ -> computed_value::T {
+ // We already computed this element's font size; no need to compute it again.
+ return context.font_size
+ }
+ /// <length> | <percentage>
+ /// TODO: support <absolute-size> and <relative-size>
+ pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
+ -> Result<SpecifiedValue, ()> {
+ specified::LengthOrPercentage::parse_non_negative(input).map(|value| {
+ match value {
+ specified::LP_Length(value) => value,
+ specified::LP_Percentage(value) => specified::Em(value),
+ }
+ })
+ }
+ </%self:single_component_value>
+
+ // CSS 2.1, Section 16 - Text
+
+ ${new_style_struct("InheritedText", is_inherited=True)}
+
+ // TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
+ ${single_keyword("text-align", "left right center justify")}
+
+ ${new_style_struct("Text", is_inherited=False)}
+
+ <%self:longhand name="text-decoration">
+ pub use to_computed_value = super::computed_as_specified;
+ #[deriving(PartialEq, Clone)]
+ pub struct SpecifiedValue {
+ pub underline: bool,
+ pub overline: bool,
+ pub line_through: bool,
+ // 'blink' is accepted in the parser but ignored.
+ // Just not blinking the text is a conforming implementation per CSS 2.1.
+ }
+ pub mod computed_value {
+ pub type T = super::SpecifiedValue;
+ pub static none: T = super::SpecifiedValue { underline: false, overline: false, line_through: false };
+ }
+ #[inline] pub fn get_initial_value() -> computed_value::T {
+ none
+ }
+ /// none | [ underline || overline || line-through || blink ]
+ pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
+ let mut result = SpecifiedValue {
+ underline: false, overline: false, line_through: false,
+ };
+ match one_component_value(input) {
+ Ok(&Ident(ref value))
+ if value.as_slice().eq_ignore_ascii_case("none") => return Ok(result),
+ _ => {}
+ }
+ let mut blink = false;
+ let mut empty = true;
+ for component_value in input.skip_whitespace() {
+ match get_ident_lower(component_value) {
+ Err(()) => return Err(()),
+ Ok(keyword) => match keyword.as_slice() {
+ "underline" => if result.underline { return Err(()) }
+ else { empty = false; result.underline = true },
+ "overline" => if result.overline { return Err(()) }
+ else { empty = false; result.overline = true },
+ "line-through" => if result.line_through { return Err(()) }
+ else { empty = false; result.line_through = true },
+ "blink" => if blink { return Err(()) }
+ else { empty = false; blink = true },
+ _ => return Err(()),
+ }
+ }
+ }
+ if !empty { Ok(result) } else { Err(()) }
+ }
+ </%self:longhand>
+
+ ${switch_to_style_struct("InheritedText")}
+
+ <%self:longhand name="-servo-text-decorations-in-effect"
+ derived_from="display text-decoration">
+ use super::RGBA;
+ use super::super::longhands::display;
+
+ pub use to_computed_value = super::computed_as_specified;
+
+ #[deriving(Clone, PartialEq)]
+ pub struct SpecifiedValue {
+ pub underline: Option<RGBA>,
+ pub overline: Option<RGBA>,
+ pub line_through: Option<RGBA>,
+ }
+
+ pub mod computed_value {
+ pub type T = super::SpecifiedValue;
+ }
+
+ #[inline]
+ pub fn get_initial_value() -> computed_value::T {
+ SpecifiedValue {
+ underline: None,
+ overline: None,
+ line_through: None,
+ }
+ }
+
+ fn maybe(flag: bool, context: &computed::Context) -> Option<RGBA> {
+ if flag {
+ Some(context.color)
+ } else {
+ None
+ }
+ }
+
+ fn derive(context: &computed::Context) -> computed_value::T {
+ // Start with no declarations if this is a block; otherwise, start with the
+ // declarations in effect and add in the text decorations that this inline specifies.
+ let mut result = match context.display {
+ display::computed_value::inline => context.inherited_text_decorations_in_effect,
+ _ => {
+ SpecifiedValue {
+ underline: None,
+ overline: None,
+ line_through: None,
+ }
+ }
+ };
+
+ if result.underline.is_none() {
+ result.underline = maybe(context.text_decoration.underline, context)
+ }
+ if result.overline.is_none() {
+ result.overline = maybe(context.text_decoration.overline, context)
+ }
+ if result.line_through.is_none() {
+ result.line_through = maybe(context.text_decoration.line_through, context)
+ }
+
+ result
+ }
+
+ #[inline]
+ pub fn derive_from_text_decoration(_: text_decoration::computed_value::T,
+ context: &computed::Context)
+ -> computed_value::T {
+ derive(context)
+ }
+
+ #[inline]
+ pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context)
+ -> computed_value::T {
+ derive(context)
+ }
+ </%self:longhand>
+
+ ${single_keyword("white-space", "normal pre")}
+
+ // CSS 2.1, Section 17 - Tables
+ ${new_style_struct("Table", is_inherited=False)}
+
+ ${single_keyword("table-layout", "auto fixed")}
+
+ // CSS 2.1, Section 18 - User interface
+
+
+ // CSS Writing Modes Level 3
+ // http://dev.w3.org/csswg/css-writing-modes/
+ ${switch_to_style_struct("InheritedBox")}
+
+ ${single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True)}
+
+ // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support)
+ // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented
+ ${single_keyword("text-orientation", "sideways sideways-left sideways-right", experimental=True)}
+}
+
+
+pub mod shorthands {
+ pub use super::*;
+ pub use super::longhands::*;
+
+ <%def name="shorthand(name, sub_properties)">
+ <%
+ shorthand = Shorthand(name, sub_properties.split())
+ SHORTHANDS.append(shorthand)
+ %>
+ pub mod ${shorthand.ident} {
+ use super::*;
+ pub struct Longhands {
+ % for sub_property in shorthand.sub_properties:
+ pub ${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
+ % endfor
+ }
+ pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<Longhands, ()> {
+ ${caller.body()}
+ }
+ }
+ </%def>
+
+ <%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)">
+ <%self:shorthand name="${name}" sub_properties="${
+ ' '.join(sub_property_pattern % side
+ for side in ['top', 'right', 'bottom', 'left'])}">
+ let mut iter = input.skip_whitespace().map(|c| ${parser_function}(c, base_url).ok());
+ // zero or more than four values is invalid.
+ // one value sets them all
+ // two values set (top, bottom) and (left, right)
+ // three values set top, (left, right) and bottom
+ // four values set them in order
+ let top = iter.next().unwrap_or(None);
+ let right = iter.next().unwrap_or(top);
+ let bottom = iter.next().unwrap_or(top);
+ let left = iter.next().unwrap_or(right);
+ if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
+ && iter.next().is_none() {
+ Ok(Longhands {
+ % for side in ["top", "right", "bottom", "left"]:
+ ${to_rust_ident(sub_property_pattern % side)}: ${side},
+ % endfor
+ })
+ } else {
+ Err(())
+ }
+ </%self:shorthand>
+ </%def>
+
+ // TODO: other background-* properties
+ <%self:shorthand name="background"
+ sub_properties="background-color background-position background-repeat background-attachment background-image">
+ use std::mem;
+
+ let (mut color, mut image, mut position, mut repeat, mut attachment) =
+ (None, None, None, None, None);
+ let mut last_component_value = None;
+ let mut any = false;
+
+ for component_value in input.skip_whitespace() {
+ if color.is_none() {
+ match background_color::from_component_value(component_value, base_url) {
+ Ok(v) => {
+ color = Some(v);
+ any = true;
+ continue
+ },
+ Err(()) => ()
+ }
+ }
+
+ if image.is_none() {
+ match background_image::from_component_value(component_value, base_url) {
+ Ok(v) => {
+ image = Some(v);
+ any = true;
+ continue
+ },
+ Err(()) => (),
+ }
+ }
+
+ if repeat.is_none() {
+ match background_repeat::from_component_value(component_value, base_url) {
+ Ok(v) => {
+ repeat = Some(v);
+ any = true;
+ continue
+ },
+ Err(()) => ()
+ }
+ }
+
+ if attachment.is_none() {
+ match background_attachment::from_component_value(component_value,
+ base_url) {
+ Ok(v) => {
+ attachment = Some(v);
+ any = true;
+ continue
+ },
+ Err(()) => ()
+ }
+ }
+
+ match mem::replace(&mut last_component_value, None) {
+ Some(saved_component_value) => {
+ if position.is_none() {
+ match background_position::parse_horizontal_and_vertical(
+ saved_component_value,
+ component_value) {
+ Ok(v) => {
+ position = Some(v);
+ any = true;
+ continue
+ },
+ Err(()) => (),
+ }
+ }
+
+ // If we get here, parsing failed.
+ return Err(())
+ }
+ None => {
+ // Save the component value.
+ last_component_value = Some(component_value)
+ }
+ }
+ }
+
+ if any && last_component_value.is_none() {
+ Ok(Longhands {
+ background_color: color,
+ background_image: image,
+ background_position: position,
+ background_repeat: repeat,
+ background_attachment: attachment,
+ })
+ } else {
+ Err(())
+ }
+ </%self:shorthand>
+
+ ${four_sides_shorthand("margin", "margin-%s", "margin_top::from_component_value")}
+ ${four_sides_shorthand("padding", "padding-%s", "padding_top::from_component_value")}
+
+ pub fn parse_color(value: &ComponentValue, _base_url: &Url) -> Result<specified::CSSColor, ()> {
+ specified::CSSColor::parse(value)
+ }
+ ${four_sides_shorthand("border-color", "border-%s-color", "parse_color")}
+ ${four_sides_shorthand("border-style", "border-%s-style",
+ "border_top_style::from_component_value")}
+ ${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
+
+ pub fn parse_border(input: &[ComponentValue], base_url: &Url)
+ -> Result<(Option<specified::CSSColor>,
+ Option<border_top_style::SpecifiedValue>,
+ Option<specified::Length>), ()> {
+ let mut color = None;
+ let mut style = None;
+ let mut width = None;
+ let mut any = false;
+ for component_value in input.skip_whitespace() {
+ if color.is_none() {
+ match specified::CSSColor::parse(component_value) {
+ Ok(c) => { color = Some(c); any = true; continue },
+ Err(()) => ()
+ }
+ }
+ if style.is_none() {
+ match border_top_style::from_component_value(component_value, base_url) {
+ Ok(s) => { style = Some(s); any = true; continue },
+ Err(()) => ()
+ }
+ }
+ if width.is_none() {
+ match parse_border_width(component_value, base_url) {
+ Ok(w) => { width = Some(w); any = true; continue },
+ Err(()) => ()
+ }
+ }
+ return Err(())
+ }
+ if any { Ok((color, style, width)) } else { Err(()) }
+ }
+
+
+ % for side in ["top", "right", "bottom", "left"]:
+ <%self:shorthand name="border-${side}" sub_properties="${' '.join(
+ 'border-%s-%s' % (side, prop)
+ for prop in ['color', 'style', 'width']
+ )}">
+ parse_border(input, base_url).map(|(color, style, width)| {
+ Longhands {
+ % for prop in ["color", "style", "width"]:
+ ${"border_%s_%s: %s," % (side, prop, prop)}
+ % endfor
+ }
+ })
+ </%self:shorthand>
+ % endfor
+
+ <%self:shorthand name="border" sub_properties="${' '.join(
+ 'border-%s-%s' % (side, prop)
+ for side in ['top', 'right', 'bottom', 'left']
+ for prop in ['color', 'style', 'width']
+ )}">
+ parse_border(input, base_url).map(|(color, style, width)| {
+ Longhands {
+ % for side in ["top", "right", "bottom", "left"]:
+ % for prop in ["color", "style", "width"]:
+ ${"border_%s_%s: %s," % (side, prop, prop)}
+ % endfor
+ % endfor
+ }
+ })
+ </%self:shorthand>
+
+ <%self:shorthand name="font" sub_properties="font-style font-variant font-weight
+ font-size line-height font-family">
+ let mut iter = input.skip_whitespace();
+ let mut nb_normals = 0u;
+ let mut style = None;
+ let mut variant = None;
+ let mut weight = None;
+ let mut size = None;
+ let mut line_height = None;
+ for component_value in iter {
+ // Special-case 'normal' because it is valid in each of
+ // font-style, font-weight and font-variant.
+ // Leaves the values to None, 'normal' is the initial value for each of them.
+ match get_ident_lower(component_value) {
+ Ok(ref ident) if ident.as_slice().eq_ignore_ascii_case("normal") => {
+ nb_normals += 1;
+ continue;
+ }
+ _ => {}
+ }
+ if style.is_none() {
+ match font_style::from_component_value(component_value, base_url) {
+ Ok(s) => { style = Some(s); continue },
+ Err(()) => ()
+ }
+ }
+ if weight.is_none() {
+ match font_weight::from_component_value(component_value, base_url) {
+ Ok(w) => { weight = Some(w); continue },
+ Err(()) => ()
+ }
+ }
+ if variant.is_none() {
+ match font_variant::from_component_value(component_value, base_url) {
+ Ok(v) => { variant = Some(v); continue },
+ Err(()) => ()
+ }
+ }
+ match font_size::from_component_value(component_value, base_url) {
+ Ok(s) => { size = Some(s); break },
+ Err(()) => return Err(())
+ }
+ }
+ #[inline]
+ fn count<T>(opt: &Option<T>) -> uint {
+ match opt {
+ &Some(_) => 1,
+ &None => 0,
+ }
+ }
+ if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 {
+ return Err(())
+ }
+ let mut copied_iter = iter.clone();
+ match copied_iter.next() {
+ Some(&Delim('/')) => {
+ iter = copied_iter;
+ line_height = match iter.next() {
+ Some(v) => line_height::from_component_value(v, base_url).ok(),
+ _ => return Err(()),
+ };
+ if line_height.is_none() { return Err(()) }
+ }
+ _ => ()
+ }
+ let family = try!(parse_comma_separated(
+ &mut BufferedIter::new(iter), font_family::parse_one_family));
+ Ok(Longhands {
+ font_style: style,
+ font_variant: variant,
+ font_weight: weight,
+ font_size: size,
+ line_height: line_height,
+ font_family: Some(family)
+ })
+ </%self:shorthand>
+
+}
+
+
+// TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template.
+// Maybe submit for inclusion in libstd?
+mod property_bit_field {
+ use std::uint;
+ use std::mem;
+
+ pub struct PropertyBitField {
+ storage: [uint, ..(${len(LONGHANDS)} - 1 + uint::BITS) / uint::BITS]
+ }
+
+ impl PropertyBitField {
+ #[inline]
+ pub fn new() -> PropertyBitField {
+ PropertyBitField { storage: unsafe { mem::zeroed() } }
+ }
+
+ #[inline]
+ fn get(&self, bit: uint) -> bool {
+ (self.storage[bit / uint::BITS] & (1 << (bit % uint::BITS))) != 0
+ }
+ #[inline]
+ fn set(&mut self, bit: uint) {
+ self.storage[bit / uint::BITS] |= 1 << (bit % uint::BITS)
+ }
+ #[inline]
+ fn clear(&mut self, bit: uint) {
+ self.storage[bit / uint::BITS] &= !(1 << (bit % uint::BITS))
+ }
+ % for i, property in enumerate(LONGHANDS):
+ #[allow(non_snake_case_functions)]
+ #[inline]
+ pub fn get_${property.ident}(&self) -> bool {
+ self.get(${i})
+ }
+ #[allow(non_snake_case_functions)]
+ #[inline]
+ pub fn set_${property.ident}(&mut self) {
+ self.set(${i})
+ }
+ #[allow(non_snake_case_functions)]
+ #[inline]
+ pub fn clear_${property.ident}(&mut self) {
+ self.clear(${i})
+ }
+ % endfor
+ }
+}
+
+
+/// Declarations are stored in reverse order.
+/// Overridden declarations are skipped.
+pub struct PropertyDeclarationBlock {
+ pub important: Arc<Vec<PropertyDeclaration>>,
+ pub normal: Arc<Vec<PropertyDeclaration>>,
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for PropertyDeclarationBlock {
+ fn encode(&self, _: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
+
+
+pub fn parse_style_attribute(input: &str, base_url: &Url) -> PropertyDeclarationBlock {
+ parse_property_declaration_list(tokenize(input), base_url)
+}
+
+
+pub fn parse_property_declaration_list<I: Iterator<Node>>(input: I, base_url: &Url) -> PropertyDeclarationBlock {
+ let mut important_declarations = vec!();
+ let mut normal_declarations = vec!();
+ let mut important_seen = PropertyBitField::new();
+ let mut normal_seen = PropertyBitField::new();
+ let items: Vec<DeclarationListItem> =
+ ErrorLoggerIterator(parse_declaration_list(input)).collect();
+ for item in items.move_iter().rev() {
+ match item {
+ DeclAtRule(rule) => log_css_error(
+ rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()),
+ Declaration(Declaration{ location: l, name: n, value: v, important: i}) => {
+ // TODO: only keep the last valid declaration for a given name.
+ let (list, seen) = if i {
+ (&mut important_declarations, &mut important_seen)
+ } else {
+ (&mut normal_declarations, &mut normal_seen)
+ };
+ match PropertyDeclaration::parse(n.as_slice(), v.as_slice(), list, base_url, seen) {
+ UnknownProperty => log_css_error(l, format!(
+ "Unsupported property: {}:{}", n, v.iter().to_css()).as_slice()),
+ ExperimentalProperty => log_css_error(l, format!(
+ "Experimental property, use `servo --enable_experimental` \
+ or `servo -e` to enable: {}:{}",
+ n, v.iter().to_css()).as_slice()),
+ InvalidValue => log_css_error(l, format!(
+ "Invalid value: {}:{}", n, v.iter().to_css()).as_slice()),
+ ValidOrIgnoredDeclaration => (),
+ }
+ }
+ }
+ }
+ PropertyDeclarationBlock {
+ important: Arc::new(important_declarations),
+ normal: Arc::new(normal_declarations),
+ }
+}
+
+
+pub enum CSSWideKeyword {
+ InitialKeyword,
+ InheritKeyword,
+ UnsetKeyword,
+}
+
+impl CSSWideKeyword {
+ pub fn parse(input: &[ComponentValue]) -> Result<CSSWideKeyword, ()> {
+ one_component_value(input).and_then(get_ident_lower).and_then(|keyword| {
+ match keyword.as_slice() {
+ "initial" => Ok(InitialKeyword),
+ "inherit" => Ok(InheritKeyword),
+ "unset" => Ok(UnsetKeyword),
+ _ => Err(())
+ }
+ })
+ }
+}
+
+
+#[deriving(Clone)]
+pub enum DeclaredValue<T> {
+ SpecifiedValue(T),
+ Initial,
+ Inherit,
+ // There is no Unset variant here.
+ // The 'unset' keyword is represented as either Initial or Inherit,
+ // depending on whether the property is inherited.
+}
+
+#[deriving(Clone)]
+pub enum PropertyDeclaration {
+ % for property in LONGHANDS:
+ ${property.camel_case}Declaration(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
+ % endfor
+}
+
+
+pub enum PropertyDeclarationParseResult {
+ UnknownProperty,
+ ExperimentalProperty,
+ InvalidValue,
+ ValidOrIgnoredDeclaration,
+}
+
+
+impl PropertyDeclaration {
+ pub fn parse(name: &str, value: &[ComponentValue],
+ result_list: &mut Vec<PropertyDeclaration>,
+ base_url: &Url,
+ seen: &mut PropertyBitField) -> PropertyDeclarationParseResult {
+ // FIXME: local variable to work around Rust #10683
+ let name_lower = name.as_slice().to_ascii_lower();
+ match name_lower.as_slice() {
+ % for property in LONGHANDS:
+ % if property.derived_from is None:
+ "${property.name}" => {
+ % if property.experimental:
+ if !::servo_util::opts::experimental_enabled() {
+ return ExperimentalProperty
+ }
+ % endif
+ if seen.get_${property.ident}() {
+ return ValidOrIgnoredDeclaration
+ }
+ match longhands::${property.ident}::parse_declared(value, base_url) {
+ Ok(value) => {
+ seen.set_${property.ident}();
+ result_list.push(${property.camel_case}Declaration(value));
+ ValidOrIgnoredDeclaration
+ },
+ Err(()) => InvalidValue,
+ }
+ },
+ % else:
+ "${property.name}" => UnknownProperty,
+ % endif
+ % endfor
+ % for shorthand in SHORTHANDS:
+ "${shorthand.name}" => {
+ if ${" && ".join("seen.get_%s()" % sub_property.ident
+ for sub_property in shorthand.sub_properties)} {
+ return ValidOrIgnoredDeclaration
+ }
+ match CSSWideKeyword::parse(value) {
+ Ok(InheritKeyword) => {
+ % for sub_property in shorthand.sub_properties:
+ if !seen.get_${sub_property.ident}() {
+ seen.set_${sub_property.ident}();
+ result_list.push(
+ ${sub_property.camel_case}Declaration(Inherit));
+ }
+ % endfor
+ ValidOrIgnoredDeclaration
+ },
+ Ok(InitialKeyword) => {
+ % for sub_property in shorthand.sub_properties:
+ if !seen.get_${sub_property.ident}() {
+ seen.set_${sub_property.ident}();
+ result_list.push(
+ ${sub_property.camel_case}Declaration(Initial));
+ }
+ % endfor
+ ValidOrIgnoredDeclaration
+ },
+ Ok(UnsetKeyword) => {
+ % for sub_property in shorthand.sub_properties:
+ if !seen.get_${sub_property.ident}() {
+ seen.set_${sub_property.ident}();
+ result_list.push(${sub_property.camel_case}Declaration(
+ ${"Inherit" if sub_property.style_struct.inherited else "Initial"}
+ ));
+ }
+ % endfor
+ ValidOrIgnoredDeclaration
+ },
+ Err(()) => match shorthands::${shorthand.ident}::parse(value, base_url) {
+ Ok(result) => {
+ % for sub_property in shorthand.sub_properties:
+ if !seen.get_${sub_property.ident}() {
+ seen.set_${sub_property.ident}();
+ result_list.push(${sub_property.camel_case}Declaration(
+ match result.${sub_property.ident} {
+ Some(value) => SpecifiedValue(value),
+ None => Initial,
+ }
+ ));
+ }
+ % endfor
+ ValidOrIgnoredDeclaration
+ },
+ Err(()) => InvalidValue,
+ }
+ }
+ },
+ % endfor
+ _ => UnknownProperty,
+ }
+ }
+}
+
+
+pub mod style_structs {
+ use super::longhands;
+
+ % for style_struct in STYLE_STRUCTS:
+ #[deriving(PartialEq, Clone)]
+ pub struct ${style_struct.name} {
+ % for longhand in style_struct.longhands:
+ pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T,
+ % endfor
+ }
+ % endfor
+}
+
+#[deriving(Clone)]
+pub struct ComputedValues {
+ % for style_struct in STYLE_STRUCTS:
+ ${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
+ % endfor
+ shareable: bool,
+ pub writing_mode: WritingMode,
+}
+
+impl ComputedValues {
+ /// Resolves the currentColor keyword.
+ /// Any color value form computed values (except for the 'color' property itself)
+ /// should go through this method.
+ ///
+ /// Usage example:
+ /// let top_color = style.resolve_color(style.Border.border_top_color);
+ #[inline]
+ pub fn resolve_color(&self, color: computed::CSSColor) -> RGBA {
+ match color {
+ RGBA(rgba) => rgba,
+ CurrentColor => self.get_color().color,
+ }
+ }
+
+ #[inline]
+ pub fn content_inline_size(&self) -> computed_values::LengthOrPercentageOrAuto {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.height } else { box_style.width }
+ }
+
+ #[inline]
+ pub fn content_block_size(&self) -> computed_values::LengthOrPercentageOrAuto {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.width } else { box_style.height }
+ }
+
+ #[inline]
+ pub fn min_inline_size(&self) -> computed_values::LengthOrPercentage {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.min_height } else { box_style.min_width }
+ }
+
+ #[inline]
+ pub fn min_block_size(&self) -> computed_values::LengthOrPercentage {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.min_width } else { box_style.min_height }
+ }
+
+ #[inline]
+ pub fn max_inline_size(&self) -> computed_values::LengthOrPercentageOrNone {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.max_height } else { box_style.max_width }
+ }
+
+ #[inline]
+ pub fn max_block_size(&self) -> computed_values::LengthOrPercentageOrNone {
+ let box_style = self.get_box();
+ if self.writing_mode.is_vertical() { box_style.max_width } else { box_style.max_height }
+ }
+
+ #[inline]
+ pub fn logical_padding(&self) -> LogicalMargin<computed_values::LengthOrPercentage> {
+ let padding_style = self.get_padding();
+ LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
+ padding_style.padding_top,
+ padding_style.padding_right,
+ padding_style.padding_bottom,
+ padding_style.padding_left,
+ ))
+ }
+
+ #[inline]
+ pub fn logical_border_width(&self) -> LogicalMargin<Au> {
+ let border_style = self.get_border();
+ LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
+ border_style.border_top_width,
+ border_style.border_right_width,
+ border_style.border_bottom_width,
+ border_style.border_left_width,
+ ))
+ }
+
+ #[inline]
+ pub fn logical_margin(&self) -> LogicalMargin<computed_values::LengthOrPercentageOrAuto> {
+ let margin_style = self.get_margin();
+ LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
+ margin_style.margin_top,
+ margin_style.margin_right,
+ margin_style.margin_bottom,
+ margin_style.margin_left,
+ ))
+ }
+
+ #[inline]
+ pub fn logical_position(&self) -> LogicalMargin<computed_values::LengthOrPercentageOrAuto> {
+ // FIXME(SimonSapin): should be the writing mode of the containing block, maybe?
+ let position_style = self.get_positionoffsets();
+ LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
+ position_style.top,
+ position_style.right,
+ position_style.bottom,
+ position_style.left,
+ ))
+ }
+
+ % for style_struct in STYLE_STRUCTS:
+ #[inline]
+ pub fn get_${style_struct.name.lower()}
+ <'a>(&'a self) -> &'a style_structs::${style_struct.name} {
+ &*self.${style_struct.ident}
+ }
+ % endfor
+}
+
+
+/// Return a WritingMode bitflags from the relevant CSS properties.
+fn get_writing_mode(inheritedbox_style: &style_structs::InheritedBox) -> WritingMode {
+ use servo_util::logical_geometry;
+ let mut flags = WritingMode::empty();
+ match inheritedbox_style.direction {
+ computed_values::direction::ltr => {},
+ computed_values::direction::rtl => {
+ flags.insert(logical_geometry::FlagRTL);
+ },
+ }
+ match inheritedbox_style.writing_mode {
+ computed_values::writing_mode::horizontal_tb => {},
+ computed_values::writing_mode::vertical_rl => {
+ flags.insert(logical_geometry::FlagVertical);
+ },
+ computed_values::writing_mode::vertical_lr => {
+ flags.insert(logical_geometry::FlagVertical);
+ flags.insert(logical_geometry::FlagVerticalLR);
+ },
+ }
+ match inheritedbox_style.text_orientation {
+ computed_values::text_orientation::sideways_right => {},
+ computed_values::text_orientation::sideways_left => {
+ flags.insert(logical_geometry::FlagSidewaysLeft);
+ },
+ computed_values::text_orientation::sideways => {
+ if flags.intersects(logical_geometry::FlagVerticalLR) {
+ flags.insert(logical_geometry::FlagSidewaysLeft);
+ }
+ },
+ }
+ flags
+}
+
+
+/// The initial values for all style structs as defined by the specification.
+lazy_init! {
+ static ref INITIAL_VALUES: ComputedValues = ComputedValues {
+ % for style_struct in STYLE_STRUCTS:
+ ${style_struct.ident}: Arc::new(style_structs::${style_struct.name} {
+ % for longhand in style_struct.longhands:
+ ${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
+ % endfor
+ }),
+ % endfor
+ shareable: true,
+ writing_mode: WritingMode::empty()
+ };
+}
+
+
+#[test]
+fn initial_writing_mode_is_empty() {
+ assert_eq!(get_writing_mode(INITIAL_VALUES.get_inheritedbox()), WritingMode::empty())
+}
+
+
+/// This only exists to limit the scope of #[allow(experimental)]
+/// FIXME: remove this when Arc::make_unique() is not experimental anymore.
+trait ArcExperimental<T> {
+ fn make_unique_experimental<'a>(&'a mut self) -> &'a mut T;
+}
+impl<T: Send + Share + Clone> ArcExperimental<T> for Arc<T> {
+ #[inline]
+ #[allow(experimental)]
+ fn make_unique_experimental<'a>(&'a mut self) -> &'a mut T {
+ self.make_unique()
+ }
+}
+
+/// Fast path for the function below. Only computes new inherited styles.
+fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock],
+ shareable: bool,
+ parent_style: &ComputedValues,
+ cached_style: &ComputedValues,
+ context: &computed::Context)
+ -> ComputedValues {
+ % for style_struct in STYLE_STRUCTS:
+ % if style_struct.inherited:
+ let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone();
+ % else:
+ let style_${style_struct.ident} = cached_style.${style_struct.ident}.clone();
+ % endif
+ % endfor
+
+ let mut seen = PropertyBitField::new();
+ // Declaration blocks are stored in increasing precedence order,
+ // we want them in decreasing order here.
+ for sub_list in applicable_declarations.iter().rev() {
+ // Declarations are already stored in reverse order.
+ for declaration in sub_list.declarations.iter() {
+ match *declaration {
+ % for style_struct in STYLE_STRUCTS:
+ % for property in style_struct.longhands:
+ % if property.derived_from is None:
+ ${property.camel_case}Declaration(ref ${'_' if not style_struct.inherited else ''}declared_value) => {
+ % if style_struct.inherited:
+ if seen.get_${property.ident}() {
+ continue
+ }
+ seen.set_${property.ident}();
+ let computed_value = match *declared_value {
+ SpecifiedValue(ref specified_value)
+ => longhands::${property.ident}::to_computed_value(
+ (*specified_value).clone(),
+ context
+ ),
+ Initial
+ => longhands::${property.ident}::get_initial_value(),
+ Inherit => {
+ // This is a bit slow, but this is rare so it shouldn't
+ // matter.
+ //
+ // FIXME: is it still?
+ parent_style.${style_struct.ident}
+ .${property.ident}
+ .clone()
+ }
+ };
+ style_${style_struct.ident}.make_unique_experimental()
+ .${property.ident} = computed_value;
+ % endif
+
+ % if property.name in DERIVED_LONGHANDS:
+ % if not style_struct.inherited:
+ // Use the cached value.
+ let computed_value = style_${style_struct.ident}
+ .${property.ident}.clone();
+ % endif
+ % for derived in DERIVED_LONGHANDS[property.name]:
+ style_${derived.style_struct.ident}
+ .make_unique_experimental()
+ .${derived.ident} =
+ longhands::${derived.ident}
+ ::derive_from_${property.ident}(
+ computed_value,
+ context);
+ % endfor
+ % endif
+ }
+ % else:
+ ${property.camel_case}Declaration(_) => {
+ // Do not allow stylesheets to set derived properties.
+ }
+ % endif
+ % endfor
+ % endfor
+ }
+ }
+ }
+
+ ComputedValues {
+ writing_mode: get_writing_mode(&*style_inheritedbox),
+ % for style_struct in STYLE_STRUCTS:
+ ${style_struct.ident}: style_${style_struct.ident},
+ % endfor
+ shareable: shareable,
+ }
+}
+
+/// Performs the CSS cascade, computing new styles for an element from its parent style and
+/// optionally a cached related style. The arguments are:
+///
+/// * `applicable_declarations`: The list of CSS rules that matched.
+///
+/// * `shareable`: Whether the `ComputedValues` structure to be constructed should be considered
+/// shareable.
+///
+/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
+///
+/// * `cached_style`: If present, cascading is short-circuited for everything but inherited
+/// values and these values are used instead. Obviously, you must be careful when supplying
+/// this that it is safe to only provide inherited declarations. If `parent_style` is `None`,
+/// this is ignored.
+///
+/// Returns the computed values and a boolean indicating whether the result is cacheable.
+pub fn cascade(applicable_declarations: &[DeclarationBlock],
+ shareable: bool,
+ parent_style: Option< &ComputedValues >,
+ cached_style: Option< &ComputedValues >)
+ -> (ComputedValues, bool) {
+ let initial_values = &*INITIAL_VALUES;
+ let (is_root_element, inherited_style) = match parent_style {
+ Some(parent_style) => (false, parent_style),
+ None => (true, initial_values),
+ };
+
+ let mut context = {
+ let inherited_font_style = inherited_style.get_font();
+ computed::Context {
+ is_root_element: is_root_element,
+ inherited_font_weight: inherited_font_style.font_weight,
+ inherited_font_size: inherited_font_style.font_size,
+ inherited_height: inherited_style.get_box().height,
+ inherited_minimum_line_height: inherited_style.get_inheritedbox()
+ ._servo_minimum_line_height,
+ inherited_text_decorations_in_effect:
+ inherited_style.get_inheritedtext()._servo_text_decorations_in_effect,
+ // To be overridden by applicable declarations:
+ font_size: inherited_font_style.font_size,
+ display: longhands::display::get_initial_value(),
+ color: inherited_style.get_color().color,
+ text_decoration: longhands::text_decoration::get_initial_value(),
+ positioned: false,
+ floated: false,
+ border_top_present: false,
+ border_right_present: false,
+ border_bottom_present: false,
+ border_left_present: false,
+ }
+ };
+
+ // This assumes that the computed and specified values have the same Rust type.
+ macro_rules! get_specified(
+ ($style_struct_getter: ident, $property: ident, $declared_value: expr) => {
+ match *$declared_value {
+ SpecifiedValue(specified_value) => specified_value,
+ Initial => longhands::$property::get_initial_value(),
+ Inherit => inherited_style.$style_struct_getter().$property.clone(),
+ }
+ };
+ )
+
+ // Initialize `context`
+ // Declarations blocks are already stored in increasing precedence order.
+ for sub_list in applicable_declarations.iter() {
+ // Declarations are stored in reverse source order, we want them in forward order here.
+ for declaration in sub_list.declarations.iter().rev() {
+ match *declaration {
+ FontSizeDeclaration(ref value) => {
+ context.font_size = match *value {
+ SpecifiedValue(specified_value) => computed::compute_Au_with_font_size(
+ specified_value, context.inherited_font_size),
+ Initial => longhands::font_size::get_initial_value(),
+ Inherit => context.inherited_font_size,
+ }
+ }
+ ColorDeclaration(ref value) => {
+ context.color = get_specified!(get_color, color, value);
+ }
+ DisplayDeclaration(ref value) => {
+ context.display = get_specified!(get_box, display, value);
+ }
+ PositionDeclaration(ref value) => {
+ context.positioned = match get_specified!(get_box, position, value) {
+ longhands::position::absolute | longhands::position::fixed => true,
+ _ => false,
+ }
+ }
+ FloatDeclaration(ref value) => {
+ context.floated = get_specified!(get_box, float, value)
+ != longhands::float::none;
+ }
+ TextDecorationDeclaration(ref value) => {
+ context.text_decoration = get_specified!(get_text, text_decoration, value);
+ }
+ % for side in ["top", "right", "bottom", "left"]:
+ Border${side.capitalize()}StyleDeclaration(ref value) => {
+ context.border_${side}_present =
+ match get_specified!(get_border, border_${side}_style, value) {
+ longhands::border_top_style::none |
+ longhands::border_top_style::hidden => false,
+ _ => true,
+ };
+ }
+ % endfor
+ _ => {}
+ }
+ }
+ }
+
+ match (cached_style, parent_style) {
+ (Some(cached_style), Some(parent_style)) => {
+ return (cascade_with_cached_declarations(applicable_declarations,
+ shareable,
+ parent_style,
+ cached_style,
+ &context), false)
+ }
+ (_, _) => {}
+ }
+
+ // Set computed values, overwriting earlier declarations for the same property.
+ % for style_struct in STYLE_STRUCTS:
+ let mut style_${style_struct.ident} =
+ % if style_struct.inherited:
+ inherited_style
+ % else:
+ initial_values
+ % endif
+ .${style_struct.ident}.clone();
+ % endfor
+ let mut cacheable = true;
+ let mut seen = PropertyBitField::new();
+ // Declaration blocks are stored in increasing precedence order,
+ // we want them in decreasing order here.
+ for sub_list in applicable_declarations.iter().rev() {
+ // Declarations are already stored in reverse order.
+ for declaration in sub_list.declarations.iter() {
+ match *declaration {
+ % for style_struct in STYLE_STRUCTS:
+ % for property in style_struct.longhands:
+ % if property.derived_from is None:
+ ${property.camel_case}Declaration(ref declared_value) => {
+ if seen.get_${property.ident}() {
+ continue
+ }
+ seen.set_${property.ident}();
+ let computed_value = match *declared_value {
+ SpecifiedValue(ref specified_value)
+ => longhands::${property.ident}::to_computed_value(
+ (*specified_value).clone(),
+ &context
+ ),
+ Initial
+ => longhands::${property.ident}::get_initial_value(),
+ Inherit => {
+ // This is a bit slow, but this is rare so it shouldn't
+ // matter.
+ //
+ // FIXME: is it still?
+ cacheable = false;
+ inherited_style.${style_struct.ident}
+ .${property.ident}
+ .clone()
+ }
+ };
+ style_${style_struct.ident}.make_unique_experimental()
+ .${property.ident} = computed_value;
+
+ % if property.name in DERIVED_LONGHANDS:
+ % for derived in DERIVED_LONGHANDS[property.name]:
+ style_${derived.style_struct.ident}
+ .make_unique_experimental()
+ .${derived.ident} =
+ longhands::${derived.ident}
+ ::derive_from_${property.ident}(
+ computed_value,
+ &context);
+ % endfor
+ % endif
+ }
+ % else:
+ ${property.camel_case}Declaration(_) => {
+ // Do not allow stylesheets to set derived properties.
+ }
+ % endif
+ % endfor
+ % endfor
+ }
+ }
+ }
+
+ // The initial value of border-*-width may be changed at computed value time.
+ {
+ let border = style_border.make_unique_experimental();
+ % for side in ["top", "right", "bottom", "left"]:
+ // Like calling to_computed_value, which wouldn't type check.
+ if !context.border_${side}_present {
+ border.border_${side}_width = Au(0);
+ }
+ % endfor
+ }
+
+ // The initial value of display may be changed at computed value time.
+ if !seen.get_display() {
+ let box_ = style_box_.make_unique_experimental();
+ box_.display = longhands::display::to_computed_value(box_.display, &context);
+ }
+
+ (ComputedValues {
+ writing_mode: get_writing_mode(&*style_inheritedbox),
+ % for style_struct in STYLE_STRUCTS:
+ ${style_struct.ident}: style_${style_struct.ident},
+ % endfor
+ shareable: shareable,
+ }, cacheable)
+}
+
+
+/// Equivalent to `cascade()` with an empty `applicable_declarations`
+/// Performs the CSS cascade for an anonymous box.
+///
+/// * `parent_style`: Computed style of the element this anonymous box inherits from.
+pub fn cascade_anonymous(parent_style: &ComputedValues) -> ComputedValues {
+ let initial_values = &*INITIAL_VALUES;
+ let mut result = ComputedValues {
+ % for style_struct in STYLE_STRUCTS:
+ ${style_struct.ident}:
+ % if style_struct.inherited:
+ parent_style
+ % else:
+ initial_values
+ % endif
+ .${style_struct.ident}.clone(),
+ % endfor
+ shareable: false,
+ writing_mode: parent_style.writing_mode,
+ };
+ {
+ let border = result.border.make_unique_experimental();
+ % for side in ["top", "right", "bottom", "left"]:
+ // Like calling to_computed_value, which wouldn't type check.
+ border.border_${side}_width = Au(0);
+ % endfor
+ }
+ // None of the teaks on 'display' apply here.
+ result
+}
+
+
+// Only re-export the types for computed values.
+pub mod computed_values {
+ % for property in LONGHANDS:
+ pub use ${property.ident} = super::longhands::${property.ident}::computed_value;
+ % endfor
+ // Don't use a side-specific name needlessly:
+ pub use border_style = super::longhands::border_top_style::computed_value;
+
+ pub use cssparser::RGBA;
+ pub use super::common_types::computed::{
+ LengthOrPercentage, LP_Length, LP_Percentage,
+ LengthOrPercentageOrAuto, LPA_Length, LPA_Percentage, LPA_Auto,
+ LengthOrPercentageOrNone, LPN_Length, LPN_Percentage, LPN_None};
+}
diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs
new file mode 100644
index 00000000000..0747495b14f
--- /dev/null
+++ b/components/style/selector_matching.rs
@@ -0,0 +1,990 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::hashmap::HashMap;
+use std::hash::Hash;
+use std::num::div_rem;
+use sync::Arc;
+
+use url::Url;
+
+use servo_util::atom::Atom;
+use servo_util::namespace;
+use servo_util::smallvec::VecLike;
+use servo_util::sort;
+
+use media_queries::{Device, Screen};
+use node::{TElement, TNode};
+use properties::{PropertyDeclaration, PropertyDeclarationBlock};
+use selectors::*;
+use stylesheets::{Stylesheet, iter_stylesheet_style_rules};
+
+pub enum StylesheetOrigin {
+ UserAgentOrigin,
+ AuthorOrigin,
+ UserOrigin,
+}
+
+/// The definition of whitespace per CSS Selectors Level 3 § 4.
+static SELECTOR_WHITESPACE: &'static [char] = &[' ', '\t', '\n', '\r', '\x0C'];
+
+/// Map node attributes to Rules whose last simple selector starts with them.
+///
+/// e.g.,
+/// "p > img" would go into the set of Rules corresponding to the
+/// element "img"
+/// "a .foo .bar.baz" would go into the set of Rules corresponding to
+/// the class "bar"
+///
+/// Because we match Rules right-to-left (i.e., moving up the tree
+/// from a node), we need to compare the last simple selector in the
+/// Rule with the node.
+///
+/// So, if a node has ID "id1" and classes "foo" and "bar", then all
+/// the rules it matches will have their last simple selector starting
+/// either with "#id1" or with ".foo" or with ".bar".
+///
+/// Hence, the union of the rules keyed on each of node's classes, ID,
+/// element name, etc. will contain the Rules that actually match that
+/// node.
+struct SelectorMap {
+ // TODO: Tune the initial capacity of the HashMap
+ id_hash: HashMap<Atom, Vec<Rule>>,
+ class_hash: HashMap<Atom, Vec<Rule>>,
+ local_name_hash: HashMap<Atom, Vec<Rule>>,
+ /// Same as local_name_hash, but keys are lower-cased.
+ /// For HTML elements in HTML documents.
+ lower_local_name_hash: HashMap<Atom, Vec<Rule>>,
+ // For Rules that don't have ID, class, or element selectors.
+ universal_rules: Vec<Rule>,
+ /// Whether this hash is empty.
+ empty: bool,
+}
+
+impl SelectorMap {
+ fn new() -> SelectorMap {
+ SelectorMap {
+ id_hash: HashMap::new(),
+ class_hash: HashMap::new(),
+ local_name_hash: HashMap::new(),
+ lower_local_name_hash: HashMap::new(),
+ universal_rules: vec!(),
+ empty: true,
+ }
+ }
+
+ /// Append to `rule_list` all Rules in `self` that match node.
+ ///
+ /// Extract matching rules as per node's ID, classes, tag name, etc..
+ /// Sort the Rules at the end to maintain cascading order.
+ fn get_all_matching_rules<E:TElement,
+ N:TNode<E>,
+ V:VecLike<DeclarationBlock>>(
+ &self,
+ node: &N,
+ matching_rules_list: &mut V,
+ shareable: &mut bool) {
+ if self.empty {
+ return
+ }
+
+ // At the end, we're going to sort the rules that we added, so remember where we began.
+ let init_len = matching_rules_list.vec_len();
+ let element = node.as_element();
+ match element.get_id() {
+ Some(id) => {
+ SelectorMap::get_matching_rules_from_hash(node,
+ &self.id_hash,
+ &id,
+ matching_rules_list,
+ shareable)
+ }
+ None => {}
+ }
+
+ match element.get_attr(&namespace::Null, "class") {
+ Some(ref class_attr) => {
+ // FIXME: Store classes pre-split as atoms to make the loop below faster.
+ for class in class_attr.split(SELECTOR_WHITESPACE) {
+ SelectorMap::get_matching_rules_from_hash(node,
+ &self.class_hash,
+ &Atom::from_slice(class),
+ matching_rules_list,
+ shareable);
+ }
+ }
+ None => {}
+ }
+
+ let local_name_hash = if node.is_html_element_in_html_document() {
+ &self.lower_local_name_hash
+ } else {
+ &self.local_name_hash
+ };
+ SelectorMap::get_matching_rules_from_hash(node,
+ local_name_hash,
+ element.get_local_name(),
+ matching_rules_list,
+ shareable);
+
+ SelectorMap::get_matching_rules(node,
+ self.universal_rules.as_slice(),
+ matching_rules_list,
+ shareable);
+
+ // Sort only the rules we just added.
+ sort::quicksort_by(matching_rules_list.vec_mut_slice_from(init_len), compare);
+
+ fn compare(a: &DeclarationBlock, b: &DeclarationBlock) -> Ordering {
+ (a.specificity, a.source_order).cmp(&(b.specificity, b.source_order))
+ }
+ }
+
+ fn get_matching_rules_from_hash<E:TElement,
+ N:TNode<E>,
+ V:VecLike<DeclarationBlock>>(
+ node: &N,
+ hash: &HashMap<Atom, Vec<Rule>>,
+ key: &Atom,
+ matching_rules: &mut V,
+ shareable: &mut bool) {
+ match hash.find(key) {
+ Some(rules) => {
+ SelectorMap::get_matching_rules(node, rules.as_slice(), matching_rules, shareable)
+ }
+ None => {}
+ }
+ }
+
+ /// Adds rules in `rules` that match `node` to the `matching_rules` list.
+ fn get_matching_rules<E:TElement,
+ N:TNode<E>,
+ V:VecLike<DeclarationBlock>>(
+ node: &N,
+ rules: &[Rule],
+ matching_rules: &mut V,
+ shareable: &mut bool) {
+ for rule in rules.iter() {
+ if matches_compound_selector(&*rule.selector, node, shareable) {
+ matching_rules.vec_push(rule.declarations.clone());
+ }
+ }
+ }
+
+ /// Insert rule into the correct hash.
+ /// Order in which to try: id_hash, class_hash, local_name_hash, universal_rules.
+ fn insert(&mut self, rule: Rule) {
+ self.empty = false;
+
+ match SelectorMap::get_id_name(&rule) {
+ Some(id_name) => {
+ self.id_hash.find_push(id_name, rule);
+ return;
+ }
+ None => {}
+ }
+ match SelectorMap::get_class_name(&rule) {
+ Some(class_name) => {
+ self.class_hash.find_push(class_name, rule);
+ return;
+ }
+ None => {}
+ }
+
+ match SelectorMap::get_local_name(&rule) {
+ Some(LocalNameSelector { name, lower_name }) => {
+ self.local_name_hash.find_push(name, rule.clone());
+ self.lower_local_name_hash.find_push(lower_name, rule);
+ return;
+ }
+ None => {}
+ }
+
+ self.universal_rules.push(rule);
+ }
+
+ /// Retrieve the first ID name in Rule, or None otherwise.
+ fn get_id_name(rule: &Rule) -> Option<Atom> {
+ let simple_selector_sequence = &rule.selector.simple_selectors;
+ for ss in simple_selector_sequence.iter() {
+ match *ss {
+ // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
+ // mode.
+ IDSelector(ref id) => return Some(id.clone()),
+ _ => {}
+ }
+ }
+ return None
+ }
+
+ /// Retrieve the FIRST class name in Rule, or None otherwise.
+ fn get_class_name(rule: &Rule) -> Option<Atom> {
+ let simple_selector_sequence = &rule.selector.simple_selectors;
+ for ss in simple_selector_sequence.iter() {
+ match *ss {
+ // TODO(pradeep): Implement case-sensitivity based on the document type and quirks
+ // mode.
+ ClassSelector(ref class) => return Some(class.clone()),
+ _ => {}
+ }
+ }
+ return None
+ }
+
+ /// Retrieve the name if it is a type selector, or None otherwise.
+ fn get_local_name(rule: &Rule) -> Option<LocalNameSelector> {
+ let simple_selector_sequence = &rule.selector.simple_selectors;
+ for ss in simple_selector_sequence.iter() {
+ match *ss {
+ LocalNameSelector(ref name) => {
+ return Some(name.clone())
+ }
+ _ => {}
+ }
+ }
+ return None
+ }
+}
+
+pub struct Stylist {
+ element_map: PerPseudoElementSelectorMap,
+ before_map: PerPseudoElementSelectorMap,
+ after_map: PerPseudoElementSelectorMap,
+ rules_source_order: uint,
+}
+
+impl Stylist {
+ #[inline]
+ pub fn new() -> Stylist {
+ let mut stylist = Stylist {
+ element_map: PerPseudoElementSelectorMap::new(),
+ before_map: PerPseudoElementSelectorMap::new(),
+ after_map: PerPseudoElementSelectorMap::new(),
+ rules_source_order: 0u,
+ };
+ let ua_stylesheet = Stylesheet::from_bytes(
+ include_bin!("user-agent.css"),
+ Url::parse("chrome:///user-agent.css").unwrap(),
+ None,
+ None);
+ stylist.add_stylesheet(ua_stylesheet, UserAgentOrigin);
+ stylist
+ }
+
+ pub fn add_stylesheet(&mut self, stylesheet: Stylesheet, origin: StylesheetOrigin) {
+ let (mut element_map, mut before_map, mut after_map) = match origin {
+ UserAgentOrigin => (
+ &mut self.element_map.user_agent,
+ &mut self.before_map.user_agent,
+ &mut self.after_map.user_agent,
+ ),
+ AuthorOrigin => (
+ &mut self.element_map.author,
+ &mut self.before_map.author,
+ &mut self.after_map.author,
+ ),
+ UserOrigin => (
+ &mut self.element_map.user,
+ &mut self.before_map.user,
+ &mut self.after_map.user,
+ ),
+ };
+ let mut rules_source_order = self.rules_source_order;
+
+ // Take apart the StyleRule into individual Rules and insert
+ // them into the SelectorMap of that priority.
+ macro_rules! append(
+ ($style_rule: ident, $priority: ident) => {
+ if $style_rule.declarations.$priority.len() > 0 {
+ for selector in $style_rule.selectors.iter() {
+ let map = match selector.pseudo_element {
+ None => &mut element_map,
+ Some(Before) => &mut before_map,
+ Some(After) => &mut after_map,
+ };
+ map.$priority.insert(Rule {
+ selector: selector.compound_selectors.clone(),
+ declarations: DeclarationBlock {
+ specificity: selector.specificity,
+ declarations: $style_rule.declarations.$priority.clone(),
+ source_order: rules_source_order,
+ },
+ });
+ }
+ }
+ };
+ );
+
+ let device = &Device { media_type: Screen }; // TODO, use Print when printing
+ iter_stylesheet_style_rules(&stylesheet, device, |style_rule| {
+ append!(style_rule, normal);
+ append!(style_rule, important);
+ rules_source_order += 1;
+ });
+ self.rules_source_order = rules_source_order;
+ }
+
+ /// Returns the applicable CSS declarations for the given element. This corresponds to
+ /// `ElementRuleCollector` in WebKit.
+ ///
+ /// The returned boolean indicates whether the style is *shareable*; that is, whether the
+ /// matched selectors are simple enough to allow the matching logic to be reduced to the logic
+ /// in `css::matching::PrivateMatchMethods::candidate_element_allows_for_style_sharing`.
+ pub fn push_applicable_declarations<E:TElement,
+ N:TNode<E>,
+ V:VecLike<DeclarationBlock>>(
+ &self,
+ element: &N,
+ style_attribute: Option<&PropertyDeclarationBlock>,
+ pseudo_element: Option<PseudoElement>,
+ applicable_declarations: &mut V)
+ -> bool {
+ assert!(element.is_element());
+ assert!(style_attribute.is_none() || pseudo_element.is_none(),
+ "Style attributes do not apply to pseudo-elements");
+
+ let map = match pseudo_element {
+ None => &self.element_map,
+ Some(Before) => &self.before_map,
+ Some(After) => &self.after_map,
+ };
+
+ let mut shareable = true;
+
+ // Step 1: Normal rules.
+ map.user_agent.normal.get_all_matching_rules(element,
+ applicable_declarations,
+ &mut shareable);
+ map.user.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable);
+ map.author.normal.get_all_matching_rules(element, applicable_declarations, &mut shareable);
+
+ // Step 2: Normal style attributes.
+ style_attribute.map(|sa| {
+ shareable = false;
+ applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.normal.clone()))
+ });
+
+ // Step 3: Author-supplied `!important` rules.
+ map.author.important.get_all_matching_rules(element,
+ applicable_declarations,
+ &mut shareable);
+
+ // Step 4: `!important` style attributes.
+ style_attribute.map(|sa| {
+ shareable = false;
+ applicable_declarations.vec_push(DeclarationBlock::from_declarations(sa.important.clone()))
+ });
+
+ // Step 5: User and UA `!important` rules.
+ map.user.important.get_all_matching_rules(element,
+ applicable_declarations,
+ &mut shareable);
+ map.user_agent.important.get_all_matching_rules(element,
+ applicable_declarations,
+ &mut shareable);
+
+ shareable
+ }
+}
+
+struct PerOriginSelectorMap {
+ normal: SelectorMap,
+ important: SelectorMap,
+}
+
+impl PerOriginSelectorMap {
+ #[inline]
+ fn new() -> PerOriginSelectorMap {
+ PerOriginSelectorMap {
+ normal: SelectorMap::new(),
+ important: SelectorMap::new(),
+ }
+ }
+}
+
+struct PerPseudoElementSelectorMap {
+ user_agent: PerOriginSelectorMap,
+ author: PerOriginSelectorMap,
+ user: PerOriginSelectorMap,
+}
+
+impl PerPseudoElementSelectorMap {
+ #[inline]
+ fn new() -> PerPseudoElementSelectorMap {
+ PerPseudoElementSelectorMap {
+ user_agent: PerOriginSelectorMap::new(),
+ author: PerOriginSelectorMap::new(),
+ user: PerOriginSelectorMap::new(),
+ }
+ }
+}
+
+#[deriving(Clone)]
+struct Rule {
+ // This is an Arc because Rule will essentially be cloned for every node
+ // that it matches. Selector contains an owned vector (through
+ // CompoundSelector) and we want to avoid the allocation.
+ selector: Arc<CompoundSelector>,
+ declarations: DeclarationBlock,
+}
+
+/// A property declaration together with its precedence among rules of equal specificity so that
+/// we can sort them.
+#[deriving(Clone)]
+pub struct DeclarationBlock {
+ pub declarations: Arc<Vec<PropertyDeclaration>>,
+ source_order: uint,
+ specificity: u32,
+}
+
+impl DeclarationBlock {
+ #[inline]
+ pub fn from_declarations(declarations: Arc<Vec<PropertyDeclaration>>) -> DeclarationBlock {
+ DeclarationBlock {
+ declarations: declarations,
+ source_order: 0,
+ specificity: 0,
+ }
+ }
+}
+
+pub fn matches<E:TElement, N:TNode<E>>(selector_list: &SelectorList, element: &N) -> bool {
+ get_selector_list_selectors(selector_list).iter().any(|selector|
+ selector.pseudo_element.is_none() &&
+ matches_compound_selector(&*selector.compound_selectors, element, &mut false))
+}
+
+
+/// Determines whether the given element matches the given single or compound selector.
+///
+/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
+/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
+/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
+/// `main/css/matching.rs`.)
+fn matches_compound_selector<E:TElement,
+ N:TNode<E>>(
+ selector: &CompoundSelector,
+ element: &N,
+ shareable: &mut bool)
+ -> bool {
+ match matches_compound_selector_internal(selector, element, shareable) {
+ Matched => true,
+ _ => false
+ }
+}
+
+/// A result of selector matching, includes 3 failure types,
+///
+/// NotMatchedAndRestartFromClosestLaterSibling
+/// NotMatchedAndRestartFromClosestDescendant
+/// NotMatchedGlobally
+///
+/// When NotMatchedGlobally appears, stop selector matching completely since
+/// the succeeding selectors never matches.
+/// It is raised when
+/// Child combinator cannot find the candidate element.
+/// Descendant combinator cannot find the candidate element.
+///
+/// When NotMatchedAndRestartFromClosestDescendant appears, the selector
+/// matching does backtracking and restarts from the closest Descendant
+/// combinator.
+/// It is raised when
+/// NextSibling combinator cannot find the candidate element.
+/// LaterSibling combinator cannot find the candidate element.
+/// Child combinator doesn't match on the found element.
+///
+/// When NotMatchedAndRestartFromClosestLaterSibling appears, the selector
+/// matching does backtracking and restarts from the closest LaterSibling
+/// combinator.
+/// It is raised when
+/// NextSibling combinator doesn't match on the found element.
+///
+/// For example, when the selector "d1 d2 a" is provided and we cannot *find*
+/// an appropriate ancestor node for "d1", this selector matching raises
+/// NotMatchedGlobally since even if "d2" is moved to more upper node, the
+/// candidates for "d1" becomes less than before and d1 .
+///
+/// The next example is siblings. When the selector "b1 + b2 ~ d1 a" is
+/// providied and we cannot *find* an appropriate brother node for b1,
+/// the selector matching raises NotMatchedAndRestartFromClosestDescendant.
+/// The selectors ("b1 + b2 ~") doesn't match and matching restart from "d1".
+///
+/// The additional example is child and sibling. When the selector
+/// "b1 + c1 > b2 ~ d1 a" is provided and the selector "b1" doesn't match on
+/// the element, this "b1" raises NotMatchedAndRestartFromClosestLaterSibling.
+/// However since the selector "c1" raises
+/// NotMatchedAndRestartFromClosestDescendant. So the selector
+/// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1".
+enum SelectorMatchingResult {
+ Matched,
+ NotMatchedAndRestartFromClosestLaterSibling,
+ NotMatchedAndRestartFromClosestDescendant,
+ NotMatchedGlobally,
+}
+
+fn matches_compound_selector_internal<E:TElement,
+ N:TNode<E>>(
+ selector: &CompoundSelector,
+ element: &N,
+ shareable: &mut bool)
+ -> SelectorMatchingResult {
+ if !selector.simple_selectors.iter().all(|simple_selector| {
+ matches_simple_selector(simple_selector, element, shareable)
+ }) {
+ return NotMatchedAndRestartFromClosestLaterSibling
+ }
+ match selector.next {
+ None => Matched,
+ Some((ref next_selector, combinator)) => {
+ let (siblings, candidate_not_found) = match combinator {
+ Child => (false, NotMatchedGlobally),
+ Descendant => (false, NotMatchedGlobally),
+ NextSibling => (true, NotMatchedAndRestartFromClosestDescendant),
+ LaterSibling => (true, NotMatchedAndRestartFromClosestDescendant),
+ };
+ let mut node = (*element).clone();
+ loop {
+ let next_node = if siblings {
+ node.prev_sibling()
+ } else {
+ node.parent_node()
+ };
+ match next_node {
+ None => return candidate_not_found,
+ Some(next_node) => node = next_node,
+ }
+ if node.is_element() {
+ let result = matches_compound_selector_internal(&**next_selector,
+ &node,
+ shareable);
+ match (result, combinator) {
+ // Return the status immediately.
+ (Matched, _) => return result,
+ (NotMatchedGlobally, _) => return result,
+
+ // Upgrade the failure status to
+ // NotMatchedAndRestartFromClosestDescendant.
+ (_, Child) => return NotMatchedAndRestartFromClosestDescendant,
+
+ // Return the status directly.
+ (_, NextSibling) => return result,
+
+ // If the failure status is NotMatchedAndRestartFromClosestDescendant
+ // and combinator is LaterSibling, give up this LaterSibling matching
+ // and restart from the closest descendant combinator.
+ (NotMatchedAndRestartFromClosestDescendant, LaterSibling) => return result,
+
+ // The Descendant combinator and the status is
+ // NotMatchedAndRestartFromClosestLaterSibling or
+ // NotMatchedAndRestartFromClosestDescendant,
+ // or the LaterSibling combinator and the status is
+ // NotMatchedAndRestartFromClosestDescendant
+ // can continue to matching on the next candidate element.
+ _ => {},
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Determines whether the given element matches the given single selector.
+///
+/// NB: If you add support for any new kinds of selectors to this routine, be sure to set
+/// `shareable` to false unless you are willing to update the style sharing logic. Otherwise things
+/// will almost certainly break as nodes will start mistakenly sharing styles. (See the code in
+/// `main/css/matching.rs`.)
+#[inline]
+fn matches_simple_selector<E:TElement,
+ N:TNode<E>>(
+ selector: &SimpleSelector,
+ element: &N,
+ shareable: &mut bool)
+ -> bool {
+ match *selector {
+ LocalNameSelector(LocalNameSelector { ref name, ref lower_name }) => {
+ let name = if element.is_html_element_in_html_document() { lower_name } else { name };
+ let element = element.as_element();
+ element.get_local_name() == name
+ }
+
+ NamespaceSelector(ref namespace) => {
+ *shareable = false;
+ let element = element.as_element();
+ element.get_namespace() == namespace
+ }
+ // TODO: case-sensitivity depends on the document type and quirks mode
+ // TODO: cache and intern IDs on elements.
+ IDSelector(ref id) => {
+ *shareable = false;
+ let element = element.as_element();
+ element.get_id().map_or(false, |attr| {
+ attr == *id
+ })
+ }
+ // TODO: cache and intern class names on elements.
+ ClassSelector(ref class) => {
+ let element = element.as_element();
+ element.get_attr(&namespace::Null, "class")
+ .map_or(false, |attr| {
+ // TODO: case-sensitivity depends on the document type and quirks mode
+ attr.split(SELECTOR_WHITESPACE).any(|c| c == class.as_slice())
+ })
+ }
+
+ AttrExists(ref attr) => {
+ *shareable = false;
+ element.match_attr(attr, |_| true)
+ }
+ AttrEqual(ref attr, ref value) => {
+ if value.as_slice() != "DIR" {
+ // FIXME(pcwalton): Remove once we start actually supporting RTL text. This is in
+ // here because the UA style otherwise disables all style sharing completely.
+ *shareable = false
+ }
+ element.match_attr(attr, |attr_value| {
+ attr_value == value.as_slice()
+ })
+ }
+ AttrIncludes(ref attr, ref value) => {
+ *shareable = false;
+ element.match_attr(attr, |attr_value| {
+ attr_value.split(SELECTOR_WHITESPACE).any(|v| v == value.as_slice())
+ })
+ }
+ AttrDashMatch(ref attr, ref value, ref dashing_value) => {
+ *shareable = false;
+ element.match_attr(attr, |attr_value| {
+ attr_value == value.as_slice() ||
+ attr_value.starts_with(dashing_value.as_slice())
+ })
+ }
+ AttrPrefixMatch(ref attr, ref value) => {
+ *shareable = false;
+ element.match_attr(attr, |attr_value| {
+ attr_value.starts_with(value.as_slice())
+ })
+ }
+ AttrSubstringMatch(ref attr, ref value) => {
+ *shareable = false;
+ element.match_attr(attr, |attr_value| {
+ attr_value.contains(value.as_slice())
+ })
+ }
+ AttrSuffixMatch(ref attr, ref value) => {
+ *shareable = false;
+ element.match_attr(attr, |attr_value| {
+ attr_value.ends_with(value.as_slice())
+ })
+ }
+
+ AnyLink => {
+ *shareable = false;
+ let element = element.as_element();
+ element.get_link().is_some()
+ }
+ Link => {
+ *shareable = false;
+ let elem = element.as_element();
+ match elem.get_link() {
+ Some(url) => !url_is_visited(url),
+ None => false,
+ }
+ }
+ Visited => {
+ *shareable = false;
+ let elem = element.as_element();
+ match elem.get_link() {
+ Some(url) => url_is_visited(url),
+ None => false,
+ }
+ }
+
+ Hover => {
+ *shareable = false;
+ let elem = element.as_element();
+ elem.get_hover_state()
+ },
+ // http://www.whatwg.org/html/#selector-disabled
+ Disabled => {
+ *shareable = false;
+ let elem = element.as_element();
+ elem.get_disabled_state()
+ },
+ // http://www.whatwg.org/html/#selector-enabled
+ Enabled => {
+ *shareable = false;
+ let elem = element.as_element();
+ elem.get_enabled_state()
+ },
+ FirstChild => {
+ *shareable = false;
+ matches_first_child(element)
+ }
+ LastChild => {
+ *shareable = false;
+ matches_last_child(element)
+ }
+ OnlyChild => {
+ *shareable = false;
+ matches_first_child(element) && matches_last_child(element)
+ }
+
+ Root => {
+ *shareable = false;
+ matches_root(element)
+ }
+
+ NthChild(a, b) => {
+ *shareable = false;
+ matches_generic_nth_child(element, a, b, false, false)
+ }
+ NthLastChild(a, b) => {
+ *shareable = false;
+ matches_generic_nth_child(element, a, b, false, true)
+ }
+ NthOfType(a, b) => {
+ *shareable = false;
+ matches_generic_nth_child(element, a, b, true, false)
+ }
+ NthLastOfType(a, b) => {
+ *shareable = false;
+ matches_generic_nth_child(element, a, b, true, true)
+ }
+
+ FirstOfType => {
+ *shareable = false;
+ matches_generic_nth_child(element, 0, 1, true, false)
+ }
+ LastOfType => {
+ *shareable = false;
+ matches_generic_nth_child(element, 0, 1, true, true)
+ }
+ OnlyOfType => {
+ *shareable = false;
+ matches_generic_nth_child(element, 0, 1, true, false) &&
+ matches_generic_nth_child(element, 0, 1, true, true)
+ }
+
+ Negation(ref negated) => {
+ *shareable = false;
+ !negated.iter().all(|s| matches_simple_selector(s, element, shareable))
+ },
+ }
+}
+
+fn url_is_visited(_url: &str) -> bool {
+ // FIXME: implement this.
+ // This function will probably need to take a "session"
+ // or something containing browsing history as an additional parameter.
+ false
+}
+
+#[inline]
+fn matches_generic_nth_child<'a,
+ E:TElement,
+ N:TNode<E>>(
+ element: &N,
+ a: i32,
+ b: i32,
+ is_of_type: bool,
+ is_from_end: bool)
+ -> bool {
+ let mut node = element.clone();
+ // fail if we can't find a parent or if the node is the root element
+ // of the document (Cf. Selectors Level 3)
+ match node.parent_node() {
+ Some(parent) => if parent.is_document() {
+ return false;
+ },
+ None => return false
+ };
+
+ let mut index = 1;
+ loop {
+ if is_from_end {
+ match node.next_sibling() {
+ None => break,
+ Some(next_sibling) => node = next_sibling
+ }
+ } else {
+ match node.prev_sibling() {
+ None => break,
+ Some(prev_sibling) => node = prev_sibling
+ }
+ }
+
+ if node.is_element() {
+ if is_of_type {
+ let element = element.as_element();
+ let node = node.as_element();
+ if element.get_local_name() == node.get_local_name() &&
+ element.get_namespace() == node.get_namespace() {
+ index += 1;
+ }
+ } else {
+ index += 1;
+ }
+ }
+
+ }
+
+ if a == 0 {
+ return b == index;
+ }
+
+ let (n, r) = div_rem(index - b, a);
+ n >= 0 && r == 0
+}
+
+#[inline]
+fn matches_root<E:TElement,N:TNode<E>>(element: &N) -> bool {
+ match element.parent_node() {
+ Some(parent) => parent.is_document(),
+ None => false
+ }
+}
+
+#[inline]
+fn matches_first_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
+ let mut node = element.clone();
+ loop {
+ match node.prev_sibling() {
+ Some(prev_sibling) => {
+ node = prev_sibling;
+ if node.is_element() {
+ return false
+ }
+ },
+ None => match node.parent_node() {
+ // Selectors level 3 says :first-child does not match the
+ // root of the document; Warning, level 4 says, for the time
+ // being, the contrary...
+ Some(parent) => return !parent.is_document(),
+ None => return false
+ }
+ }
+ }
+}
+
+#[inline]
+fn matches_last_child<E:TElement,N:TNode<E>>(element: &N) -> bool {
+ let mut node = element.clone();
+ loop {
+ match node.next_sibling() {
+ Some(next_sibling) => {
+ node = next_sibling;
+ if node.is_element() {
+ return false
+ }
+ },
+ None => match node.parent_node() {
+ // Selectors level 3 says :last-child does not match the
+ // root of the document; Warning, level 4 says, for the time
+ // being, the contrary...
+ Some(parent) => return !parent.is_document(),
+ None => return false
+ }
+ }
+ }
+}
+
+
+trait FindPush<K, V> {
+ fn find_push(&mut self, key: K, value: V);
+}
+
+impl<K: Eq + Hash, V> FindPush<K, V> for HashMap<K, Vec<V>> {
+ fn find_push(&mut self, key: K, value: V) {
+ match self.find_mut(&key) {
+ Some(vec) => {
+ vec.push(value);
+ return
+ }
+ None => {}
+ }
+ self.insert(key, vec![value]);
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use servo_util::atom::Atom;
+ use sync::Arc;
+ use super::{DeclarationBlock, Rule, SelectorMap};
+ use selectors::LocalNameSelector;
+
+ /// Helper method to get some Rules from selector strings.
+ /// Each sublist of the result contains the Rules for one StyleRule.
+ fn get_mock_rules(css_selectors: &[&str]) -> Vec<Vec<Rule>> {
+ use namespaces::NamespaceMap;
+ use selectors::parse_selector_list;
+ use cssparser::tokenize;
+
+ let namespaces = NamespaceMap::new();
+ css_selectors.iter().enumerate().map(|(i, selectors)| {
+ parse_selector_list(tokenize(*selectors).map(|(c, _)| c), &namespaces)
+ .unwrap().move_iter().map(|s| {
+ Rule {
+ selector: s.compound_selectors.clone(),
+ declarations: DeclarationBlock {
+ specificity: s.specificity,
+ declarations: Arc::new(vec!()),
+ source_order: i,
+ }
+ }
+ }).collect()
+ }).collect()
+ }
+
+ #[test]
+ fn test_rule_ordering_same_specificity(){
+ let rules_list = get_mock_rules(["a.intro", "img.sidebar"]);
+ let a = &rules_list[0][0].declarations;
+ let b = &rules_list[1][0].declarations;
+ assert!((a.specificity, a.source_order).cmp(&(b.specificity, b.source_order)) == Less,
+ "The rule that comes later should win.");
+ }
+
+ #[test]
+ fn test_get_id_name(){
+ let rules_list = get_mock_rules([".intro", "#top"]);
+ assert_eq!(SelectorMap::get_id_name(&rules_list[0][0]), None);
+ assert_eq!(SelectorMap::get_id_name(&rules_list[1][0]), Some(Atom::from_slice("top")));
+ }
+
+ #[test]
+ fn test_get_class_name(){
+ let rules_list = get_mock_rules([".intro.foo", "#top"]);
+ assert_eq!(SelectorMap::get_class_name(&rules_list[0][0]), Some(Atom::from_slice("intro")));
+ assert_eq!(SelectorMap::get_class_name(rules_list.get(1).get(0)), None);
+ }
+
+ #[test]
+ fn test_get_local_name(){
+ let rules_list = get_mock_rules(["img.foo", "#top", "IMG", "ImG"]);
+ let check = |i, names: Option<(&str, &str)>| {
+ assert!(SelectorMap::get_local_name(&rules_list[i][0])
+ == names.map(|(name, lower_name)| LocalNameSelector {
+ name: Atom::from_slice(name),
+ lower_name: Atom::from_slice(lower_name) }))
+ };
+ check(0, Some(("img", "img")));
+ check(1, None);
+ check(2, Some(("IMG", "img")));
+ check(3, Some(("ImG", "img")));
+ }
+
+ #[test]
+ fn test_insert(){
+ let rules_list = get_mock_rules([".intro.foo", "#top"]);
+ let mut selector_map = SelectorMap::new();
+ selector_map.insert(rules_list[1][0].clone());
+ assert_eq!(1, selector_map.id_hash.find(&Atom::from_slice("top")).unwrap()[0].declarations.source_order);
+ selector_map.insert(rules_list[0][0].clone());
+ assert_eq!(0, selector_map.class_hash.find(&Atom::from_slice("intro")).unwrap()[0].declarations.source_order);
+ assert!(selector_map.class_hash.find(&Atom::from_slice("foo")).is_none());
+ }
+}
diff --git a/components/style/selectors.rs b/components/style/selectors.rs
new file mode 100644
index 00000000000..44b098fa329
--- /dev/null
+++ b/components/style/selectors.rs
@@ -0,0 +1,717 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::{cmp, iter};
+use std::ascii::{StrAsciiExt, OwnedStrAsciiExt};
+use sync::Arc;
+
+use cssparser::ast::*;
+use cssparser::{tokenize, parse_nth};
+
+use servo_util::atom::Atom;
+use servo_util::namespace::Namespace;
+use servo_util::namespace;
+
+use namespaces::NamespaceMap;
+
+
+// Only used in tests
+impl PartialEq for Arc<CompoundSelector> {
+ fn eq(&self, other: &Arc<CompoundSelector>) -> bool {
+ **self == **other
+ }
+}
+
+
+#[deriving(PartialEq, Clone)]
+pub struct Selector {
+ pub compound_selectors: Arc<CompoundSelector>,
+ pub pseudo_element: Option<PseudoElement>,
+ pub specificity: u32,
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum PseudoElement {
+ Before,
+ After,
+// FirstLine,
+// FirstLetter,
+}
+
+
+#[deriving(PartialEq, Clone)]
+pub struct CompoundSelector {
+ pub simple_selectors: Vec<SimpleSelector>,
+ pub next: Option<(Box<CompoundSelector>, Combinator)>, // c.next is left of c
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum Combinator {
+ Child, // >
+ Descendant, // space
+ NextSibling, // +
+ LaterSibling, // ~
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum SimpleSelector {
+ IDSelector(Atom),
+ ClassSelector(Atom),
+ LocalNameSelector(LocalNameSelector),
+ NamespaceSelector(Namespace),
+
+ // Attribute selectors
+ AttrExists(AttrSelector), // [foo]
+ AttrEqual(AttrSelector, String), // [foo=bar]
+ AttrIncludes(AttrSelector, String), // [foo~=bar]
+ AttrDashMatch(AttrSelector, String, String), // [foo|=bar] Second string is the first + "-"
+ AttrPrefixMatch(AttrSelector, String), // [foo^=bar]
+ AttrSubstringMatch(AttrSelector, String), // [foo*=bar]
+ AttrSuffixMatch(AttrSelector, String), // [foo$=bar]
+
+ // Pseudo-classes
+ Negation(Vec<SimpleSelector>),
+ AnyLink,
+ Link,
+ Visited,
+ Hover,
+ Disabled,
+ Enabled,
+ FirstChild, LastChild, OnlyChild,
+// Empty,
+ Root,
+// Lang(String),
+ NthChild(i32, i32),
+ NthLastChild(i32, i32),
+ NthOfType(i32, i32),
+ NthLastOfType(i32, i32),
+ FirstOfType,
+ LastOfType,
+ OnlyOfType
+ // ...
+}
+
+#[deriving(PartialEq, Clone)]
+pub struct LocalNameSelector {
+ pub name: Atom,
+ pub lower_name: Atom,
+}
+
+#[deriving(PartialEq, Clone)]
+pub struct AttrSelector {
+ pub name: String,
+ pub lower_name: String,
+ pub namespace: NamespaceConstraint,
+}
+
+#[deriving(PartialEq, Clone)]
+pub enum NamespaceConstraint {
+ AnyNamespace,
+ SpecificNamespace(Namespace),
+}
+
+
+pub fn parse_selector_list_from_str(input: &str) -> Result<SelectorList, ()> {
+ let namespaces = NamespaceMap::new();
+ let iter = tokenize(input).map(|(token, _)| token);
+ parse_selector_list(iter, &namespaces).map(|s| SelectorList { selectors: s })
+}
+
+/// Re-exported to script, but opaque.
+pub struct SelectorList {
+ selectors: Vec<Selector>
+}
+
+/// Public to the style crate, but not re-exported to script
+pub fn get_selector_list_selectors<'a>(selector_list: &'a SelectorList) -> &'a [Selector] {
+ selector_list.selectors.as_slice()
+}
+
+/// Parse a comma-separated list of Selectors.
+/// aka Selector Group in http://www.w3.org/TR/css3-selectors/#grouping
+///
+/// Return the Selectors or None if there is an invalid selector.
+pub fn parse_selector_list<I: Iterator<ComponentValue>>(
+ iter: I, namespaces: &NamespaceMap)
+ -> Result<Vec<Selector>, ()> {
+ let iter = &mut iter.peekable();
+ let mut results = vec![try!(parse_selector(iter, namespaces))];
+
+ loop {
+ skip_whitespace(iter);
+ match iter.peek() {
+ None => break, // EOF
+ Some(&Comma) => {
+ iter.next();
+ }
+ _ => return Err(()),
+ }
+ results.push(try!(parse_selector(iter, namespaces)));
+ }
+ Ok(results)
+}
+
+
+type Iter<I> = iter::Peekable<ComponentValue, I>;
+
+/// Build up a Selector.
+/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ;
+///
+/// `Err` means invalid selector.
+fn parse_selector<I: Iterator<ComponentValue>>(
+ iter: &mut Iter<I>, namespaces: &NamespaceMap)
+ -> Result<Selector, ()> {
+ let (first, mut pseudo_element) = try!(parse_simple_selectors(iter, namespaces));
+ let mut compound = CompoundSelector{ simple_selectors: first, next: None };
+
+ while pseudo_element.is_none() {
+ let any_whitespace = skip_whitespace(iter);
+ let combinator = match iter.peek() {
+ None => break, // EOF
+ Some(&Comma) => break,
+ Some(&Delim('>')) => { iter.next(); Child },
+ Some(&Delim('+')) => { iter.next(); NextSibling },
+ Some(&Delim('~')) => { iter.next(); LaterSibling },
+ Some(_) => {
+ if any_whitespace { Descendant }
+ else { return Err(()) }
+ }
+ };
+ let (simple_selectors, pseudo) = try!(parse_simple_selectors(iter, namespaces));
+ compound = CompoundSelector {
+ simple_selectors: simple_selectors,
+ next: Some((box compound, combinator))
+ };
+ pseudo_element = pseudo;
+ }
+ Ok(Selector {
+ specificity: compute_specificity(&compound, &pseudo_element),
+ compound_selectors: Arc::new(compound),
+ pseudo_element: pseudo_element,
+ })
+}
+
+
+fn compute_specificity(mut selector: &CompoundSelector,
+ pseudo_element: &Option<PseudoElement>) -> u32 {
+ struct Specificity {
+ id_selectors: u32,
+ class_like_selectors: u32,
+ element_selectors: u32,
+ }
+ let mut specificity = Specificity {
+ id_selectors: 0,
+ class_like_selectors: 0,
+ element_selectors: 0,
+ };
+ if pseudo_element.is_some() { specificity.element_selectors += 1 }
+
+ simple_selectors_specificity(selector.simple_selectors.as_slice(), &mut specificity);
+ loop {
+ match selector.next {
+ None => break,
+ Some((ref next_selector, _)) => {
+ selector = &**next_selector;
+ simple_selectors_specificity(selector.simple_selectors.as_slice(), &mut specificity)
+ }
+ }
+ }
+
+ fn simple_selectors_specificity(simple_selectors: &[SimpleSelector],
+ specificity: &mut Specificity) {
+ for simple_selector in simple_selectors.iter() {
+ match simple_selector {
+ &LocalNameSelector(..) => specificity.element_selectors += 1,
+ &IDSelector(..) => specificity.id_selectors += 1,
+ &ClassSelector(..)
+ | &AttrExists(..) | &AttrEqual(..) | &AttrIncludes(..) | &AttrDashMatch(..)
+ | &AttrPrefixMatch(..) | &AttrSubstringMatch(..) | &AttrSuffixMatch(..)
+ | &AnyLink | &Link | &Visited | &Hover | &Disabled | &Enabled
+ | &FirstChild | &LastChild | &OnlyChild | &Root
+// | &Empty | &Lang(*)
+ | &NthChild(..) | &NthLastChild(..)
+ | &NthOfType(..) | &NthLastOfType(..)
+ | &FirstOfType | &LastOfType | &OnlyOfType
+ => specificity.class_like_selectors += 1,
+ &NamespaceSelector(..) => (),
+ &Negation(ref negated)
+ => simple_selectors_specificity(negated.as_slice(), specificity),
+ }
+ }
+ }
+
+ static MAX_10BIT: u32 = (1u32 << 10) - 1;
+ cmp::min(specificity.id_selectors, MAX_10BIT) << 20
+ | cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10
+ | cmp::min(specificity.element_selectors, MAX_10BIT)
+}
+
+
+/// simple_selector_sequence
+/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]*
+/// | [ HASH | class | attrib | pseudo | negation ]+
+///
+/// `Err(())` means invalid selector
+fn parse_simple_selectors<I: Iterator<ComponentValue>>(
+ iter: &mut Iter<I>, namespaces: &NamespaceMap)
+ -> Result<(Vec<SimpleSelector>, Option<PseudoElement>), ()> {
+ let mut empty = true;
+ let mut simple_selectors = match try!(parse_type_selector(iter, namespaces)) {
+ None => vec![],
+ Some(s) => { empty = false; s }
+ };
+
+ let mut pseudo_element = None;
+ loop {
+ match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ false)) {
+ None => break,
+ Some(SimpleSelectorResult(s)) => { simple_selectors.push(s); empty = false },
+ Some(PseudoElementResult(p)) => { pseudo_element = Some(p); empty = false; break },
+ }
+ }
+ if empty { Err(()) } // An empty selector is invalid
+ else { Ok((simple_selectors, pseudo_element)) }
+}
+
+
+/// * `Err(())`: Invalid selector, abort
+/// * `Ok(None)`: Not a type selector, could be something else. `iter` was not consumed.
+/// * `Ok(Some(vec))`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`)
+fn parse_type_selector<I: Iterator<ComponentValue>>(
+ iter: &mut Iter<I>, namespaces: &NamespaceMap)
+ -> Result<Option<Vec<SimpleSelector>>, ()> {
+ skip_whitespace(iter);
+ match try!(parse_qualified_name(iter, /* in_attr_selector = */ false, namespaces)) {
+ None => Ok(None),
+ Some((namespace, local_name)) => {
+ let mut simple_selectors = vec!();
+ match namespace {
+ SpecificNamespace(ns) => simple_selectors.push(NamespaceSelector(ns)),
+ AnyNamespace => (),
+ }
+ match local_name {
+ Some(name) => {
+ simple_selectors.push(LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice(name.as_slice()),
+ lower_name: Atom::from_slice(name.into_ascii_lower().as_slice())
+ }))
+ }
+ None => (),
+ }
+ Ok(Some(simple_selectors))
+ }
+ }
+}
+
+
+enum SimpleSelectorParseResult {
+ SimpleSelectorResult(SimpleSelector),
+ PseudoElementResult(PseudoElement),
+}
+
+/// Parse a simple selector other than a type selector.
+///
+/// * `Err(())`: Invalid selector, abort
+/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed.
+/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element
+fn parse_one_simple_selector<I: Iterator<ComponentValue>>(
+ iter: &mut Iter<I>, namespaces: &NamespaceMap, inside_negation: bool)
+ -> Result<Option<SimpleSelectorParseResult>, ()> {
+ match iter.peek() {
+ Some(&IDHash(_)) => match iter.next() {
+ Some(IDHash(id)) => Ok(Some(SimpleSelectorResult(
+ IDSelector(Atom::from_slice(id.as_slice()))))),
+ _ => fail!("Implementation error, this should not happen."),
+ },
+ Some(&Delim('.')) => {
+ iter.next();
+ match iter.next() {
+ Some(Ident(class)) => Ok(Some(SimpleSelectorResult(
+ ClassSelector(Atom::from_slice(class.as_slice()))))),
+ _ => Err(()),
+ }
+ }
+ Some(&SquareBracketBlock(_)) => match iter.next() {
+ Some(SquareBracketBlock(content))
+ => Ok(Some(SimpleSelectorResult(try!(parse_attribute_selector(content, namespaces))))),
+ _ => fail!("Implementation error, this should not happen."),
+ },
+ Some(&Colon) => {
+ iter.next();
+ match iter.next() {
+ Some(Ident(name)) => match parse_simple_pseudo_class(name.as_slice()) {
+ Err(()) => {
+ match name.as_slice().to_ascii_lower().as_slice() {
+ // Supported CSS 2.1 pseudo-elements only.
+ // ** Do not add to this list! **
+ "before" => Ok(Some(PseudoElementResult(Before))),
+ "after" => Ok(Some(PseudoElementResult(After))),
+// "first-line" => PseudoElementResult(FirstLine),
+// "first-letter" => PseudoElementResult(FirstLetter),
+ _ => Err(())
+ }
+ },
+ Ok(result) => Ok(Some(SimpleSelectorResult(result))),
+ },
+ Some(Function(name, arguments))
+ => Ok(Some(SimpleSelectorResult(try!(parse_functional_pseudo_class(
+ name, arguments, namespaces, inside_negation))))),
+ Some(Colon) => {
+ match iter.next() {
+ Some(Ident(name))
+ => Ok(Some(PseudoElementResult(try!(parse_pseudo_element(name))))),
+ _ => Err(()),
+ }
+ }
+ _ => Err(()),
+ }
+ }
+ _ => Ok(None),
+ }
+}
+
+
+/// * `Err(())`: Invalid selector, abort
+/// * `Ok(None)`: Not a simple selector, could be something else. `iter` was not consumed.
+/// * `Ok(Some((namespace, local_name)))`: `None` for the local name means a `*` universal selector
+fn parse_qualified_name<I: Iterator<ComponentValue>>(
+ iter: &mut Iter<I>, in_attr_selector: bool, namespaces: &NamespaceMap)
+ -> Result<Option<(NamespaceConstraint, Option<String>)>, ()> {
+ let default_namespace = |local_name| {
+ let namespace = match namespaces.default {
+ Some(ref ns) => SpecificNamespace(ns.clone()),
+ None => AnyNamespace,
+ };
+ Ok(Some((namespace, local_name)))
+ };
+
+ let explicit_namespace = |iter: &mut Iter<I>, namespace| {
+ assert!(iter.next() == Some(Delim('|')),
+ "Implementation error, this should not happen.");
+ match iter.peek() {
+ Some(&Delim('*')) if !in_attr_selector => {
+ iter.next();
+ Ok(Some((namespace, None)))
+ },
+ Some(&Ident(_)) => {
+ let local_name = get_next_ident(iter);
+ Ok(Some((namespace, Some(local_name))))
+ },
+ _ => Err(()),
+ }
+ };
+
+ match iter.peek() {
+ Some(&Ident(_)) => {
+ let value = get_next_ident(iter);
+ match iter.peek() {
+ Some(&Delim('|')) => {
+ let namespace = match namespaces.prefix_map.find(&value) {
+ None => return Err(()), // Undeclared namespace prefix
+ Some(ref ns) => (*ns).clone(),
+ };
+ explicit_namespace(iter, SpecificNamespace(namespace))
+ },
+ _ if in_attr_selector => Ok(Some(
+ (SpecificNamespace(namespace::Null), Some(value)))),
+ _ => default_namespace(Some(value)),
+ }
+ },
+ Some(&Delim('*')) => {
+ iter.next(); // Consume '*'
+ match iter.peek() {
+ Some(&Delim('|')) => explicit_namespace(iter, AnyNamespace),
+ _ => {
+ if !in_attr_selector { default_namespace(None) }
+ else { Err(()) }
+ },
+ }
+ },
+ Some(&Delim('|')) => explicit_namespace(iter, SpecificNamespace(namespace::Null)),
+ _ => Ok(None),
+ }
+}
+
+
+fn parse_attribute_selector(content: Vec<ComponentValue>, namespaces: &NamespaceMap)
+ -> Result<SimpleSelector, ()> {
+ let iter = &mut content.move_iter().peekable();
+ let attr = match try!(parse_qualified_name(iter, /* in_attr_selector = */ true, namespaces)) {
+ None => return Err(()),
+ Some((_, None)) => fail!("Implementation error, this should not happen."),
+ Some((namespace, Some(local_name))) => AttrSelector {
+ namespace: namespace,
+ lower_name: local_name.as_slice().to_ascii_lower(),
+ name: local_name,
+ },
+ };
+ skip_whitespace(iter);
+ // TODO: deal with empty value or value containing whitespace (see spec)
+ macro_rules! get_value( () => {{
+ skip_whitespace(iter);
+ match iter.next() {
+ Some(Ident(value)) | Some(String(value)) => value,
+ _ => return Err(())
+ }
+ }};)
+ let result = match iter.next() {
+ None => AttrExists(attr), // [foo]
+ Some(Delim('=')) => AttrEqual(attr, (get_value!())), // [foo=bar]
+ Some(IncludeMatch) => AttrIncludes(attr, (get_value!())), // [foo~=bar]
+ Some(DashMatch) => {
+ let value = get_value!();
+ let dashing_value = format!("{}-", value);
+ AttrDashMatch(attr, value, dashing_value) // [foo|=bar]
+ },
+ Some(PrefixMatch) => AttrPrefixMatch(attr, (get_value!())), // [foo^=bar]
+ Some(SubstringMatch) => AttrSubstringMatch(attr, (get_value!())), // [foo*=bar]
+ Some(SuffixMatch) => AttrSuffixMatch(attr, (get_value!())), // [foo$=bar]
+ _ => return Err(())
+ };
+ skip_whitespace(iter);
+ if iter.next().is_none() { Ok(result) } else { Err(()) }
+}
+
+
+fn parse_simple_pseudo_class(name: &str) -> Result<SimpleSelector, ()> {
+ match name.to_ascii_lower().as_slice() {
+ "any-link" => Ok(AnyLink),
+ "link" => Ok(Link),
+ "visited" => Ok(Visited),
+ "hover" => Ok(Hover),
+ "disabled" => Ok(Disabled),
+ "enabled" => Ok(Enabled),
+ "first-child" => Ok(FirstChild),
+ "last-child" => Ok(LastChild),
+ "only-child" => Ok(OnlyChild),
+ "root" => Ok(Root),
+ "first-of-type" => Ok(FirstOfType),
+ "last-of-type" => Ok(LastOfType),
+ "only-of-type" => Ok(OnlyOfType),
+// "empty" => Ok(Empty),
+ _ => Err(())
+ }
+}
+
+
+fn parse_functional_pseudo_class(name: String, arguments: Vec<ComponentValue>,
+ namespaces: &NamespaceMap, inside_negation: bool)
+ -> Result<SimpleSelector, ()> {
+ match name.as_slice().to_ascii_lower().as_slice() {
+// "lang" => parse_lang(arguments),
+ "nth-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthChild(a, b)),
+ "nth-last-child" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastChild(a, b)),
+ "nth-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthOfType(a, b)),
+ "nth-last-of-type" => parse_nth(arguments.as_slice()).map(|(a, b)| NthLastOfType(a, b)),
+ "not" => if inside_negation { Err(()) } else { parse_negation(arguments, namespaces) },
+ _ => Err(())
+ }
+}
+
+
+fn parse_pseudo_element(name: String) -> Result<PseudoElement, ()> {
+ match name.as_slice().to_ascii_lower().as_slice() {
+ // All supported pseudo-elements
+ "before" => Ok(Before),
+ "after" => Ok(After),
+// "first-line" => Some(FirstLine),
+// "first-letter" => Some(FirstLetter),
+ _ => Err(())
+ }
+}
+
+
+//fn parse_lang(arguments: vec!(ComponentValue)) -> Result<SimpleSelector, ()> {
+// let mut iter = arguments.move_skip_whitespace();
+// match iter.next() {
+// Some(Ident(value)) => {
+// if "" == value || iter.next().is_some() { None }
+// else { Ok(Lang(value)) }
+// },
+// _ => Err(()),
+// }
+//}
+
+
+/// Level 3: Parse **one** simple_selector
+fn parse_negation(arguments: Vec<ComponentValue>, namespaces: &NamespaceMap)
+ -> Result<SimpleSelector, ()> {
+ let iter = &mut arguments.move_iter().peekable();
+ match try!(parse_type_selector(iter, namespaces)) {
+ Some(type_selector) => Ok(Negation(type_selector)),
+ None => {
+ match try!(parse_one_simple_selector(iter, namespaces, /* inside_negation = */ true)) {
+ Some(SimpleSelectorResult(simple_selector)) => Ok(Negation(vec![simple_selector])),
+ _ => Err(())
+ }
+ },
+ }
+}
+
+
+/// Assuming the next token is an ident, consume it and return its value
+#[inline]
+fn get_next_ident<I: Iterator<ComponentValue>>(iter: &mut Iter<I>) -> String {
+ match iter.next() {
+ Some(Ident(value)) => value,
+ _ => fail!("Implementation error, this should not happen."),
+ }
+}
+
+
+#[inline]
+fn skip_whitespace<I: Iterator<ComponentValue>>(iter: &mut Iter<I>) -> bool {
+ let mut any_whitespace = false;
+ loop {
+ if iter.peek() != Some(&WhiteSpace) { return any_whitespace }
+ any_whitespace = true;
+ iter.next();
+ }
+}
+
+
+#[cfg(test)]
+mod tests {
+ use sync::Arc;
+ use cssparser;
+ use servo_util::atom::Atom;
+ use servo_util::namespace;
+ use namespaces::NamespaceMap;
+ use super::*;
+
+ fn parse(input: &str) -> Result<Vec<Selector>, ()> {
+ parse_ns(input, &NamespaceMap::new())
+ }
+
+ fn parse_ns(input: &str, namespaces: &NamespaceMap) -> Result<Vec<Selector>, ()> {
+ parse_selector_list(cssparser::tokenize(input).map(|(v, _)| v), namespaces)
+ }
+
+ fn specificity(a: u32, b: u32, c: u32) -> u32 {
+ a << 20 | b << 10 | c
+ }
+
+ #[test]
+ fn test_parsing() {
+ assert!(parse("") == Err(()))
+ assert!(parse("EeÉ") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice("EeÉ"),
+ lower_name: Atom::from_slice("eeÉ") })),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(0, 0, 1),
+ })))
+ assert!(parse(".foo") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(ClassSelector(Atom::from_slice("foo"))),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(0, 1, 0),
+ })))
+ assert!(parse("#bar") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(IDSelector(Atom::from_slice("bar"))),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(1, 0, 0),
+ })))
+ assert!(parse("e.foo#bar") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice("e"),
+ lower_name: Atom::from_slice("e") }),
+ ClassSelector(Atom::from_slice("foo")),
+ IDSelector(Atom::from_slice("bar"))),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(1, 1, 1),
+ })))
+ assert!(parse("e.foo #bar") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(IDSelector(Atom::from_slice("bar"))),
+ next: Some((box CompoundSelector {
+ simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice("e"),
+ lower_name: Atom::from_slice("e") }),
+ ClassSelector(Atom::from_slice("foo"))),
+ next: None,
+ }, Descendant)),
+ }),
+ pseudo_element: None,
+ specificity: specificity(1, 1, 1),
+ })))
+ // Default namespace does not apply to attribute selectors
+ // https://github.com/mozilla/servo/pull/1652
+ let mut namespaces = NamespaceMap::new();
+ assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(AttrExists(AttrSelector {
+ name: "Foo".to_string(),
+ lower_name: "foo".to_string(),
+ namespace: SpecificNamespace(namespace::Null),
+ })),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(0, 1, 0),
+ })))
+ // Default namespace does not apply to attribute selectors
+ // https://github.com/mozilla/servo/pull/1652
+ namespaces.default = Some(namespace::MathML);
+ assert!(parse_ns("[Foo]", &namespaces) == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(AttrExists(AttrSelector {
+ name: "Foo".to_string(),
+ lower_name: "foo".to_string(),
+ namespace: SpecificNamespace(namespace::Null),
+ })),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(0, 1, 0),
+ })))
+ // Default namespace does apply to type selectors
+ assert!(parse_ns("e", &namespaces) == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(
+ NamespaceSelector(namespace::MathML),
+ LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice("e"),
+ lower_name: Atom::from_slice("e") }),
+ ),
+ next: None,
+ }),
+ pseudo_element: None,
+ specificity: specificity(0, 0, 1),
+ })))
+ // https://github.com/mozilla/servo/issues/1723
+ assert!(parse("::before") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(),
+ next: None,
+ }),
+ pseudo_element: Some(Before),
+ specificity: specificity(0, 0, 1),
+ })))
+ assert!(parse("div :after") == Ok(vec!(Selector {
+ compound_selectors: Arc::new(CompoundSelector {
+ simple_selectors: vec!(),
+ next: Some((box CompoundSelector {
+ simple_selectors: vec!(LocalNameSelector(LocalNameSelector {
+ name: Atom::from_slice("div"),
+ lower_name: Atom::from_slice("div") })),
+ next: None,
+ }, Descendant)),
+ }),
+ pseudo_element: Some(After),
+ specificity: specificity(0, 0, 2),
+ })))
+ }
+}
diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs
new file mode 100644
index 00000000000..cc2f1945ca9
--- /dev/null
+++ b/components/style/stylesheets.rs
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::iter::Iterator;
+use std::ascii::StrAsciiExt;
+use url::Url;
+
+use encoding::EncodingRef;
+
+use cssparser::{decode_stylesheet_bytes, tokenize, parse_stylesheet_rules, ToCss};
+use cssparser::ast::*;
+use selectors;
+use properties;
+use errors::{ErrorLoggerIterator, log_css_error};
+use namespaces::{NamespaceMap, parse_namespace_rule};
+use media_queries::{MediaRule, parse_media_rule};
+use media_queries;
+use font_face::{FontFaceRule, parse_font_face_rule, iter_font_face_rules_inner};
+
+
+pub struct Stylesheet {
+ /// List of rules in the order they were found (important for
+ /// cascading order)
+ rules: Vec<CSSRule>,
+}
+
+
+pub enum CSSRule {
+ CSSStyleRule(StyleRule),
+ CSSMediaRule(MediaRule),
+ CSSFontFaceRule(FontFaceRule),
+}
+
+
+pub struct StyleRule {
+ pub selectors: Vec<selectors::Selector>,
+ pub declarations: properties::PropertyDeclarationBlock,
+}
+
+
+impl Stylesheet {
+ pub fn from_bytes_iter<I: Iterator<Vec<u8>>>(
+ mut input: I, base_url: Url, protocol_encoding_label: Option<&str>,
+ environment_encoding: Option<EncodingRef>) -> Stylesheet {
+ let mut bytes = vec!();
+ // TODO: incremental decoding and tokinization/parsing
+ for chunk in input {
+ bytes.push_all(chunk.as_slice())
+ }
+ Stylesheet::from_bytes(bytes.as_slice(), base_url, protocol_encoding_label, environment_encoding)
+ }
+
+ pub fn from_bytes(
+ bytes: &[u8], base_url: Url, protocol_encoding_label: Option<&str>,
+ environment_encoding: Option<EncodingRef>) -> Stylesheet {
+ // TODO: bytes.as_slice could be bytes.container_as_bytes()
+ let (string, _) = decode_stylesheet_bytes(
+ bytes.as_slice(), protocol_encoding_label, environment_encoding);
+ Stylesheet::from_str(string.as_slice(), base_url)
+ }
+
+ pub fn from_str(css: &str, base_url: Url) -> Stylesheet {
+ static STATE_CHARSET: uint = 1;
+ static STATE_IMPORTS: uint = 2;
+ static STATE_NAMESPACES: uint = 3;
+ static STATE_BODY: uint = 4;
+ let mut state: uint = STATE_CHARSET;
+
+ let mut rules = vec!();
+ let mut namespaces = NamespaceMap::new();
+
+ for rule in ErrorLoggerIterator(parse_stylesheet_rules(tokenize(css))) {
+ let next_state; // Unitialized to force each branch to set it.
+ match rule {
+ QualifiedRule(rule) => {
+ next_state = STATE_BODY;
+ parse_style_rule(rule, &mut rules, &namespaces, &base_url)
+ },
+ AtRule(rule) => {
+ let lower_name = rule.name.as_slice().to_ascii_lower();
+ match lower_name.as_slice() {
+ "charset" => {
+ if state > STATE_CHARSET {
+ log_css_error(rule.location, "@charset must be the first rule")
+ }
+ // Valid @charset rules are just ignored
+ next_state = STATE_IMPORTS;
+ },
+ "import" => {
+ if state > STATE_IMPORTS {
+ next_state = state;
+ log_css_error(rule.location,
+ "@import must be before any rule but @charset")
+ } else {
+ next_state = STATE_IMPORTS;
+ // TODO: support @import
+ log_css_error(rule.location, "@import is not supported yet")
+ }
+ },
+ "namespace" => {
+ if state > STATE_NAMESPACES {
+ next_state = state;
+ log_css_error(
+ rule.location,
+ "@namespace must be before any rule but @charset and @import"
+ )
+ } else {
+ next_state = STATE_NAMESPACES;
+ parse_namespace_rule(rule, &mut namespaces)
+ }
+ },
+ _ => {
+ next_state = STATE_BODY;
+ parse_nested_at_rule(lower_name.as_slice(), rule, &mut rules, &namespaces, &base_url)
+ },
+ }
+ },
+ }
+ state = next_state;
+ }
+ Stylesheet{ rules: rules }
+ }
+}
+
+
+pub fn parse_style_rule(rule: QualifiedRule, parent_rules: &mut Vec<CSSRule>,
+ namespaces: &NamespaceMap, base_url: &Url) {
+ let QualifiedRule{location: location, prelude: prelude, block: block} = rule;
+ // FIXME: avoid doing this for valid selectors
+ let serialized = prelude.iter().to_css();
+ match selectors::parse_selector_list(prelude.move_iter(), namespaces) {
+ Ok(selectors) => parent_rules.push(CSSStyleRule(StyleRule{
+ selectors: selectors,
+ declarations: properties::parse_property_declaration_list(block.move_iter(), base_url)
+ })),
+ Err(()) => log_css_error(location, format!(
+ "Invalid/unsupported selector: {}", serialized).as_slice()),
+ }
+}
+
+
+// lower_name is passed explicitly to avoid computing it twice.
+pub fn parse_nested_at_rule(lower_name: &str, rule: AtRule,
+ parent_rules: &mut Vec<CSSRule>, namespaces: &NamespaceMap, base_url: &Url) {
+ match lower_name {
+ "media" => parse_media_rule(rule, parent_rules, namespaces, base_url),
+ "font-face" => parse_font_face_rule(rule, parent_rules, base_url),
+ _ => log_css_error(rule.location,
+ format!("Unsupported at-rule: @{:s}", lower_name).as_slice())
+ }
+}
+
+
+pub fn iter_style_rules<'a>(rules: &[CSSRule], device: &media_queries::Device,
+ callback: |&StyleRule|) {
+ for rule in rules.iter() {
+ match *rule {
+ CSSStyleRule(ref rule) => callback(rule),
+ CSSMediaRule(ref rule) => if rule.media_queries.evaluate(device) {
+ iter_style_rules(rule.rules.as_slice(), device, |s| callback(s))
+ },
+ CSSFontFaceRule(_) => {},
+ }
+ }
+}
+
+#[inline]
+pub fn iter_stylesheet_style_rules(stylesheet: &Stylesheet, device: &media_queries::Device,
+ callback: |&StyleRule|) {
+ iter_style_rules(stylesheet.rules.as_slice(), device, callback)
+}
+
+
+#[inline]
+pub fn iter_font_face_rules(stylesheet: &Stylesheet, callback: |family: &str, sources: &Url|) {
+ iter_font_face_rules_inner(stylesheet.rules.as_slice(), callback)
+}
diff --git a/components/style/user-agent.css b/components/style/user-agent.css
new file mode 100644
index 00000000000..b52748cb0c1
--- /dev/null
+++ b/components/style/user-agent.css
@@ -0,0 +1,118 @@
+html, address,
+blockquote,
+body, div,
+dt, fieldset, form,
+frame, frameset,
+h1, h2, h3, h4,
+h5, h6, noframes,
+center, dir,
+hr, menu, pre { display: block; unicode-bidi: embed }
+ head, noscript { display: none }
+ table { display: table }
+ tr { display: table-row }
+ thead { display: table-header-group }
+ tbody { display: table-row-group }
+ tfoot { display: table-footer-group }
+ col { display: table-column }
+ colgroup { display: table-column-group }
+ td, th { display: table-cell }
+ caption { display: table-caption }
+ th { font-weight: bolder; text-align: center }
+ caption { text-align: center }
+ body { margin: 8px }
+ h1 { font-size: 2em; margin: .67em 0 }
+ h2 { font-size: 1.5em; margin: .75em 0 }
+ h3 { font-size: 1.17em; margin: .83em 0 }
+h4,
+blockquote,
+fieldset, form,
+dir, menu { margin: 1.12em 0 }
+ h5 { font-size: .83em; margin: 1.5em 0 }
+ h6 { font-size: .75em; margin: 1.67em 0 }
+h1, h2, h3, h4,
+h5, h6, b,
+ strong { font-weight: bolder }
+ blockquote { margin-left: 40px; margin-right: 40px }
+i, cite, em,
+ var, address { font-style: italic }
+pre, tt, code,
+ kbd, samp { font-family: monospace }
+ pre { white-space: pre }
+button, textarea,
+ input, select { display: inline-block }
+ big { font-size: 1.17em }
+ small, sub, sup { font-size: .83em }
+ sub { vertical-align: sub }
+ sup { vertical-align: super }
+ table { border-spacing: 2px; }
+thead, tbody,
+ tfoot { vertical-align: middle }
+ td, th, tr { vertical-align: inherit }
+ s, strike, del { text-decoration: line-through }
+ hr { border: 1px inset }
+
+/* lists */
+dd { display: block; margin-left: 40px }
+p, dl, multicol { display: block; margin: 1em 0 }
+ul { display: block; list-style-type: disc;
+ margin: 1em 0; padding-left: 40px }
+
+ol { display: block; list-style-type: decimal;
+ margin: 1em 0; padding-left: 40px }
+
+li { display: list-item }
+
+/* nested lists have no top/bottom margins */
+ul ul, ul ol, ul dl,
+ol ul, ol ol, ol dl,
+dl ul, dl ol, dl dl { margin-top: 0; margin-bottom: 0 }
+
+/* 2 deep unordered lists use a circle */
+ol ul, ul ul { list-style-type: circle; }
+
+/* 3 deep (or more) unordered lists use a square */
+ol ol ul, ol ul ul,
+ul ol ul, ul ul ul { list-style-type: square; }
+
+/* The type attribute on ol and ul elements */
+ul[type="disc"] { list-style-type: disc; }
+ul[type="circle"] { list-style-type: circle; }
+ul[type="square"] { list-style-type: square; }
+ol[type="1"] { list-style-type: decimal; }
+ol[type="a"] { list-style-type: lower-alpha; }
+ol[type="A"] { list-style-type: upper-alpha; }
+ol[type="i"] { list-style-type: lower-roman; }
+ol[type="I"] { list-style-type: upper-roman; }
+
+u, ins { text-decoration: underline }
+br:before { content: "\A"; white-space: pre }
+
+center { text-align: center }
+a:link,
+a:visited,
+area:link,
+area:visited,
+link:link,
+link:visited { text-decoration: underline }
+:focus { outline: thin dotted invert }
+
+/* Begin bidirectionality settings (do not change) */
+BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override }
+BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override }
+
+*[DIR="ltr"] { direction: ltr; unicode-bidi: embed }
+*[DIR="rtl"] { direction: rtl; unicode-bidi: embed }
+
+@media print {
+h1 { page-break-before: always }
+h1, h2, h3,
+h4, h5, h6 { page-break-after: avoid }
+ul, ol, dl { page-break-before: avoid }
+}
+
+/* Servo additions */
+a:link,
+area:link,
+link:link { color: blue }
+script { display: none }
+style { display: none }
diff --git a/components/util/Cargo.toml b/components/util/Cargo.toml
new file mode 100644
index 00000000000..be6662b76f6
--- /dev/null
+++ b/components/util/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "util"
+version = "0.0.1"
+authors = ["The Servo Project Developers"]
+
+[lib]
+name = "util"
+path = "lib.rs"
+
+[dependencies.azure]
+git = "https://github.com/servo/rust-azure"
+
+[dependencies.geom]
+git = "https://github.com/servo/rust-geom"
+
+[dependencies.task_info]
+path = "../../support/rust-task_info"
+
+[dependencies.string_cache]
+git = "https://github.com/servo/string-cache"
diff --git a/components/util/atom.rs b/components/util/atom.rs
new file mode 100644
index 00000000000..49cb047768e
--- /dev/null
+++ b/components/util/atom.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Provides a wrapper around the Atom type in the string cache
+//! crate. It's needed so that it can implement the Encodable
+//! trait which is required by Servo.
+
+use serialize::{Encoder, Encodable};
+use std::fmt;
+use std::hash::Hash;
+use string_cache::atom;
+
+#[deriving(Clone, Eq, Hash, PartialEq)]
+pub struct Atom {
+ atom: atom::Atom,
+}
+
+impl Atom {
+ #[inline(always)]
+ pub fn from_slice(slice: &str) -> Atom {
+ Atom {
+ atom: atom::Atom::from_slice(slice)
+ }
+ }
+
+ #[inline(always)]
+ pub fn as_slice<'t>(&'t self) -> &'t str {
+ self.atom.as_slice()
+ }
+}
+
+impl fmt::Show for Atom {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{:s}", self.atom.as_slice())
+ }
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Atom {
+ fn encode(&self, _s: &mut S) -> Result<(), E> {
+ Ok(())
+ }
+}
diff --git a/components/util/cache.rs b/components/util/cache.rs
new file mode 100644
index 00000000000..1b159cea8c1
--- /dev/null
+++ b/components/util/cache.rs
@@ -0,0 +1,279 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::hashmap::HashMap;
+use rand::Rng;
+use std::hash::{Hash, sip};
+use std::rand::task_rng;
+use std::slice::Items;
+
+#[cfg(test)]
+use std::cell::Cell;
+
+pub trait Cache<K: PartialEq, V: Clone> {
+ fn insert(&mut self, key: K, value: V);
+ fn find(&mut self, key: &K) -> Option<V>;
+ fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V;
+ fn evict_all(&mut self);
+}
+
+pub struct MonoCache<K, V> {
+ entry: Option<(K,V)>,
+}
+
+impl<K: Clone + PartialEq, V: Clone> MonoCache<K,V> {
+ pub fn new(_size: uint) -> MonoCache<K,V> {
+ MonoCache { entry: None }
+ }
+}
+
+impl<K: Clone + PartialEq, V: Clone> Cache<K,V> for MonoCache<K,V> {
+ fn insert(&mut self, key: K, value: V) {
+ self.entry = Some((key, value));
+ }
+
+ fn find(&mut self, key: &K) -> Option<V> {
+ match self.entry {
+ None => None,
+ Some((ref k, ref v)) => if *k == *key { Some(v.clone()) } else { None }
+ }
+ }
+
+ fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V {
+ match self.find(key) {
+ Some(value) => value,
+ None => {
+ let value = blk(key);
+ self.entry = Some((key.clone(), value.clone()));
+ value
+ }
+ }
+ }
+
+ fn evict_all(&mut self) {
+ self.entry = None;
+ }
+}
+
+#[test]
+fn test_monocache() {
+ let mut cache: MonoCache<uint,Cell<&str>> = MonoCache::new(10);
+ let one = Cell::new("one");
+ let two = Cell::new("two");
+ cache.insert(1, one);
+
+ assert!(cache.find(&1).is_some());
+ assert!(cache.find(&2).is_none());
+ cache.find_or_create(&2, |_v| { two });
+ assert!(cache.find(&2).is_some());
+ assert!(cache.find(&1).is_none());
+}
+
+pub struct HashCache<K, V> {
+ entries: HashMap<K, V>,
+}
+
+impl<K: Clone + PartialEq + Eq + Hash, V: Clone> HashCache<K,V> {
+ pub fn new() -> HashCache<K, V> {
+ HashCache {
+ entries: HashMap::new(),
+ }
+ }
+}
+
+impl<K: Clone + PartialEq + Eq + Hash, V: Clone> Cache<K,V> for HashCache<K,V> {
+ fn insert(&mut self, key: K, value: V) {
+ self.entries.insert(key, value);
+ }
+
+ fn find(&mut self, key: &K) -> Option<V> {
+ match self.entries.find(key) {
+ Some(v) => Some(v.clone()),
+ None => None,
+ }
+ }
+
+ fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V {
+ self.entries.find_or_insert_with(key.clone(), blk).clone()
+ }
+
+ fn evict_all(&mut self) {
+ self.entries.clear();
+ }
+}
+
+#[test]
+fn test_hashcache() {
+ let mut cache: HashCache<uint, Cell<&str>> = HashCache::new();
+ let one = Cell::new("one");
+ let two = Cell::new("two");
+
+ cache.insert(1, one);
+ assert!(cache.find(&1).is_some());
+ assert!(cache.find(&2).is_none());
+
+ cache.find_or_create(&2, |_v| { two });
+ assert!(cache.find(&1).is_some());
+ assert!(cache.find(&2).is_some());
+}
+
+pub struct LRUCache<K, V> {
+ entries: Vec<(K, V)>,
+ cache_size: uint,
+}
+
+impl<K: Clone + PartialEq, V: Clone> LRUCache<K,V> {
+ pub fn new(size: uint) -> LRUCache<K, V> {
+ LRUCache {
+ entries: vec!(),
+ cache_size: size,
+ }
+ }
+
+ #[inline]
+ pub fn touch(&mut self, pos: uint) -> V {
+ let last_index = self.entries.len() - 1;
+ if pos != last_index {
+ let entry = self.entries.remove(pos);
+ self.entries.push(entry.unwrap());
+ }
+ self.entries[last_index].ref1().clone()
+ }
+
+ pub fn iter<'a>(&'a self) -> Items<'a,(K,V)> {
+ self.entries.iter()
+ }
+}
+
+impl<K: Clone + PartialEq, V: Clone> Cache<K,V> for LRUCache<K,V> {
+ fn insert(&mut self, key: K, val: V) {
+ if self.entries.len() == self.cache_size {
+ self.entries.remove(0);
+ }
+ self.entries.push((key, val));
+ }
+
+ fn find(&mut self, key: &K) -> Option<V> {
+ match self.entries.iter().position(|&(ref k, _)| *k == *key) {
+ Some(pos) => Some(self.touch(pos)),
+ None => None,
+ }
+ }
+
+ fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V {
+ match self.entries.iter().position(|&(ref k, _)| *k == *key) {
+ Some(pos) => self.touch(pos),
+ None => {
+ let val = blk(key);
+ self.insert(key.clone(), val.clone());
+ val
+ }
+ }
+ }
+
+ fn evict_all(&mut self) {
+ self.entries.clear();
+ }
+}
+
+pub struct SimpleHashCache<K,V> {
+ entries: Vec<Option<(K,V)>>,
+ k0: u64,
+ k1: u64,
+}
+
+impl<K:Clone+PartialEq+Hash,V:Clone> SimpleHashCache<K,V> {
+ pub fn new(cache_size: uint) -> SimpleHashCache<K,V> {
+ let mut r = task_rng();
+ SimpleHashCache {
+ entries: Vec::from_elem(cache_size, None),
+ k0: r.gen(),
+ k1: r.gen(),
+ }
+ }
+
+ #[inline]
+ fn to_bucket(&self, h: uint) -> uint {
+ h % self.entries.len()
+ }
+
+ #[inline]
+ fn bucket_for_key<Q:Hash>(&self, key: &Q) -> uint {
+ self.to_bucket(sip::hash_with_keys(self.k0, self.k1, key) as uint)
+ }
+
+ #[inline]
+ pub fn find_equiv<'a,Q:Hash+Equiv<K>>(&'a self, key: &Q) -> Option<&'a V> {
+ let bucket_index = self.bucket_for_key(key);
+ match self.entries[bucket_index] {
+ Some((ref existing_key, ref value)) if key.equiv(existing_key) => Some(value),
+ _ => None,
+ }
+ }
+}
+
+impl<K:Clone+PartialEq+Hash,V:Clone> Cache<K,V> for SimpleHashCache<K,V> {
+ fn insert(&mut self, key: K, value: V) {
+ let bucket_index = self.bucket_for_key(&key);
+ *self.entries.get_mut(bucket_index) = Some((key, value));
+ }
+
+ fn find(&mut self, key: &K) -> Option<V> {
+ let bucket_index = self.bucket_for_key(key);
+ match self.entries[bucket_index] {
+ Some((ref existing_key, ref value)) if existing_key == key => Some((*value).clone()),
+ _ => None,
+ }
+ }
+
+ fn find_or_create(&mut self, key: &K, blk: |&K| -> V) -> V {
+ match self.find(key) {
+ Some(value) => return value,
+ None => {}
+ }
+ let value = blk(key);
+ self.insert((*key).clone(), value.clone());
+ value
+ }
+
+ fn evict_all(&mut self) {
+ for slot in self.entries.mut_iter() {
+ *slot = None
+ }
+ }
+}
+
+#[test]
+fn test_lru_cache() {
+ let one = Cell::new("one");
+ let two = Cell::new("two");
+ let three = Cell::new("three");
+ let four = Cell::new("four");
+
+ // Test normal insertion.
+ let mut cache: LRUCache<uint,Cell<&str>> = LRUCache::new(2); // (_, _) (cache is empty)
+ cache.insert(1, one); // (1, _)
+ cache.insert(2, two); // (1, 2)
+ cache.insert(3, three); // (2, 3)
+
+ assert!(cache.find(&1).is_none()); // (2, 3) (no change)
+ assert!(cache.find(&3).is_some()); // (2, 3)
+ assert!(cache.find(&2).is_some()); // (3, 2)
+
+ // Test that LRU works (this insertion should replace 3, not 2).
+ cache.insert(4, four); // (2, 4)
+
+ assert!(cache.find(&1).is_none()); // (2, 4) (no change)
+ assert!(cache.find(&2).is_some()); // (4, 2)
+ assert!(cache.find(&3).is_none()); // (4, 2) (no change)
+ assert!(cache.find(&4).is_some()); // (2, 4) (no change)
+
+ // Test find_or_create.
+ cache.find_or_create(&1, |_| { one }); // (4, 1)
+
+ assert!(cache.find(&1).is_some()); // (4, 1) (no change)
+ assert!(cache.find(&2).is_none()); // (4, 1) (no change)
+ assert!(cache.find(&3).is_none()); // (4, 1) (no change)
+ assert!(cache.find(&4).is_some()); // (1, 4)
+}
diff --git a/components/util/debug_utils.rs b/components/util/debug_utils.rs
new file mode 100644
index 00000000000..e8d6cd31fea
--- /dev/null
+++ b/components/util/debug_utils.rs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::io;
+use std::io::Writer;
+use std::mem;
+use std::mem::size_of;
+use std::slice::raw::buf_as_slice;
+
+fn hexdump_slice(buf: &[u8]) {
+ let mut stderr = io::stderr();
+ stderr.write(b" ").unwrap();
+ for (i, &v) in buf.iter().enumerate() {
+ let output = format!("{:02X} ", v as uint);
+ stderr.write(output.as_bytes()).unwrap();
+ match i % 16 {
+ 15 => { stderr.write(b"\n ").unwrap(); },
+ 7 => { stderr.write(b" ").unwrap(); },
+ _ => ()
+ }
+ stderr.flush().unwrap();
+ }
+ stderr.write(b"\n").unwrap();
+}
+
+pub fn hexdump<T>(obj: &T) {
+ unsafe {
+ let buf: *const u8 = mem::transmute(obj);
+ debug!("dumping at {:p}", buf);
+ buf_as_slice(buf, size_of::<T>(), hexdump_slice);
+ }
+}
diff --git a/components/util/geometry.rs b/components/util/geometry.rs
new file mode 100644
index 00000000000..c87e98e38b7
--- /dev/null
+++ b/components/util/geometry.rs
@@ -0,0 +1,304 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use geom::length::Length;
+use geom::point::Point2D;
+use geom::rect::Rect;
+use geom::size::Size2D;
+
+use serialize::{Encodable, Encoder};
+use std::default::Default;
+use std::num::{NumCast, One, Zero};
+use std::fmt;
+
+// Units for use with geom::length and geom::scale_factor.
+
+/// A normalized "pixel" at the default resolution for the display.
+///
+/// Like the CSS "px" unit, the exact physical size of this unit may vary between devices, but it
+/// should approximate a device-independent reference length. This unit corresponds to Android's
+/// "density-independent pixel" (dip), Mac OS X's "point", and Windows "device-independent pixel."
+///
+/// The relationship between DevicePixel and ScreenPx is defined by the OS. On most low-dpi
+/// screens, one ScreenPx is equal to one DevicePixel. But on high-density screens it can be
+/// some larger number. For example, by default on Apple "retina" displays, one ScreenPx equals
+/// two DevicePixels. On Android "MDPI" displays, one ScreenPx equals 1.5 device pixels.
+///
+/// The ratio between ScreenPx and DevicePixel for a given display be found by calling
+/// `servo::windowing::WindowMethods::hidpi_factor`.
+pub enum ScreenPx {}
+
+/// One CSS "px" in the coordinate system of the "initial viewport":
+/// http://www.w3.org/TR/css-device-adapt/#initial-viewport
+///
+/// ViewportPx is equal to ScreenPx times a "page zoom" factor controlled by the user. This is
+/// the desktop-style "full page" zoom that enlarges content but then reflows the layout viewport
+/// so it still exactly fits the visible area.
+///
+/// At the default zoom level of 100%, one PagePx is equal to one ScreenPx. However, if the
+/// document is zoomed in or out then this scale may be larger or smaller.
+#[deriving(Encodable)]
+pub enum ViewportPx {}
+
+/// One CSS "px" in the root coordinate system for the content document.
+///
+/// PagePx is equal to ViewportPx multiplied by a "viewport zoom" factor controlled by the user.
+/// This is the mobile-style "pinch zoom" that enlarges content without reflowing it. When the
+/// viewport zoom is not equal to 1.0, then the layout viewport is no longer the same physical size
+/// as the viewable area.
+#[deriving(Encodable)]
+pub enum PagePx {}
+
+// In summary, the hierarchy of pixel units and the factors to convert from one to the next:
+//
+// DevicePixel
+// / hidpi_ratio => ScreenPx
+// / desktop_zoom => ViewportPx
+// / pinch_zoom => PagePx
+
+// An Au is an "App Unit" and represents 1/60th of a CSS pixel. It was
+// originally proposed in 2002 as a standard unit of measure in Gecko.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=177805 for more info.
+//
+// FIXME: Implement Au using Length and ScaleFactor instead of a custom type.
+#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Zero)]
+pub struct Au(pub i32);
+
+impl Default for Au {
+ #[inline]
+ fn default() -> Au {
+ Au(0)
+ }
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for Au {
+ fn encode(&self, e: &mut S) -> Result<(), E> {
+ e.emit_f64(to_frac_px(*self))
+ }
+}
+
+impl fmt::Show for Au {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}px", to_frac_px(*self))
+ }}
+
+impl Add<Au,Au> for Au {
+ #[inline]
+ fn add(&self, other: &Au) -> Au {
+ let Au(s) = *self;
+ let Au(o) = *other;
+ Au(s + o)
+ }
+}
+
+impl Sub<Au,Au> for Au {
+ #[inline]
+ fn sub(&self, other: &Au) -> Au {
+ let Au(s) = *self;
+ let Au(o) = *other;
+ Au(s - o)
+ }
+
+}
+
+impl Mul<Au,Au> for Au {
+ #[inline]
+ fn mul(&self, other: &Au) -> Au {
+ let Au(s) = *self;
+ let Au(o) = *other;
+ Au(s * o)
+ }
+}
+
+impl Div<Au,Au> for Au {
+ #[inline]
+ fn div(&self, other: &Au) -> Au {
+ let Au(s) = *self;
+ let Au(o) = *other;
+ Au(s / o)
+ }
+}
+
+impl Rem<Au,Au> for Au {
+ #[inline]
+ fn rem(&self, other: &Au) -> Au {
+ let Au(s) = *self;
+ let Au(o) = *other;
+ Au(s % o)
+ }
+}
+
+impl Neg<Au> for Au {
+ #[inline]
+ fn neg(&self) -> Au {
+ let Au(s) = *self;
+ Au(-s)
+ }
+}
+
+impl One for Au {
+ #[inline]
+ fn one() -> Au { Au(1) }
+}
+
+impl Num for Au {}
+
+#[inline]
+pub fn min(x: Au, y: Au) -> Au { if x < y { x } else { y } }
+#[inline]
+pub fn max(x: Au, y: Au) -> Au { if x > y { x } else { y } }
+
+impl NumCast for Au {
+ #[inline]
+ fn from<T:ToPrimitive>(n: T) -> Option<Au> {
+ Some(Au(n.to_i32().unwrap()))
+ }
+}
+
+impl ToPrimitive for Au {
+ #[inline]
+ fn to_i64(&self) -> Option<i64> {
+ let Au(s) = *self;
+ Some(s as i64)
+ }
+
+ #[inline]
+ fn to_u64(&self) -> Option<u64> {
+ let Au(s) = *self;
+ Some(s as u64)
+ }
+
+ #[inline]
+ fn to_f32(&self) -> Option<f32> {
+ let Au(s) = *self;
+ s.to_f32()
+ }
+
+ #[inline]
+ fn to_f64(&self) -> Option<f64> {
+ let Au(s) = *self;
+ s.to_f64()
+ }
+}
+
+impl Au {
+ /// FIXME(pcwalton): Workaround for lack of cross crate inlining of newtype structs!
+ #[inline]
+ pub fn new(value: i32) -> Au {
+ Au(value)
+ }
+
+ #[inline]
+ pub fn scale_by(self, factor: f64) -> Au {
+ let Au(s) = self;
+ Au(((s as f64) * factor) as i32)
+ }
+
+ #[inline]
+ pub fn from_px(px: int) -> Au {
+ NumCast::from(px * 60).unwrap()
+ }
+
+ #[inline]
+ pub fn from_page_px(px: Length<PagePx, f32>) -> Au {
+ NumCast::from(px.get() * 60f32).unwrap()
+ }
+
+ #[inline]
+ pub fn to_nearest_px(&self) -> int {
+ let Au(s) = *self;
+ ((s as f64) / 60f64).round() as int
+ }
+
+ #[inline]
+ pub fn to_snapped(&self) -> Au {
+ let Au(s) = *self;
+ let res = s % 60i32;
+ return if res >= 30i32 { return Au(s - res + 60i32) }
+ else { return Au(s - res) };
+ }
+
+ #[inline]
+ pub fn from_frac32_px(px: f32) -> Au {
+ Au((px * 60f32) as i32)
+ }
+
+ #[inline]
+ pub fn from_pt(pt: f64) -> Au {
+ from_frac_px(pt_to_px(pt))
+ }
+
+ #[inline]
+ pub fn from_frac_px(px: f64) -> Au {
+ Au((px * 60f64) as i32)
+ }
+
+ #[inline]
+ pub fn min(x: Au, y: Au) -> Au {
+ let Au(xi) = x;
+ let Au(yi) = y;
+ if xi < yi { x } else { y }
+ }
+
+ #[inline]
+ pub fn max(x: Au, y: Au) -> Au {
+ let Au(xi) = x;
+ let Au(yi) = y;
+ if xi > yi { x } else { y }
+ }
+}
+
+// assumes 72 points per inch, and 96 px per inch
+pub fn pt_to_px(pt: f64) -> f64 {
+ pt / 72f64 * 96f64
+}
+
+// assumes 72 points per inch, and 96 px per inch
+pub fn px_to_pt(px: f64) -> f64 {
+ px / 96f64 * 72f64
+}
+
+pub fn from_frac_px(px: f64) -> Au {
+ Au((px * 60f64) as i32)
+}
+
+pub fn from_px(px: int) -> Au {
+ NumCast::from(px * 60).unwrap()
+}
+
+pub fn to_px(au: Au) -> int {
+ let Au(a) = au;
+ (a / 60) as int
+}
+
+pub fn to_frac_px(au: Au) -> f64 {
+ let Au(a) = au;
+ (a as f64) / 60f64
+}
+
+// assumes 72 points per inch, and 96 px per inch
+pub fn from_pt(pt: f64) -> Au {
+ from_px((pt / 72f64 * 96f64) as int)
+}
+
+// assumes 72 points per inch, and 96 px per inch
+pub fn to_pt(au: Au) -> f64 {
+ let Au(a) = au;
+ (a as f64) / 60f64 * 72f64 / 96f64
+}
+
+/// Returns true if the rect contains the given point. Points on the top or left sides of the rect
+/// are considered inside the rectangle, while points on the right or bottom sides of the rect are
+/// not considered inside the rectangle.
+pub fn rect_contains_point<T:PartialOrd + Add<T,T>>(rect: Rect<T>, point: Point2D<T>) -> bool {
+ point.x >= rect.origin.x && point.x < rect.origin.x + rect.size.width &&
+ point.y >= rect.origin.y && point.y < rect.origin.y + rect.size.height
+}
+
+/// A helper function to convert a rect of `f32` pixels to a rect of app units.
+pub fn f32_rect_to_au_rect(rect: Rect<f32>) -> Rect<Au> {
+ Rect(Point2D(Au::from_frac32_px(rect.origin.x), Au::from_frac32_px(rect.origin.y)),
+ Size2D(Au::from_frac32_px(rect.size.width), Au::from_frac32_px(rect.size.height)))
+}
+
diff --git a/components/util/lib.rs b/components/util/lib.rs
new file mode 100644
index 00000000000..d05efb735b6
--- /dev/null
+++ b/components/util/lib.rs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![feature(macro_rules,unsafe_destructor)]
+
+#![feature(phase)]
+#[phase(plugin, link)]
+extern crate log;
+
+extern crate debug;
+extern crate alloc;
+extern crate azure;
+extern crate collections;
+extern crate geom;
+extern crate getopts;
+extern crate layers;
+extern crate libc;
+extern crate native;
+extern crate rand;
+extern crate rustrt;
+extern crate serialize;
+extern crate sync;
+#[cfg(target_os="macos")]
+extern crate task_info;
+extern crate std_time = "time";
+extern crate string_cache;
+
+pub mod atom;
+pub mod cache;
+pub mod debug_utils;
+pub mod geometry;
+pub mod logical_geometry;
+pub mod memory;
+pub mod namespace;
+pub mod opts;
+pub mod range;
+pub mod smallvec;
+pub mod sort;
+pub mod str;
+pub mod task;
+pub mod time;
+pub mod vec;
+pub mod workqueue;
diff --git a/components/util/logical_geometry.rs b/components/util/logical_geometry.rs
new file mode 100644
index 00000000000..a16dd6a5c8d
--- /dev/null
+++ b/components/util/logical_geometry.rs
@@ -0,0 +1,1023 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Geometry in flow-relative space.
+
+use geom::{Size2D, Point2D, SideOffsets2D, Rect};
+use std::cmp::{min, max};
+use std::fmt::{Show, Formatter, FormatError};
+use std::num::Zero;
+
+bitflags!(
+ #[deriving(Encodable)]
+ flags WritingMode: u8 {
+ static FlagRTL = 1 << 0,
+ static FlagVertical = 1 << 1,
+ static FlagVerticalLR = 1 << 2,
+ static FlagSidewaysLeft = 1 << 3
+ }
+)
+
+impl WritingMode {
+ #[inline]
+ pub fn is_vertical(&self) -> bool {
+ self.intersects(FlagVertical)
+ }
+
+ /// Asuming .is_vertical(), does the block direction go left to right?
+ #[inline]
+ pub fn is_vertical_lr(&self) -> bool {
+ self.intersects(FlagVerticalLR)
+ }
+
+ /// Asuming .is_vertical(), does the inline direction go top to bottom?
+ #[inline]
+ pub fn is_inline_tb(&self) -> bool {
+ !(self.intersects(FlagSidewaysLeft) ^ self.intersects(FlagRTL))
+ }
+
+ #[inline]
+ pub fn is_bidi_ltr(&self) -> bool {
+ !self.intersects(FlagRTL)
+ }
+
+ #[inline]
+ pub fn is_sideways_left(&self) -> bool {
+ self.intersects(FlagSidewaysLeft)
+ }
+}
+
+impl Show for WritingMode {
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ if self.is_vertical() {
+ try!(write!(formatter, "V"));
+ if self.is_vertical_lr() {
+ try!(write!(formatter, " LR"));
+ } else {
+ try!(write!(formatter, " RL"));
+ }
+ if self.intersects(FlagSidewaysLeft) {
+ try!(write!(formatter, " SidewaysL"));
+ }
+ } else {
+ try!(write!(formatter, "H"));
+ }
+ if self.is_bidi_ltr() {
+ write!(formatter, " LTR")
+ } else {
+ write!(formatter, " RTL")
+ }
+ }
+}
+
+
+/// Wherever logical geometry is used, the writing mode is known based on context:
+/// every method takes a `mode` parameter.
+/// However, this context is easy to get wrong.
+/// In debug builds only, logical geometry objects store their writing mode
+/// (in addition to taking it as a parameter to methods) and check it.
+/// In non-debug builds, make this storage zero-size and the checks no-ops.
+#[cfg(ndebug)]
+#[deriving(Encodable, PartialEq, Eq, Clone)]
+struct DebugWritingMode;
+
+#[cfg(not(ndebug))]
+#[deriving(Encodable, PartialEq, Eq, Clone)]
+struct DebugWritingMode {
+ mode: WritingMode
+}
+
+#[cfg(ndebug)]
+impl DebugWritingMode {
+ #[inline]
+ fn check(&self, _other: WritingMode) {}
+
+ #[inline]
+ fn check_debug(&self, _other: DebugWritingMode) {}
+
+ #[inline]
+ fn new(_mode: WritingMode) -> DebugWritingMode {
+ DebugWritingMode
+ }
+}
+
+#[cfg(not(ndebug))]
+impl DebugWritingMode {
+ #[inline]
+ fn check(&self, other: WritingMode) {
+ assert!(self.mode == other)
+ }
+
+ #[inline]
+ fn check_debug(&self, other: DebugWritingMode) {
+ assert!(self.mode == other.mode)
+ }
+
+ #[inline]
+ fn new(mode: WritingMode) -> DebugWritingMode {
+ DebugWritingMode { mode: mode }
+ }
+}
+
+impl Show for DebugWritingMode {
+ #[cfg(ndebug)]
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ write!(formatter, "?")
+ }
+
+ #[cfg(not(ndebug))]
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ self.mode.fmt(formatter)
+ }
+}
+
+
+/// A 2D size in flow-relative dimensions
+#[deriving(Encodable, PartialEq, Eq, Clone)]
+pub struct LogicalSize<T> {
+ pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure
+ pub block: T, // block-size, a.k.a. logical height, a.k.a. extent
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Show> Show for LogicalSize<T> {
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ write!(formatter, "LogicalSize[{}, {}, {}]",
+ self.debug_writing_mode, self.inline, self.block)
+ }
+}
+
+// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
+impl<T: Zero> LogicalSize<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalSize<T> {
+ LogicalSize {
+ inline: Zero::zero(),
+ block: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn is_zero(&self) -> bool {
+ self.inline.is_zero() && self.block.is_zero()
+ }
+}
+
+impl<T: Copy> LogicalSize<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> {
+ LogicalSize {
+ inline: inline,
+ block: block,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> {
+ if mode.is_vertical() {
+ LogicalSize::new(mode, size.height, size.width)
+ } else {
+ LogicalSize::new(mode, size.width, size.height)
+ }
+ }
+
+ #[inline]
+ pub fn width(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.block
+ } else {
+ self.inline
+ }
+ }
+
+ #[inline]
+ pub fn set_width(&mut self, mode: WritingMode, width: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.block = width
+ } else {
+ self.inline = width
+ }
+ }
+
+ #[inline]
+ pub fn height(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.inline
+ } else {
+ self.block
+ }
+ }
+
+ #[inline]
+ pub fn set_height(&mut self, mode: WritingMode, height: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.inline = height
+ } else {
+ self.block = height
+ }
+ }
+
+ #[inline]
+ pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ Size2D { width: self.block, height: self.inline }
+ } else {
+ Size2D { width: self.inline, height: self.block }
+ }
+ }
+
+ #[inline]
+ pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> {
+ if mode_from == mode_to {
+ self.debug_writing_mode.check(mode_from);
+ *self
+ } else {
+ LogicalSize::from_physical(mode_to, self.to_physical(mode_from))
+ }
+ }
+}
+
+impl<T: Add<T, T>> Add<LogicalSize<T>, LogicalSize<T>> for LogicalSize<T> {
+ #[inline]
+ fn add(&self, other: &LogicalSize<T>) -> LogicalSize<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalSize {
+ debug_writing_mode: self.debug_writing_mode,
+ inline: self.inline + other.inline,
+ block: self.block + other.block,
+ }
+ }
+}
+
+impl<T: Sub<T, T>> Sub<LogicalSize<T>, LogicalSize<T>> for LogicalSize<T> {
+ #[inline]
+ fn sub(&self, other: &LogicalSize<T>) -> LogicalSize<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalSize {
+ debug_writing_mode: self.debug_writing_mode,
+ inline: self.inline - other.inline,
+ block: self.block - other.block,
+ }
+ }
+}
+
+
+/// A 2D point in flow-relative dimensions
+#[deriving(PartialEq, Encodable, Eq, Clone)]
+pub struct LogicalPoint<T> {
+ pub i: T, /// inline-axis coordinate
+ pub b: T, /// block-axis coordinate
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Show> Show for LogicalPoint<T> {
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ write!(formatter, "LogicalPoint[{}, {}, {}]",
+ self.debug_writing_mode, self.i, self.b)
+ }
+}
+
+// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
+impl<T: Zero> LogicalPoint<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalPoint<T> {
+ LogicalPoint {
+ i: Zero::zero(),
+ b: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn is_zero(&self) -> bool {
+ self.i.is_zero() && self.b.is_zero()
+ }
+}
+
+impl<T: Copy> LogicalPoint<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> {
+ LogicalPoint {
+ i: i,
+ b: b,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy + Sub<T, T>> LogicalPoint<T> {
+ #[inline]
+ pub fn from_physical(mode: WritingMode, point: Point2D<T>, container_size: Size2D<T>)
+ -> LogicalPoint<T> {
+ if mode.is_vertical() {
+ LogicalPoint {
+ i: if mode.is_inline_tb() { point.y } else { container_size.height - point.y },
+ b: if mode.is_vertical_lr() { point.x } else { container_size.width - point.x },
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ } else {
+ LogicalPoint {
+ i: if mode.is_bidi_ltr() { point.x } else { container_size.width - point.x },
+ b: point.y,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+ }
+
+ #[inline]
+ pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() { self.b } else { container_size.width - self.b }
+ } else {
+ if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i }
+ }
+ }
+
+ #[inline]
+ pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.b = if mode.is_vertical_lr() { x } else { container_size.width - x }
+ } else {
+ self.i = if mode.is_bidi_ltr() { x } else { container_size.width - x }
+ }
+ }
+
+ #[inline]
+ pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_inline_tb() { self.i } else { container_size.height - self.i }
+ } else {
+ self.b
+ }
+ }
+
+ #[inline]
+ pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.i = if mode.is_inline_tb() { y } else { container_size.height - y }
+ } else {
+ self.b = y
+ }
+ }
+
+ #[inline]
+ pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ Point2D {
+ x: if mode.is_vertical_lr() { self.b } else { container_size.width - self.b },
+ y: if mode.is_inline_tb() { self.i } else { container_size.height - self.i }
+ }
+ } else {
+ Point2D {
+ x: if mode.is_bidi_ltr() { self.i } else { container_size.width - self.i },
+ y: self.b
+ }
+ }
+ }
+
+ #[inline]
+ pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D<T>)
+ -> LogicalPoint<T> {
+ if mode_from == mode_to {
+ self.debug_writing_mode.check(mode_from);
+ *self
+ } else {
+ LogicalPoint::from_physical(
+ mode_to, self.to_physical(mode_from, container_size), container_size)
+ }
+ }
+}
+
+impl<T: Add<T,T>> LogicalPoint<T> {
+ /// This doesn’t really makes sense,
+ /// but happens when dealing with mutliple origins.
+ #[inline]
+ pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalPoint {
+ debug_writing_mode: self.debug_writing_mode,
+ i: self.i + other.i,
+ b: self.b + other.b,
+ }
+ }
+}
+
+impl<T: Add<T,T>> Add<LogicalSize<T>, LogicalPoint<T>> for LogicalPoint<T> {
+ #[inline]
+ fn add(&self, other: &LogicalSize<T>) -> LogicalPoint<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalPoint {
+ debug_writing_mode: self.debug_writing_mode,
+ i: self.i + other.inline,
+ b: self.b + other.block,
+ }
+ }
+}
+
+impl<T: Sub<T,T>> Sub<LogicalSize<T>, LogicalPoint<T>> for LogicalPoint<T> {
+ #[inline]
+ fn sub(&self, other: &LogicalSize<T>) -> LogicalPoint<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalPoint {
+ debug_writing_mode: self.debug_writing_mode,
+ i: self.i - other.inline,
+ b: self.b - other.block,
+ }
+ }
+}
+
+
+/// A "margin" in flow-relative dimensions
+/// Represents the four sides of the margins, borders, or padding of a CSS box,
+/// or a combination of those.
+/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle.
+#[deriving(Encodable, PartialEq, Eq, Clone)]
+pub struct LogicalMargin<T> {
+ pub block_start: T,
+ pub inline_end: T,
+ pub block_end: T,
+ pub inline_start: T,
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Show> Show for LogicalMargin<T> {
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ write!(formatter,
+ "LogicalMargin[{}, block_start: {}, inline_end: {}, \
+ block_end: {}, inline_start: {}]",
+ self.debug_writing_mode, self.block_start,
+ self.inline_end, self.block_end, self.inline_start)
+ }
+}
+
+impl<T: Zero> LogicalMargin<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
+ LogicalMargin {
+ block_start: Zero::zero(),
+ inline_end: Zero::zero(),
+ block_end: Zero::zero(),
+ inline_start: Zero::zero(),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn is_zero(&self) -> bool {
+ self.block_start.is_zero() &&
+ self.inline_end.is_zero() &&
+ self.block_end.is_zero() &&
+ self.inline_start.is_zero()
+ }
+}
+
+impl<T: Copy> LogicalMargin<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, block_start: T, inline_end: T, block_end: T, inline_start: T)
+ -> LogicalMargin<T> {
+ LogicalMargin {
+ block_start: block_start,
+ inline_end: inline_end,
+ block_end: block_end,
+ inline_start: inline_start,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
+ LogicalMargin::new(mode, value, value, value, value)
+ }
+
+ #[inline]
+ pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
+ let block_start;
+ let inline_end;
+ let block_end;
+ let inline_start;
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() {
+ block_start = offsets.left;
+ block_end = offsets.right;
+ } else {
+ block_start = offsets.right;
+ block_end = offsets.left;
+ }
+ if mode.is_inline_tb() {
+ inline_start = offsets.top;
+ inline_end = offsets.bottom;
+ } else {
+ inline_start = offsets.bottom;
+ inline_end = offsets.top;
+ }
+ } else {
+ block_start = offsets.top;
+ block_end = offsets.bottom;
+ if mode.is_bidi_ltr() {
+ inline_start = offsets.left;
+ inline_end = offsets.right;
+ } else {
+ inline_start = offsets.right;
+ inline_end = offsets.left;
+ }
+ }
+ LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start)
+ }
+
+ #[inline]
+ pub fn top(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_inline_tb() { self.inline_start } else { self.inline_end }
+ } else {
+ self.block_start
+ }
+ }
+
+ #[inline]
+ pub fn set_top(&mut self, mode: WritingMode, top: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_inline_tb() { self.inline_start = top } else { self.inline_end = top }
+ } else {
+ self.block_start = top
+ }
+ }
+
+ #[inline]
+ pub fn right(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() { self.block_end } else { self.block_start }
+ } else {
+ if mode.is_bidi_ltr() { self.inline_end } else { self.inline_start }
+ }
+ }
+
+ #[inline]
+ pub fn set_right(&mut self, mode: WritingMode, right: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() { self.block_end = right } else { self.block_start = right }
+ } else {
+ if mode.is_bidi_ltr() { self.inline_end = right } else { self.inline_start = right }
+ }
+ }
+
+ #[inline]
+ pub fn bottom(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_inline_tb() { self.inline_end } else { self.inline_start }
+ } else {
+ self.block_end
+ }
+ }
+
+ #[inline]
+ pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_inline_tb() { self.inline_end = bottom } else { self.inline_start = bottom }
+ } else {
+ self.block_end = bottom
+ }
+ }
+
+ #[inline]
+ pub fn left(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() { self.block_start } else { self.block_end }
+ } else {
+ if mode.is_bidi_ltr() { self.inline_start } else { self.inline_end }
+ }
+ }
+
+ #[inline]
+ pub fn set_left(&mut self, mode: WritingMode, left: T) {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() { self.block_start = left } else { self.block_end = left }
+ } else {
+ if mode.is_bidi_ltr() { self.inline_start = left } else { self.inline_end = left }
+ }
+ }
+
+ #[inline]
+ pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
+ self.debug_writing_mode.check(mode);
+ let top;
+ let right;
+ let bottom;
+ let left;
+ if mode.is_vertical() {
+ if mode.is_vertical_lr() {
+ left = self.block_start;
+ right = self.block_end;
+ } else {
+ right = self.block_start;
+ left = self.block_end;
+ }
+ if mode.is_inline_tb() {
+ top = self.inline_start;
+ bottom = self.inline_end;
+ } else {
+ bottom = self.inline_start;
+ top = self.inline_end;
+ }
+ } else {
+ top = self.block_start;
+ bottom = self.block_end;
+ if mode.is_bidi_ltr() {
+ left = self.inline_start;
+ right = self.inline_end;
+ } else {
+ right = self.inline_start;
+ left = self.inline_end;
+ }
+ }
+ SideOffsets2D::new(top, right, bottom, left)
+ }
+
+ #[inline]
+ pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
+ if mode_from == mode_to {
+ self.debug_writing_mode.check(mode_from);
+ *self
+ } else {
+ LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
+ }
+ }
+}
+
+impl<T: Add<T, T>> LogicalMargin<T> {
+ #[inline]
+ pub fn inline_start_end(&self) -> T {
+ self.inline_start + self.inline_end
+ }
+
+ #[inline]
+ pub fn block_start_end(&self) -> T {
+ self.block_start + self.block_end
+ }
+
+ #[inline]
+ pub fn top_bottom(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.inline_start_end()
+ } else {
+ self.block_start_end()
+ }
+ }
+
+ #[inline]
+ pub fn left_right(&self, mode: WritingMode) -> T {
+ self.debug_writing_mode.check(mode);
+ if mode.is_vertical() {
+ self.block_start_end()
+ } else {
+ self.inline_start_end()
+ }
+ }
+}
+
+impl<T: Add<T, T>> Add<LogicalMargin<T>, LogicalMargin<T>> for LogicalMargin<T> {
+ #[inline]
+ fn add(&self, other: &LogicalMargin<T>) -> LogicalMargin<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalMargin {
+ debug_writing_mode: self.debug_writing_mode,
+ block_start: self.block_start + other.block_start,
+ inline_end: self.inline_end + other.inline_end,
+ block_end: self.block_end + other.block_end,
+ inline_start: self.inline_start + other.inline_start,
+ }
+ }
+}
+
+impl<T: Sub<T, T>> Sub<LogicalMargin<T>, LogicalMargin<T>> for LogicalMargin<T> {
+ #[inline]
+ fn sub(&self, other: &LogicalMargin<T>) -> LogicalMargin<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalMargin {
+ debug_writing_mode: self.debug_writing_mode,
+ block_start: self.block_start - other.block_start,
+ inline_end: self.inline_end - other.inline_end,
+ block_end: self.block_end - other.block_end,
+ inline_start: self.inline_start - other.inline_start,
+ }
+ }
+}
+
+
+/// A rectangle in flow-relative dimensions
+#[deriving(Encodable, PartialEq, Eq, Clone)]
+pub struct LogicalRect<T> {
+ pub start: LogicalPoint<T>,
+ pub size: LogicalSize<T>,
+ debug_writing_mode: DebugWritingMode,
+}
+
+impl<T: Show> Show for LogicalRect<T> {
+ fn fmt(&self, formatter: &mut Formatter) -> Result<(), FormatError> {
+ write!(formatter,
+ "LogicalRect[{}, inline_start: {}, block_start: {}, \
+ inline: {}, block: {}]",
+ self.debug_writing_mode, self.start.i, self.start.b,
+ self.size.inline, self.size.block)
+ }
+}
+
+impl<T: Zero> LogicalRect<T> {
+ #[inline]
+ pub fn zero(mode: WritingMode) -> LogicalRect<T> {
+ LogicalRect {
+ start: LogicalPoint::zero(mode),
+ size: LogicalSize::zero(mode),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn is_zero(&self) -> bool {
+ self.start.is_zero() && self.size.is_zero()
+ }
+}
+
+impl<T: Copy> LogicalRect<T> {
+ #[inline]
+ pub fn new(mode: WritingMode, inline_start: T, block_start: T, inline: T, block: T)
+ -> LogicalRect<T> {
+ LogicalRect {
+ start: LogicalPoint::new(mode, inline_start, block_start),
+ size: LogicalSize::new(mode, inline, block),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn from_point_size(mode: WritingMode, start: LogicalPoint<T>, size: LogicalSize<T>)
+ -> LogicalRect<T> {
+ start.debug_writing_mode.check(mode);
+ size.debug_writing_mode.check(mode);
+ LogicalRect {
+ start: start,
+ size: size,
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+}
+
+impl<T: Copy + Add<T, T> + Sub<T, T>> LogicalRect<T> {
+ #[inline]
+ pub fn from_physical(mode: WritingMode, rect: Rect<T>, container_size: Size2D<T>)
+ -> LogicalRect<T> {
+ let inline_start;
+ let block_start;
+ let inline;
+ let block;
+ if mode.is_vertical() {
+ inline = rect.size.height;
+ block = rect.size.width;
+ if mode.is_vertical_lr() {
+ block_start = rect.origin.x;
+ } else {
+ block_start = container_size.width - (rect.origin.x + rect.size.width);
+ }
+ if mode.is_inline_tb() {
+ inline_start = rect.origin.y;
+ } else {
+ inline_start = container_size.height - (rect.origin.y + rect.size.height);
+ }
+ } else {
+ inline = rect.size.width;
+ block = rect.size.height;
+ block_start = rect.origin.y;
+ if mode.is_bidi_ltr() {
+ inline_start = rect.origin.x;
+ } else {
+ inline_start = container_size.width - (rect.origin.x + rect.size.width);
+ }
+ }
+ LogicalRect {
+ start: LogicalPoint::new(mode, inline_start, block_start),
+ size: LogicalSize::new(mode, inline, block),
+ debug_writing_mode: DebugWritingMode::new(mode),
+ }
+ }
+
+ #[inline]
+ pub fn inline_end(&self) -> T {
+ self.start.i + self.size.inline
+ }
+
+ #[inline]
+ pub fn block_end(&self) -> T {
+ self.start.b + self.size.block
+ }
+
+ #[inline]
+ pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> {
+ self.debug_writing_mode.check(mode);
+ let x;
+ let y;
+ let width;
+ let height;
+ if mode.is_vertical() {
+ width = self.size.block;
+ height = self.size.inline;
+ if mode.is_vertical_lr() {
+ x = self.start.b;
+ } else {
+ x = container_size.width - self.block_end();
+ }
+ if mode.is_inline_tb() {
+ y = self.start.i;
+ } else {
+ y = container_size.height - self.inline_end();
+ }
+ } else {
+ width = self.size.inline;
+ height = self.size.block;
+ y = self.start.b;
+ if mode.is_bidi_ltr() {
+ x = self.start.i;
+ } else {
+ x = container_size.width - self.inline_end();
+ }
+ }
+ Rect {
+ origin: Point2D { x: x, y: y },
+ size: Size2D { width: width, height: height },
+ }
+ }
+
+ #[inline]
+ pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode, container_size: Size2D<T>)
+ -> LogicalRect<T> {
+ if mode_from == mode_to {
+ self.debug_writing_mode.check(mode_from);
+ *self
+ } else {
+ LogicalRect::from_physical(
+ mode_to, self.to_physical(mode_from, container_size), container_size)
+ }
+ }
+
+ pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
+ LogicalRect {
+ start: self.start + LogicalSize {
+ inline: offset.i,
+ block: offset.b,
+ debug_writing_mode: offset.debug_writing_mode,
+ },
+ size: self.size,
+ debug_writing_mode: self.debug_writing_mode,
+ }
+ }
+}
+
+impl<T: Copy + Ord + Add<T, T> + Sub<T, T>> LogicalRect<T> {
+ #[inline]
+ pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+
+ let inline_start = min(self.start.i, other.start.i);
+ let block_start = min(self.start.b, other.start.b);
+ LogicalRect {
+ start: LogicalPoint {
+ i: inline_start,
+ b: block_start,
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ size: LogicalSize {
+ inline: max(self.inline_end(), other.inline_end()) - inline_start,
+ block: max(self.block_end(), other.block_end()) - block_start,
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ debug_writing_mode: self.debug_writing_mode,
+ }
+ }
+}
+
+impl<T: Add<T, T> + Sub<T, T>> Add<LogicalMargin<T>, LogicalRect<T>> for LogicalRect<T> {
+ #[inline]
+ fn add(&self, other: &LogicalMargin<T>) -> LogicalRect<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalRect {
+ start: LogicalPoint {
+ // Growing a rectangle on the start side means pushing its
+ // start point on the negative direction.
+ i: self.start.i - other.inline_start,
+ b: self.start.b - other.block_start,
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ size: LogicalSize {
+ inline: self.size.inline + other.inline_start_end(),
+ block: self.size.block + other.block_start_end(),
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ debug_writing_mode: self.debug_writing_mode,
+ }
+ }
+}
+
+
+impl<T: Add<T, T> + Sub<T, T>> Sub<LogicalMargin<T>, LogicalRect<T>> for LogicalRect<T> {
+ #[inline]
+ fn sub(&self, other: &LogicalMargin<T>) -> LogicalRect<T> {
+ self.debug_writing_mode.check_debug(other.debug_writing_mode);
+ LogicalRect {
+ start: LogicalPoint {
+ // Shrinking a rectangle on the start side means pushing its
+ // start point on the positive direction.
+ i: self.start.i + other.inline_start,
+ b: self.start.b + other.block_start,
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ size: LogicalSize {
+ inline: self.size.inline - other.inline_start_end(),
+ block: self.size.block - other.block_start_end(),
+ debug_writing_mode: self.debug_writing_mode,
+ },
+ debug_writing_mode: self.debug_writing_mode,
+ }
+ }
+}
+
+#[cfg(test)]
+fn modes() -> [WritingMode, ..10] {
+ [
+ WritingMode::empty(),
+ FlagVertical,
+ FlagVertical | FlagVerticalLR,
+ FlagVertical | FlagVerticalLR | FlagSidewaysLeft,
+ FlagVertical | FlagSidewaysLeft,
+ FlagRTL,
+ FlagVertical | FlagRTL,
+ FlagVertical | FlagVerticalLR | FlagRTL,
+ FlagVertical | FlagVerticalLR | FlagSidewaysLeft | FlagRTL,
+ FlagVertical | FlagSidewaysLeft | FlagRTL,
+ ]
+}
+
+#[test]
+fn test_size_round_trip() {
+ let physical = Size2D(1u32, 2u32);
+ for &mode in modes().iter() {
+ let logical = LogicalSize::from_physical(mode, physical);
+ assert!(logical.to_physical(mode) == physical);
+ assert!(logical.width(mode) == 1);
+ assert!(logical.height(mode) == 2);
+ }
+}
+
+#[test]
+fn test_point_round_trip() {
+ let physical = Point2D(1u32, 2u32);
+ let container = Size2D(100, 200);
+ for &mode in modes().iter() {
+ let logical = LogicalPoint::from_physical(mode, physical, container);
+ assert!(logical.to_physical(mode, container) == physical);
+ assert!(logical.x(mode, container) == 1);
+ assert!(logical.y(mode, container) == 2);
+ }
+}
+
+#[test]
+fn test_margin_round_trip() {
+ let physical = SideOffsets2D::new(1u32, 2u32, 3u32, 4u32);
+ for &mode in modes().iter() {
+ let logical = LogicalMargin::from_physical(mode, physical);
+ assert!(logical.to_physical(mode) == physical);
+ assert!(logical.top(mode) == 1);
+ assert!(logical.right(mode) == 2);
+ assert!(logical.bottom(mode) == 3);
+ assert!(logical.left(mode) == 4);
+ }
+}
+
+#[test]
+fn test_rect_round_trip() {
+ let physical = Rect(Point2D(1u32, 2u32), Size2D(3u32, 4u32));
+ let container = Size2D(100, 200);
+ for &mode in modes().iter() {
+ let logical = LogicalRect::from_physical(mode, physical, container);
+ assert!(logical.to_physical(mode, container) == physical);
+ }
+}
diff --git a/components/util/memory.rs b/components/util/memory.rs
new file mode 100644
index 00000000000..25aa13c8316
--- /dev/null
+++ b/components/util/memory.rs
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Memory profiling functions.
+
+use libc::{c_char,c_int,c_void,size_t};
+use std::io::timer::sleep;
+#[cfg(target_os="linux")]
+use std::io::File;
+use std::mem::size_of;
+#[cfg(target_os="linux")]
+use std::os::page_size;
+use std::ptr::mut_null;
+use task::spawn_named;
+#[cfg(target_os="macos")]
+use task_info::task_basic_info::{virtual_size,resident_size};
+
+pub struct MemoryProfilerChan(pub Sender<MemoryProfilerMsg>);
+
+impl MemoryProfilerChan {
+ pub fn send(&self, msg: MemoryProfilerMsg) {
+ let MemoryProfilerChan(ref c) = *self;
+ c.send(msg);
+ }
+}
+
+pub enum MemoryProfilerMsg {
+ /// Message used to force print the memory profiling metrics.
+ PrintMsg,
+ /// Tells the memory profiler to shut down.
+ ExitMsg,
+}
+
+pub struct MemoryProfiler {
+ pub port: Receiver<MemoryProfilerMsg>,
+}
+
+impl MemoryProfiler {
+ pub fn create(period: Option<f64>) -> MemoryProfilerChan {
+ let (chan, port) = channel();
+ match period {
+ Some(period) => {
+ let period = (period * 1000f64) as u64;
+ let chan = chan.clone();
+ spawn_named("Memory profiler timer", proc() {
+ loop {
+ sleep(period);
+ if chan.send_opt(PrintMsg).is_err() {
+ break;
+ }
+ }
+ });
+ // Spawn the memory profiler.
+ spawn_named("Memory profiler", proc() {
+ let memory_profiler = MemoryProfiler::new(port);
+ memory_profiler.start();
+ });
+ }
+ None => {
+ // No-op to handle messages when the memory profiler is
+ // inactive.
+ spawn_named("Memory profiler", proc() {
+ loop {
+ match port.recv_opt() {
+ Err(_) | Ok(ExitMsg) => break,
+ _ => {}
+ }
+ }
+ });
+ }
+ }
+
+ MemoryProfilerChan(chan)
+ }
+
+ pub fn new(port: Receiver<MemoryProfilerMsg>) -> MemoryProfiler {
+ MemoryProfiler {
+ port: port
+ }
+ }
+
+ pub fn start(&self) {
+ loop {
+ match self.port.recv_opt() {
+ Ok(msg) => {
+ if !self.handle_msg(msg) {
+ break
+ }
+ }
+ _ => break
+ }
+ }
+ }
+
+ fn handle_msg(&self, msg: MemoryProfilerMsg) -> bool {
+ match msg {
+ PrintMsg => {
+ self.handle_print_msg();
+ true
+ },
+ ExitMsg => false
+ }
+ }
+
+ fn print_measurement(path: &str, nbytes: Option<u64>) {
+ match nbytes {
+ Some(nbytes) => {
+ let mebi = 1024f64 * 1024f64;
+ println!("{:16s}: {:12.2f}", path, (nbytes as f64) / mebi);
+ }
+ None => {
+ println!("{:16s}: {:>12s}", path, "???");
+ }
+ }
+ }
+
+ fn handle_print_msg(&self) {
+ println!("{:16s}: {:12s}", "_category_", "_size (MiB)_");
+
+ // Virtual and physical memory usage, as reported by the OS.
+ MemoryProfiler::print_measurement("vsize", get_vsize());
+ MemoryProfiler::print_measurement("resident", get_resident());
+
+ // The descriptions of the jemalloc measurements are taken directly
+ // from the jemalloc documentation.
+
+ // Total number of bytes allocated by the application.
+ MemoryProfiler::print_measurement("heap-allocated", get_jemalloc_stat("stats.allocated"));
+
+ // Total number of bytes in active pages allocated by the application.
+ // This is a multiple of the page size, and greater than or equal to
+ // |stats.allocated|.
+ MemoryProfiler::print_measurement("heap-active", get_jemalloc_stat("stats.active"));
+
+ // Total number of bytes in chunks mapped on behalf of the application.
+ // This is a multiple of the chunk size, and is at least as large as
+ // |stats.active|. This does not include inactive chunks.
+ MemoryProfiler::print_measurement("heap-mapped", get_jemalloc_stat("stats.mapped"));
+
+ println!("");
+ }
+}
+
+extern {
+ fn je_mallctl(name: *const c_char, oldp: *mut c_void, oldlenp: *mut size_t,
+ newp: *mut c_void, newlen: size_t) -> c_int;
+}
+
+fn get_jemalloc_stat(name: &'static str) -> Option<u64> {
+ let mut old: size_t = 0;
+ let c_name = name.to_c_str();
+ let oldp = &mut old as *mut _ as *mut c_void;
+ let mut oldlen = size_of::<size_t>() as size_t;
+ let rv: c_int;
+ unsafe {
+ rv = je_mallctl(c_name.unwrap(), oldp, &mut oldlen, mut_null(), 0);
+ }
+ if rv == 0 { Some(old as u64) } else { None }
+}
+
+// Like std::macros::try!, but for Option<>.
+macro_rules! option_try(
+ ($e:expr) => (match $e { Some(e) => e, None => return None })
+)
+
+#[cfg(target_os="linux")]
+fn get_proc_self_statm_field(field: uint) -> Option<u64> {
+ let mut f = File::open(&Path::new("/proc/self/statm"));
+ match f.read_to_string() {
+ Ok(contents) => {
+ let s = option_try!(contents.as_slice().words().nth(field));
+ let npages: u64 = option_try!(from_str(s));
+ Some(npages * (page_size() as u64))
+ }
+ Err(_) => None
+ }
+}
+
+#[cfg(target_os="linux")]
+fn get_vsize() -> Option<u64> {
+ get_proc_self_statm_field(0)
+}
+
+#[cfg(target_os="linux")]
+fn get_resident() -> Option<u64> {
+ get_proc_self_statm_field(1)
+}
+
+#[cfg(target_os="macos")]
+fn get_vsize() -> Option<u64> {
+ virtual_size()
+}
+
+#[cfg(target_os="macos")]
+fn get_resident() -> Option<u64> {
+ resident_size()
+}
+
+#[cfg(not(target_os="linux"), not(target_os = "macos"))]
+fn get_vsize() -> Option<u64> {
+ None
+}
+
+#[cfg(not(target_os="linux"), not(target_os = "macos"))]
+fn get_resident() -> Option<u64> {
+ None
+}
+
diff --git a/components/util/namespace.rs b/components/util/namespace.rs
new file mode 100644
index 00000000000..1f32ae83017
--- /dev/null
+++ b/components/util/namespace.rs
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[deriving(PartialEq, Clone, Encodable)]
+pub enum Namespace {
+ Null,
+ HTML,
+ XML,
+ XMLNS,
+ XLink,
+ SVG,
+ MathML,
+ Other(String)
+}
+
+impl Namespace {
+ /// Empty string for "no namespace"
+ pub fn from_str(url: &str) -> Namespace {
+ match url {
+ "http://www.w3.org/1999/xhtml" => HTML,
+ "http://www.w3.org/XML/1998/namespace" => XML,
+ "http://www.w3.org/2000/xmlns/" => XMLNS,
+ "http://www.w3.org/1999/xlink" => XLink,
+ "http://www.w3.org/2000/svg" => SVG,
+ "http://www.w3.org/1998/Math/MathML" => MathML,
+ "" => Null,
+ ns => Other(ns.to_string())
+ }
+ }
+ pub fn to_str<'a>(&'a self) -> &'a str {
+ match *self {
+ Null => "",
+ HTML => "http://www.w3.org/1999/xhtml",
+ XML => "http://www.w3.org/XML/1998/namespace",
+ XMLNS => "http://www.w3.org/2000/xmlns/",
+ XLink => "http://www.w3.org/1999/xlink",
+ SVG => "http://www.w3.org/2000/svg",
+ MathML => "http://www.w3.org/1998/Math/MathML",
+ Other(ref x) => x.as_slice()
+ }
+ }
+}
diff --git a/components/util/opts.rs b/components/util/opts.rs
new file mode 100644
index 00000000000..40da68632d8
--- /dev/null
+++ b/components/util/opts.rs
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Configuration options for a single run of the servo application. Created
+//! from command line arguments.
+
+use geometry::ScreenPx;
+
+use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend};
+use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend};
+use geom::scale_factor::ScaleFactor;
+use layers::geometry::DevicePixel;
+use getopts;
+use std::cmp;
+use std::io;
+use std::os;
+use std::rt;
+
+/// Global flags for Servo, currently set on the command line.
+#[deriving(Clone)]
+pub struct Opts {
+ /// The initial URLs to load.
+ pub urls: Vec<String>,
+
+ /// The rendering backend to use (`-r`).
+ pub render_backend: BackendType,
+
+ /// How many threads to use for CPU rendering (`-t`).
+ ///
+ /// FIXME(pcwalton): This is not currently used. All rendering is sequential.
+ pub n_render_threads: uint,
+
+ /// True to use CPU painting, false to use GPU painting via Skia-GL (`-c`). Note that
+ /// compositing is always done on the GPU.
+ pub cpu_painting: bool,
+
+ /// The maximum size of each tile in pixels (`-s`).
+ pub tile_size: uint,
+
+ /// The ratio of device pixels per px at the default scale. If unspecified, will use the
+ /// platform default setting.
+ pub device_pixels_per_px: Option<ScaleFactor<ScreenPx, DevicePixel, f32>>,
+
+ /// `None` to disable the time profiler or `Some` with an interval in seconds to enable it and
+ /// cause it to produce output on that interval (`-p`).
+ pub time_profiler_period: Option<f64>,
+
+ /// `None` to disable the memory profiler or `Some` with an interval in seconds to enable it
+ /// and cause it to produce output on that interval (`-m`).
+ pub memory_profiler_period: Option<f64>,
+
+ /// Enable experimental web features (`-e`).
+ pub enable_experimental: bool,
+
+ /// The number of threads to use for layout (`-y`). Defaults to 1, which results in a recursive
+ /// sequential algorithm.
+ pub layout_threads: uint,
+
+ /// True to exit after the page load (`-x`).
+ pub exit_after_load: bool,
+
+ pub output_file: Option<String>,
+ pub headless: bool,
+ pub hard_fail: bool,
+
+ /// True if we should bubble intrinsic widths sequentially (`-b`). If this is true, then
+ /// intrinsic widths are computed as a separate pass instead of during flow construction. You
+ /// may wish to turn this flag on in order to benchmark style recalculation against other
+ /// browser engines.
+ pub bubble_inline_sizes_separately: bool,
+
+ /// True if we should show borders on all layers and tiles for
+ /// debugging purposes (`--show-debug-borders`).
+ pub show_debug_borders: bool,
+
+ /// If set with --disable-text-aa, disable antialiasing on fonts. This is primarily useful for reftests
+ /// where pixel perfect results are required when using fonts such as the Ahem
+ /// font for layout tests.
+ pub enable_text_antialiasing: bool,
+
+ /// True if each step of layout is traced to an external JSON file
+ /// for debugging purposes. Settings this implies sequential layout
+ /// and render.
+ pub trace_layout: bool,
+}
+
+fn print_usage(app: &str, opts: &[getopts::OptGroup]) {
+ let message = format!("Usage: {} [ options ... ] [URL]\n\twhere options include", app);
+ println!("{}", getopts::usage(message.as_slice(), opts));
+}
+
+fn args_fail(msg: &str) {
+ io::stderr().write_line(msg).unwrap();
+ os::set_exit_status(1);
+}
+
+pub fn from_cmdline_args(args: &[String]) -> Option<Opts> {
+ let app_name = args[0].to_string();
+ let args = args.tail();
+
+ let opts = vec!(
+ getopts::optflag("c", "cpu", "CPU rendering"),
+ getopts::optopt("o", "output", "Output file", "output.png"),
+ getopts::optopt("r", "rendering", "Rendering backend", "direct2d|core-graphics|core-graphics-accelerated|cairo|skia."),
+ getopts::optopt("s", "size", "Size of tiles", "512"),
+ getopts::optopt("", "device-pixel-ratio", "Device pixels per px", ""),
+ getopts::optflag("e", "experimental", "Enable experimental web features"),
+ getopts::optopt("t", "threads", "Number of render threads", "1"),
+ getopts::optflagopt("p", "profile", "Profiler flag and output interval", "10"),
+ getopts::optflagopt("m", "memory-profile", "Memory profiler flag and output interval", "10"),
+ getopts::optflag("x", "exit", "Exit after load flag"),
+ getopts::optopt("y", "layout-threads", "Number of threads to use for layout", "1"),
+ getopts::optflag("z", "headless", "Headless mode"),
+ getopts::optflag("f", "hard-fail", "Exit on task failure instead of displaying about:failure"),
+ getopts::optflag("b", "bubble-widths", "Bubble intrinsic widths separately like other engines"),
+ getopts::optflag("", "show-debug-borders", "Show debugging borders on layers and tiles."),
+ getopts::optflag("", "disable-text-aa", "Disable antialiasing for text rendering."),
+ getopts::optflag("", "trace-layout", "Write layout trace to external file for debugging."),
+ getopts::optflag("h", "help", "Print this message")
+ );
+
+ let opt_match = match getopts::getopts(args, opts.as_slice()) {
+ Ok(m) => m,
+ Err(f) => {
+ args_fail(format!("{}", f).as_slice());
+ return None;
+ }
+ };
+
+ if opt_match.opt_present("h") || opt_match.opt_present("help") {
+ print_usage(app_name.as_slice(), opts.as_slice());
+ return None;
+ };
+
+ let urls = if opt_match.free.is_empty() {
+ print_usage(app_name.as_slice(), opts.as_slice());
+ args_fail("servo asks that you provide 1 or more URLs");
+ return None;
+ } else {
+ opt_match.free.clone()
+ };
+
+ let render_backend = match opt_match.opt_str("r") {
+ Some(backend_str) => {
+ if "direct2d" == backend_str.as_slice() {
+ Direct2DBackend
+ } else if "core-graphics" == backend_str.as_slice() {
+ CoreGraphicsBackend
+ } else if "core-graphics-accelerated" == backend_str.as_slice() {
+ CoreGraphicsAcceleratedBackend
+ } else if "cairo" == backend_str.as_slice() {
+ CairoBackend
+ } else if "skia" == backend_str.as_slice() {
+ SkiaBackend
+ } else {
+ fail!("unknown backend type")
+ }
+ }
+ None => SkiaBackend
+ };
+
+ let tile_size: uint = match opt_match.opt_str("s") {
+ Some(tile_size_str) => from_str(tile_size_str.as_slice()).unwrap(),
+ None => 512,
+ };
+
+ let device_pixels_per_px = opt_match.opt_str("device-pixel-ratio").map(|dppx_str|
+ ScaleFactor(from_str(dppx_str.as_slice()).unwrap())
+ );
+
+ let mut n_render_threads: uint = match opt_match.opt_str("t") {
+ Some(n_render_threads_str) => from_str(n_render_threads_str.as_slice()).unwrap(),
+ None => 1, // FIXME: Number of cores.
+ };
+
+ // If only the flag is present, default to a 5 second period for both profilers.
+ let time_profiler_period = opt_match.opt_default("p", "5").map(|period| {
+ from_str(period.as_slice()).unwrap()
+ });
+ let memory_profiler_period = opt_match.opt_default("m", "5").map(|period| {
+ from_str(period.as_slice()).unwrap()
+ });
+
+ let cpu_painting = opt_match.opt_present("c");
+
+ let mut layout_threads: uint = match opt_match.opt_str("y") {
+ Some(layout_threads_str) => from_str(layout_threads_str.as_slice()).unwrap(),
+ None => cmp::max(rt::default_sched_threads() * 3 / 4, 1),
+ };
+
+ let mut bubble_inline_sizes_separately = opt_match.opt_present("b");
+
+ let trace_layout = opt_match.opt_present("trace-layout");
+ if trace_layout {
+ n_render_threads = 1;
+ layout_threads = 1;
+ bubble_inline_sizes_separately = true;
+ }
+
+ Some(Opts {
+ urls: urls,
+ render_backend: render_backend,
+ n_render_threads: n_render_threads,
+ cpu_painting: cpu_painting,
+ tile_size: tile_size,
+ device_pixels_per_px: device_pixels_per_px,
+ time_profiler_period: time_profiler_period,
+ memory_profiler_period: memory_profiler_period,
+ enable_experimental: opt_match.opt_present("e"),
+ layout_threads: layout_threads,
+ exit_after_load: opt_match.opt_present("x"),
+ output_file: opt_match.opt_str("o"),
+ headless: opt_match.opt_present("z"),
+ hard_fail: opt_match.opt_present("f"),
+ bubble_inline_sizes_separately: bubble_inline_sizes_separately,
+ show_debug_borders: opt_match.opt_present("show-debug-borders"),
+ enable_text_antialiasing: !opt_match.opt_present("disable-text-aa"),
+ trace_layout: trace_layout,
+ })
+}
+
+static mut EXPERIMENTAL_ENABLED: bool = false;
+
+pub fn set_experimental_enabled(new_value: bool) {
+ unsafe {
+ EXPERIMENTAL_ENABLED = new_value;
+ }
+}
+
+pub fn experimental_enabled() -> bool {
+ unsafe {
+ EXPERIMENTAL_ENABLED
+ }
+}
diff --git a/components/util/range.rs b/components/util/range.rs
new file mode 100644
index 00000000000..ffea08e6264
--- /dev/null
+++ b/components/util/range.rs
@@ -0,0 +1,355 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::cmp::{max, min};
+use std::iter;
+use std::fmt;
+use std::num;
+use std::num::{Bounded, Zero};
+
+/// An index type to be used by a `Range`
+pub trait RangeIndex: Copy
+ + Clone
+ + fmt::Show
+ + PartialEq
+ + PartialOrd
+ + Eq
+ + Ord
+ + Add<Self, Self>
+ + Sub<Self, Self>
+ + Neg<Self>
+ + Zero {}
+
+pub trait IntRangeIndex<T>: RangeIndex + Copy {
+ fn new(x: T) -> Self;
+ fn get(self) -> T;
+}
+
+impl RangeIndex for int {}
+
+impl IntRangeIndex<int> for int {
+ #[inline]
+ fn new(x: int) -> int { x }
+
+ #[inline]
+ fn get(self) -> int { self }
+}
+
+/// Implements a range index type with operator overloads
+#[macro_export]
+macro_rules! int_range_index {
+ ($(#[$attr:meta])* struct $Self:ident($T:ty)) => (
+ #[deriving(Clone, PartialEq, PartialOrd, Eq, Ord, Show, Zero)]
+ $(#[$attr])*
+ pub struct $Self(pub $T);
+
+ impl $Self {
+ #[inline]
+ pub fn to_uint(self) -> uint {
+ self.get() as uint
+ }
+ }
+
+ impl RangeIndex for $Self {}
+
+ impl IntRangeIndex<$T> for $Self {
+ #[inline]
+ fn new(x: $T) -> $Self {
+ $Self(x)
+ }
+
+ #[inline]
+ fn get(self) -> $T {
+ match self { $Self(x) => x }
+ }
+ }
+
+ impl Add<$Self, $Self> for $Self {
+ #[inline]
+ fn add(&self, other: &$Self) -> $Self {
+ $Self(self.get() + other.get())
+ }
+ }
+
+ impl Sub<$Self, $Self> for $Self {
+ #[inline]
+ fn sub(&self, other: &$Self) -> $Self {
+ $Self(self.get() - other.get())
+ }
+ }
+
+ impl Neg<$Self> for $Self {
+ #[inline]
+ fn neg(&self) -> $Self {
+ $Self(-self.get())
+ }
+ }
+ )
+}
+
+#[deriving(Show)]
+pub enum RangeRelation<I> {
+ OverlapsBegin(/* overlap */ I),
+ OverlapsEnd(/* overlap */ I),
+ ContainedBy,
+ Contains,
+ Coincides,
+ EntirelyBefore,
+ EntirelyAfter
+}
+
+/// A range of indices
+#[deriving(Clone, Encodable)]
+pub struct Range<I> {
+ begin: I,
+ length: I,
+}
+
+impl<I: RangeIndex> fmt::Show for Range<I> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "[{} .. {})", self.begin(), self.end())
+ }
+}
+
+/// An iterator over each index in a range
+pub struct EachIndex<T, I> {
+ it: iter::Range<T>,
+}
+
+pub fn each_index<T: Int, I: IntRangeIndex<T>>(start: I, stop: I) -> EachIndex<T, I> {
+ EachIndex { it: iter::range(start.get(), stop.get()) }
+}
+
+impl<T: Int, I: IntRangeIndex<T>> Iterator<I> for EachIndex<T, I> {
+ #[inline]
+ fn next(&mut self) -> Option<I> {
+ self.it.next().map(|i| IntRangeIndex::new(i))
+ }
+
+ #[inline]
+ fn size_hint(&self) -> (uint, Option<uint>) {
+ self.it.size_hint()
+ }
+}
+
+impl<I: RangeIndex> Range<I> {
+ /// Create a new range from beginning and length offsets. This could be
+ /// denoted as `[begin, begin + length)`.
+ ///
+ /// ~~~
+ /// |-- begin ->|-- length ->|
+ /// | | |
+ /// <- o - - - - - +============+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn new(begin: I, length: I) -> Range<I> {
+ Range { begin: begin, length: length }
+ }
+
+ #[inline]
+ pub fn empty() -> Range<I> {
+ Range::new(num::zero(), num::zero())
+ }
+
+ /// The index offset to the beginning of the range.
+ ///
+ /// ~~~
+ /// |-- begin ->|
+ /// | |
+ /// <- o - - - - - +============+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn begin(&self) -> I { self.begin }
+
+ /// The index offset from the beginning to the end of the range.
+ ///
+ /// ~~~
+ /// |-- length ->|
+ /// | |
+ /// <- o - - - - - +============+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn length(&self) -> I { self.length }
+
+ /// The index offset to the end of the range.
+ ///
+ /// ~~~
+ /// |--------- end --------->|
+ /// | |
+ /// <- o - - - - - +============+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn end(&self) -> I { self.begin + self.length }
+
+ /// `true` if the index is between the beginning and the end of the range.
+ ///
+ /// ~~~
+ /// false true false
+ /// | | |
+ /// <- o - - + - - +=====+======+ - + - ->
+ /// ~~~
+ #[inline]
+ pub fn contains(&self, i: I) -> bool {
+ i >= self.begin() && i < self.end()
+ }
+
+ /// `true` if the offset from the beginning to the end of the range is zero.
+ #[inline]
+ pub fn is_empty(&self) -> bool {
+ self.length().is_zero()
+ }
+
+ /// Shift the entire range by the supplied index delta.
+ ///
+ /// ~~~
+ /// |-- delta ->|
+ /// | |
+ /// <- o - +============+ - - - - - | - - - ->
+ /// |
+ /// <- o - - - - - - - +============+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn shift_by(&mut self, delta: I) {
+ self.begin = self.begin + delta;
+ }
+
+ /// Extend the end of the range by the supplied index delta.
+ ///
+ /// ~~~
+ /// |-- delta ->|
+ /// | |
+ /// <- o - - - - - +====+ - - - - - | - - - ->
+ /// |
+ /// <- o - - - - - +================+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn extend_by(&mut self, delta: I) {
+ self.length = self.length + delta;
+ }
+
+ /// Move the end of the range to the target index.
+ ///
+ /// ~~~
+ /// target
+ /// |
+ /// <- o - - - - - +====+ - - - - - | - - - ->
+ /// |
+ /// <- o - - - - - +================+ - - - ->
+ /// ~~~
+ #[inline]
+ pub fn extend_to(&mut self, target: I) {
+ self.length = target - self.begin;
+ }
+
+ /// Adjust the beginning offset and the length by the supplied deltas.
+ #[inline]
+ pub fn adjust_by(&mut self, begin_delta: I, length_delta: I) {
+ self.begin = self.begin + begin_delta;
+ self.length = self.length + length_delta;
+ }
+
+ /// Set the begin and length values.
+ #[inline]
+ pub fn reset(&mut self, begin: I, length: I) {
+ self.begin = begin;
+ self.length = length;
+ }
+
+ #[inline]
+ pub fn intersect(&self, other: &Range<I>) -> Range<I> {
+ let begin = max(self.begin(), other.begin());
+ let end = min(self.end(), other.end());
+
+ if end < begin {
+ Range::empty()
+ } else {
+ Range::new(begin, end - begin)
+ }
+ }
+
+ /// Computes the relationship between two ranges (`self` and `other`),
+ /// from the point of view of `self`. So, 'EntirelyBefore' means
+ /// that the `self` range is entirely before `other` range.
+ #[inline]
+ pub fn relation_to_range(&self, other: &Range<I>) -> RangeRelation<I> {
+ if other.begin() > self.end() {
+ return EntirelyBefore;
+ }
+ if self.begin() > other.end() {
+ return EntirelyAfter;
+ }
+ if self.begin() == other.begin() && self.end() == other.end() {
+ return Coincides;
+ }
+ if self.begin() <= other.begin() && self.end() >= other.end() {
+ return Contains;
+ }
+ if self.begin() >= other.begin() && self.end() <= other.end() {
+ return ContainedBy;
+ }
+ if self.begin() < other.begin() && self.end() < other.end() {
+ let overlap = self.end() - other.begin();
+ return OverlapsBegin(overlap);
+ }
+ if self.begin() > other.begin() && self.end() > other.end() {
+ let overlap = other.end() - self.begin();
+ return OverlapsEnd(overlap);
+ }
+ fail!("relation_to_range(): didn't classify self={}, other={}",
+ self, other);
+ }
+}
+
+/// Methods for `Range`s with indices based on integer values
+impl<T: Int, I: IntRangeIndex<T>> Range<I> {
+ /// Returns an iterater that increments over `[begin, end)`.
+ #[inline]
+ pub fn each_index(&self) -> EachIndex<T, I> {
+ each_index(self.begin(), self.end())
+ }
+
+ #[inline]
+ pub fn is_valid_for_string(&self, s: &str) -> bool {
+ let s_len = s.len();
+ match num::cast::<uint, T>(s_len) {
+ Some(len) => {
+ let len = IntRangeIndex::new(len);
+ self.begin() < len
+ && self.end() <= len
+ && self.length() <= len
+ },
+ None => {
+ debug!("Range<T>::is_valid_for_string: string length (len={}) is longer than the \
+ max value for the range index (max={})", s_len,
+ {
+ let max: T = Bounded::max_value();
+ let val: I = IntRangeIndex::new(max);
+ val
+ });
+ false
+ },
+ }
+ }
+
+ #[inline]
+ pub fn repair_after_coalesced_range(&mut self, other: &Range<I>) {
+ let relation = self.relation_to_range(other);
+ debug!("repair_after_coalesced_range: possibly repairing range {}", *self);
+ debug!("repair_after_coalesced_range: relation of original range and coalesced range {}: {}",
+ *other, relation);
+ let _1: I = IntRangeIndex::new(num::one::<T>());
+ match relation {
+ EntirelyBefore => {},
+ EntirelyAfter => { self.shift_by(-other.length()); },
+ Coincides | ContainedBy => { self.reset(other.begin(), _1); },
+ Contains => { self.extend_by(-other.length()); },
+ OverlapsBegin(overlap) => { self.extend_by(_1 - overlap); },
+ OverlapsEnd(overlap) => {
+ let len = self.length() - overlap + _1;
+ self.reset(other.begin(), len);
+ }
+ };
+ debug!("repair_after_coalesced_range: new range: ---- {}", *self);
+ }
+}
diff --git a/components/util/smallvec.rs b/components/util/smallvec.rs
new file mode 100644
index 00000000000..4b926c78701
--- /dev/null
+++ b/components/util/smallvec.rs
@@ -0,0 +1,530 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Small vectors in various sizes. These store a certain number of elements inline and fall back
+//! to the heap for larger allocations.
+
+use i = std::mem::init;
+use std::cmp;
+use std::intrinsics;
+use std::kinds::marker::ContravariantLifetime;
+use std::mem;
+use std::ptr;
+use std::raw::Slice;
+use rustrt::local_heap;
+use alloc::heap;
+
+// Generic code for all small vectors
+
+pub trait VecLike<T> {
+ fn vec_len(&self) -> uint;
+ fn vec_push(&mut self, value: T);
+
+ fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T];
+
+ #[inline]
+ fn vec_mut_slice_from<'a>(&'a mut self, start: uint) -> &'a mut [T] {
+ let len = self.vec_len();
+ self.vec_mut_slice(start, len)
+ }
+}
+
+impl<T> VecLike<T> for Vec<T> {
+ #[inline]
+ fn vec_len(&self) -> uint {
+ self.len()
+ }
+
+ #[inline]
+ fn vec_push(&mut self, value: T) {
+ self.push(value);
+ }
+
+ #[inline]
+ fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] {
+ self.mut_slice(start, end)
+ }
+}
+
+trait SmallVecPrivate<T> {
+ unsafe fn set_len(&mut self, new_len: uint);
+ unsafe fn set_cap(&mut self, new_cap: uint);
+ fn data(&self, index: uint) -> *const T;
+ fn mut_data(&mut self, index: uint) -> *mut T;
+ unsafe fn ptr(&self) -> *const T;
+ unsafe fn mut_ptr(&mut self) -> *mut T;
+ unsafe fn set_ptr(&mut self, new_ptr: *mut T);
+}
+
+pub trait SmallVec<T> : SmallVecPrivate<T> {
+ fn inline_size(&self) -> uint;
+ fn len(&self) -> uint;
+ fn cap(&self) -> uint;
+
+ fn spilled(&self) -> bool {
+ self.cap() > self.inline_size()
+ }
+
+ fn begin(&self) -> *const T {
+ unsafe {
+ if self.spilled() {
+ self.ptr()
+ } else {
+ self.data(0)
+ }
+ }
+ }
+
+ fn end(&self) -> *const T {
+ unsafe {
+ self.begin().offset(self.len() as int)
+ }
+ }
+
+ fn iter<'a>(&'a self) -> SmallVecIterator<'a,T> {
+ SmallVecIterator {
+ ptr: self.begin(),
+ end: self.end(),
+ lifetime: ContravariantLifetime::<'a>,
+ }
+ }
+
+ fn mut_iter<'a>(&'a mut self) -> SmallVecMutIterator<'a,T> {
+ unsafe {
+ SmallVecMutIterator {
+ ptr: mem::transmute(self.begin()),
+ end: mem::transmute(self.end()),
+ lifetime: ContravariantLifetime::<'a>,
+ }
+ }
+ }
+
+ /// NB: For efficiency reasons (avoiding making a second copy of the inline elements), this
+ /// actually clears out the original array instead of moving it.
+ fn move_iter<'a>(&'a mut self) -> SmallVecMoveIterator<'a,T> {
+ unsafe {
+ let iter = mem::transmute(self.iter());
+ let ptr_opt = if self.spilled() {
+ Some(mem::transmute(self.ptr()))
+ } else {
+ None
+ };
+ let inline_size = self.inline_size();
+ self.set_cap(inline_size);
+ self.set_len(0);
+ SmallVecMoveIterator {
+ allocation: ptr_opt,
+ cap: inline_size,
+ iter: iter,
+ lifetime: ContravariantLifetime::<'a>,
+ }
+ }
+ }
+
+ fn push(&mut self, value: T) {
+ let cap = self.cap();
+ if self.len() == cap {
+ self.grow(cmp::max(cap * 2, 1))
+ }
+ unsafe {
+ let end: &mut T = mem::transmute(self.end());
+ ptr::write(end, value);
+ let len = self.len();
+ self.set_len(len + 1)
+ }
+ }
+
+ fn push_all_move<V:SmallVec<T>>(&mut self, mut other: V) {
+ for value in other.move_iter() {
+ self.push(value)
+ }
+ }
+
+ fn pop(&mut self) -> Option<T> {
+ if self.len() == 0 {
+ return None
+ }
+
+ unsafe {
+ let mut value: T = mem::uninitialized();
+ let last_index = self.len() - 1;
+
+ if (last_index as int) < 0 {
+ fail!("overflow")
+ }
+ let end_ptr = self.begin().offset(last_index as int);
+
+ mem::swap(&mut value, mem::transmute::<*const T,&mut T>(end_ptr));
+ self.set_len(last_index);
+ Some(value)
+ }
+ }
+
+ fn grow(&mut self, new_cap: uint) {
+ unsafe {
+ let new_alloc: *mut T = mem::transmute(heap::allocate(mem::size_of::<T>() *
+ new_cap,
+ mem::min_align_of::<T>()));
+ ptr::copy_nonoverlapping_memory(new_alloc, self.begin(), self.len());
+
+ if self.spilled() {
+ if intrinsics::owns_managed::<T>() {
+ local_heap::local_free(self.ptr() as *mut u8)
+ } else {
+ heap::deallocate(self.mut_ptr() as *mut u8,
+ mem::size_of::<T>() * self.cap(),
+ mem::min_align_of::<T>())
+ }
+ } else {
+ let mut_begin: *mut T = mem::transmute(self.begin());
+ intrinsics::set_memory(mut_begin, 0, self.len())
+ }
+
+ self.set_ptr(new_alloc);
+ self.set_cap(new_cap)
+ }
+ }
+
+ fn get<'a>(&'a self, index: uint) -> &'a T {
+ if index >= self.len() {
+ self.fail_bounds_check(index)
+ }
+ unsafe {
+ mem::transmute(self.begin().offset(index as int))
+ }
+ }
+
+ fn get_mut<'a>(&'a mut self, index: uint) -> &'a mut T {
+ if index >= self.len() {
+ self.fail_bounds_check(index)
+ }
+ unsafe {
+ mem::transmute(self.begin().offset(index as int))
+ }
+ }
+
+ fn slice<'a>(&'a self, start: uint, end: uint) -> &'a [T] {
+ assert!(start <= end);
+ assert!(end <= self.len());
+ unsafe {
+ mem::transmute(Slice {
+ data: self.begin().offset(start as int),
+ len: (end - start)
+ })
+ }
+ }
+
+ fn as_slice<'a>(&'a self) -> &'a [T] {
+ self.slice(0, self.len())
+ }
+
+ fn as_mut_slice<'a>(&'a mut self) -> &'a mut [T] {
+ let len = self.len();
+ self.mut_slice(0, len)
+ }
+
+ fn mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] {
+ assert!(start <= end);
+ assert!(end <= self.len());
+ unsafe {
+ mem::transmute(Slice {
+ data: self.begin().offset(start as int),
+ len: (end - start)
+ })
+ }
+ }
+
+ fn mut_slice_from<'a>(&'a mut self, start: uint) -> &'a mut [T] {
+ let len = self.len();
+ self.mut_slice(start, len)
+ }
+
+ fn fail_bounds_check(&self, index: uint) {
+ fail!("index {} beyond length ({})", index, self.len())
+ }
+}
+
+pub struct SmallVecIterator<'a,T> {
+ ptr: *const T,
+ end: *const T,
+ lifetime: ContravariantLifetime<'a>
+}
+
+impl<'a,T> Iterator<&'a T> for SmallVecIterator<'a,T> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a T> {
+ unsafe {
+ if self.ptr == self.end {
+ return None
+ }
+ let old = self.ptr;
+ self.ptr = if mem::size_of::<T>() == 0 {
+ mem::transmute(self.ptr as uint + 1)
+ } else {
+ self.ptr.offset(1)
+ };
+ Some(mem::transmute(old))
+ }
+ }
+}
+
+pub struct SmallVecMutIterator<'a,T> {
+ ptr: *mut T,
+ end: *mut T,
+ lifetime: ContravariantLifetime<'a>,
+}
+
+impl<'a,T> Iterator<&'a mut T> for SmallVecMutIterator<'a,T> {
+ #[inline]
+ fn next(&mut self) -> Option<&'a mut T> {
+ unsafe {
+ if self.ptr == self.end {
+ return None
+ }
+ let old = self.ptr;
+ self.ptr = if mem::size_of::<T>() == 0 {
+ mem::transmute(self.ptr as uint + 1)
+ } else {
+ self.ptr.offset(1)
+ };
+ Some(mem::transmute(old))
+ }
+ }
+}
+
+pub struct SmallVecMoveIterator<'a,T> {
+ allocation: Option<*mut u8>,
+ cap: uint,
+ iter: SmallVecIterator<'static,T>,
+ lifetime: ContravariantLifetime<'a>,
+}
+
+impl<'a,T> Iterator<T> for SmallVecMoveIterator<'a,T> {
+ #[inline]
+ fn next(&mut self) -> Option<T> {
+ unsafe {
+ match self.iter.next() {
+ None => None,
+ Some(reference) => {
+ // Zero out the values as we go so they don't get double-freed.
+ let reference: &mut T = mem::transmute(reference);
+ Some(mem::replace(reference, mem::zeroed()))
+ }
+ }
+ }
+ }
+}
+
+#[unsafe_destructor]
+impl<'a,T> Drop for SmallVecMoveIterator<'a,T> {
+ fn drop(&mut self) {
+ // Destroy the remaining elements.
+ for _ in *self {}
+
+ match self.allocation {
+ None => {}
+ Some(allocation) => {
+ unsafe {
+ if intrinsics::owns_managed::<T>() {
+ local_heap::local_free(allocation as *mut u8)
+ } else {
+ heap::deallocate(allocation as *mut u8,
+ mem::size_of::<T>() * self.cap,
+ mem::min_align_of::<T>())
+ }
+ }
+ }
+ }
+ }
+}
+
+// Concrete implementations
+
+macro_rules! def_small_vector(
+ ($name:ident, $size:expr) => (
+ pub struct $name<T> {
+ len: uint,
+ cap: uint,
+ ptr: *const T,
+ data: [T, ..$size],
+ }
+
+ impl<T> SmallVecPrivate<T> for $name<T> {
+ unsafe fn set_len(&mut self, new_len: uint) {
+ self.len = new_len
+ }
+ unsafe fn set_cap(&mut self, new_cap: uint) {
+ self.cap = new_cap
+ }
+ fn data(&self, index: uint) -> *const T {
+ let ptr: *const T = &self.data[index];
+ ptr
+ }
+ fn mut_data(&mut self, index: uint) -> *mut T {
+ let ptr: *mut T = &mut self.data[index];
+ ptr
+ }
+ unsafe fn ptr(&self) -> *const T {
+ self.ptr
+ }
+ unsafe fn mut_ptr(&mut self) -> *mut T {
+ mem::transmute(self.ptr)
+ }
+ unsafe fn set_ptr(&mut self, new_ptr: *mut T) {
+ self.ptr = mem::transmute(new_ptr)
+ }
+ }
+
+ impl<T> SmallVec<T> for $name<T> {
+ fn inline_size(&self) -> uint {
+ $size
+ }
+ fn len(&self) -> uint {
+ self.len
+ }
+ fn cap(&self) -> uint {
+ self.cap
+ }
+ }
+
+ impl<T> VecLike<T> for $name<T> {
+ #[inline]
+ fn vec_len(&self) -> uint {
+ self.len()
+ }
+
+ #[inline]
+ fn vec_push(&mut self, value: T) {
+ self.push(value);
+ }
+
+ #[inline]
+ fn vec_mut_slice<'a>(&'a mut self, start: uint, end: uint) -> &'a mut [T] {
+ self.mut_slice(start, end)
+ }
+ }
+
+ impl<T> $name<T> {
+ #[inline]
+ pub fn new() -> $name<T> {
+ unsafe {
+ $name {
+ len: 0,
+ cap: $size,
+ ptr: ptr::null(),
+ data: mem::zeroed(),
+ }
+ }
+ }
+ }
+ )
+)
+
+def_small_vector!(SmallVec1, 1)
+def_small_vector!(SmallVec2, 2)
+def_small_vector!(SmallVec4, 4)
+def_small_vector!(SmallVec8, 8)
+def_small_vector!(SmallVec16, 16)
+def_small_vector!(SmallVec24, 24)
+def_small_vector!(SmallVec32, 32)
+
+macro_rules! def_small_vector_drop_impl(
+ ($name:ident, $size:expr) => (
+ #[unsafe_destructor]
+ impl<T> Drop for $name<T> {
+ fn drop(&mut self) {
+ if !self.spilled() {
+ return
+ }
+
+ unsafe {
+ let ptr = self.mut_ptr();
+ for i in range(0, self.len()) {
+ *ptr.offset(i as int) = mem::uninitialized();
+ }
+
+ if intrinsics::owns_managed::<T>() {
+ local_heap::local_free(self.ptr() as *mut u8)
+ } else {
+ heap::deallocate(self.mut_ptr() as *mut u8,
+ mem::size_of::<T>() * self.cap(),
+ mem::min_align_of::<T>())
+ }
+ }
+ }
+ }
+ )
+)
+
+def_small_vector_drop_impl!(SmallVec1, 1)
+def_small_vector_drop_impl!(SmallVec2, 2)
+def_small_vector_drop_impl!(SmallVec4, 4)
+def_small_vector_drop_impl!(SmallVec8, 8)
+def_small_vector_drop_impl!(SmallVec16, 16)
+def_small_vector_drop_impl!(SmallVec24, 24)
+def_small_vector_drop_impl!(SmallVec32, 32)
+
+macro_rules! def_small_vector_clone_impl(
+ ($name:ident) => (
+ impl<T:Clone> Clone for $name<T> {
+ fn clone(&self) -> $name<T> {
+ let mut new_vector = $name::new();
+ for element in self.iter() {
+ new_vector.push((*element).clone())
+ }
+ new_vector
+ }
+ }
+ )
+)
+
+def_small_vector_clone_impl!(SmallVec1)
+def_small_vector_clone_impl!(SmallVec2)
+def_small_vector_clone_impl!(SmallVec4)
+def_small_vector_clone_impl!(SmallVec8)
+def_small_vector_clone_impl!(SmallVec16)
+def_small_vector_clone_impl!(SmallVec24)
+def_small_vector_clone_impl!(SmallVec32)
+
+#[cfg(test)]
+pub mod tests {
+ use smallvec::{SmallVec, SmallVec2, SmallVec16};
+
+ // We heap allocate all these strings so that double frees will show up under valgrind.
+
+ #[test]
+ pub fn test_inline() {
+ let mut v = SmallVec16::new();
+ v.push("hello".to_string());
+ v.push("there".to_string());
+ assert_eq!(v.as_slice(), &["hello".to_string(), "there".to_string()]);
+ }
+
+ #[test]
+ pub fn test_spill() {
+ let mut v = SmallVec2::new();
+ v.push("hello".to_string());
+ v.push("there".to_string());
+ v.push("burma".to_string());
+ v.push("shave".to_string());
+ assert_eq!(v.as_slice(), &["hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string()]);
+ }
+
+ #[test]
+ pub fn test_double_spill() {
+ let mut v = SmallVec2::new();
+ v.push("hello".to_string());
+ v.push("there".to_string());
+ v.push("burma".to_string());
+ v.push("shave".to_string());
+ v.push("hello".to_string());
+ v.push("there".to_string());
+ v.push("burma".to_string());
+ v.push("shave".to_string());
+ assert_eq!(v.as_slice(), &[
+ "hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string(), "hello".to_string(), "there".to_string(), "burma".to_string(), "shave".to_string(),
+ ]);
+ }
+}
+
diff --git a/components/util/sort.rs b/components/util/sort.rs
new file mode 100644
index 00000000000..32dc52f6574
--- /dev/null
+++ b/components/util/sort.rs
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! In-place sorting.
+
+fn quicksort_helper<T>(arr: &mut [T], left: int, right: int, compare: fn(&T, &T) -> Ordering) {
+ if right <= left {
+ return
+ }
+
+ let mut i: int = left - 1;
+ let mut j: int = right;
+ let mut p: int = i;
+ let mut q: int = j;
+ unsafe {
+ let v: *mut T = &mut arr[right as uint];
+ loop {
+ i += 1;
+ while compare(&arr[i as uint], &*v) == Less {
+ i += 1
+ }
+ j -= 1;
+ while compare(&*v, &arr[j as uint]) == Less {
+ if j == left {
+ break
+ }
+ j -= 1;
+ }
+ if i >= j {
+ break
+ }
+ arr.swap(i as uint, j as uint);
+ if compare(&arr[i as uint], &*v) == Equal {
+ p += 1;
+ arr.swap(p as uint, i as uint)
+ }
+ if compare(&*v, &arr[j as uint]) == Equal {
+ q -= 1;
+ arr.swap(j as uint, q as uint)
+ }
+ }
+ }
+
+ arr.swap(i as uint, right as uint);
+ j = i - 1;
+ i += 1;
+ let mut k: int = left;
+ while k < p {
+ arr.swap(k as uint, j as uint);
+ k += 1;
+ j -= 1;
+ assert!(k < arr.len() as int);
+ }
+ k = right - 1;
+ while k > q {
+ arr.swap(i as uint, k as uint);
+ k -= 1;
+ i += 1;
+ assert!(k != 0);
+ }
+
+ quicksort_helper(arr, left, j, compare);
+ quicksort_helper(arr, i, right, compare);
+}
+
+/// An in-place quicksort.
+///
+/// The algorithm is from Sedgewick and Bentley, "Quicksort is Optimal":
+/// http://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf
+pub fn quicksort_by<T>(arr: &mut [T], compare: fn(&T, &T) -> Ordering) {
+ if arr.len() <= 1 {
+ return
+ }
+
+ let len = arr.len();
+ quicksort_helper(arr, 0, (len - 1) as int, compare);
+}
+
+#[cfg(test)]
+pub mod test {
+ use std::rand;
+ use std::rand::Rng;
+
+ use sort;
+
+ #[test]
+ pub fn random() {
+ let mut rng = rand::task_rng();
+ for _ in range(0u32, 50000u32) {
+ let len: uint = rng.gen();
+ let mut v: Vec<int> = rng.gen_iter::<int>().take((len % 32) + 1).collect();
+ fn compare_ints(a: &int, b: &int) -> Ordering { a.cmp(b) }
+ sort::quicksort_by(v.as_mut_slice(), compare_ints);
+ for i in range(0, v.len() - 1) {
+ assert!(v.get(i) <= v.get(i + 1))
+ }
+ }
+ }
+}
+
diff --git a/components/util/str.rs b/components/util/str.rs
new file mode 100644
index 00000000000..9d07cf80b99
--- /dev/null
+++ b/components/util/str.rs
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::iter::Filter;
+use std::str::CharSplits;
+
+pub type DOMString = String;
+pub type StaticCharVec = &'static [char];
+pub type StaticStringVec = &'static [&'static str];
+
+pub fn null_str_as_empty(s: &Option<DOMString>) -> DOMString {
+ // We don't use map_default because it would allocate "".to_string() even for Some.
+ match *s {
+ Some(ref s) => s.clone(),
+ None => "".to_string()
+ }
+}
+
+pub fn null_str_as_empty_ref<'a>(s: &'a Option<DOMString>) -> &'a str {
+ match *s {
+ Some(ref s) => s.as_slice(),
+ None => ""
+ }
+}
+
+pub fn is_whitespace(s: &str) -> bool {
+ s.chars().all(|c| match c {
+ '\u0020' | '\u0009' | '\u000D' | '\u000A' => true,
+ _ => false
+ })
+}
+
+/// A "space character" according to:
+///
+/// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#
+/// space-character
+pub static HTML_SPACE_CHARACTERS: StaticCharVec = &[
+ '\u0020',
+ '\u0009',
+ '\u000a',
+ '\u000c',
+ '\u000d',
+];
+
+pub fn split_html_space_chars<'a>(s: &'a str) -> Filter<'a, &'a str, CharSplits<'a, StaticCharVec>> {
+ s.split(HTML_SPACE_CHARACTERS).filter(|&split| !split.is_empty())
+}
+
+/// Shared implementation to parse an integer according to
+/// <http://www.whatwg.org/html/#rules-for-parsing-integers> or
+/// <http://www.whatwg.org/html/#rules-for-parsing-non-negative-integers>.
+fn do_parse_integer<T: Iterator<char>>(input: T) -> Option<i64> {
+ fn is_ascii_digit(c: &char) -> bool {
+ match *c {
+ '0'..'9' => true,
+ _ => false,
+ }
+ }
+
+
+ let mut input = input.skip_while(|c| {
+ HTML_SPACE_CHARACTERS.iter().any(|s| s == c)
+ }).peekable();
+
+ let sign = match input.peek() {
+ None => return None,
+ Some(&'-') => {
+ input.next();
+ -1
+ },
+ Some(&'+') => {
+ input.next();
+ 1
+ },
+ Some(_) => 1,
+ };
+
+ match input.peek() {
+ Some(c) if is_ascii_digit(c) => (),
+ _ => return None,
+ }
+
+ let value = input.take_while(is_ascii_digit).map(|d| {
+ d as i64 - '0' as i64
+ }).fold(Some(0i64), |accumulator, d| {
+ accumulator.and_then(|accumulator| {
+ accumulator.checked_mul(&10)
+ }).and_then(|accumulator| {
+ accumulator.checked_add(&d)
+ })
+ });
+
+ return value.and_then(|value| value.checked_mul(&sign));
+}
+
+/// Parse an integer according to
+/// <http://www.whatwg.org/html/#rules-for-parsing-integers>.
+pub fn parse_integer<T: Iterator<char>>(input: T) -> Option<i32> {
+ do_parse_integer(input).and_then(|result| {
+ result.to_i32()
+ })
+}
+
+/// Parse an integer according to
+/// <http://www.whatwg.org/html/#rules-for-parsing-non-negative-integers>.
+pub fn parse_unsigned_integer<T: Iterator<char>>(input: T) -> Option<u32> {
+ do_parse_integer(input).and_then(|result| {
+ result.to_u32()
+ })
+}
diff --git a/components/util/task.rs b/components/util/task.rs
new file mode 100644
index 00000000000..b3e03771610
--- /dev/null
+++ b/components/util/task.rs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::str::IntoMaybeOwned;
+use std::task;
+use std::comm::Sender;
+use std::task::TaskBuilder;
+use native::task::NativeTaskBuilder;
+
+pub fn spawn_named<S: IntoMaybeOwned<'static>>(name: S, f: proc():Send) {
+ let builder = task::TaskBuilder::new().named(name);
+ builder.spawn(f);
+}
+
+/// Arrange to send a particular message to a channel if the task built by
+/// this `TaskBuilder` fails.
+pub fn spawn_named_with_send_on_failure<T: Send>(name: &'static str,
+ f: proc(): Send,
+ msg: T,
+ dest: Sender<T>,
+ native: bool) {
+ let future_result = if native {
+ TaskBuilder::new().named(name).native().try_future(f)
+ } else {
+ TaskBuilder::new().named(name).try_future(f)
+ };
+
+ let watched_name = name.to_string();
+ let watcher_name = format!("{:s}Watcher", watched_name);
+ TaskBuilder::new().named(watcher_name).spawn(proc() {
+ match future_result.unwrap() {
+ Ok(()) => (),
+ Err(..) => {
+ debug!("{:s} failed, notifying constellation", name);
+ dest.send(msg);
+ }
+ }
+ });
+}
diff --git a/components/util/time.rs b/components/util/time.rs
new file mode 100644
index 00000000000..4f282aa2648
--- /dev/null
+++ b/components/util/time.rs
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Timing functions.
+
+use std_time::precise_time_ns;
+use collections::treemap::TreeMap;
+use std::comm::{Sender, channel, Receiver};
+use std::f64;
+use std::iter::AdditiveIterator;
+use std::io::timer::sleep;
+use task::{spawn_named};
+
+// front-end representation of the profiler used to communicate with the profiler
+#[deriving(Clone)]
+pub struct TimeProfilerChan(pub Sender<TimeProfilerMsg>);
+
+impl TimeProfilerChan {
+ pub fn send(&self, msg: TimeProfilerMsg) {
+ let TimeProfilerChan(ref c) = *self;
+ c.send(msg);
+ }
+}
+
+pub enum TimeProfilerMsg {
+ /// Normal message used for reporting time
+ TimeMsg(TimeProfilerCategory, f64),
+ /// Message used to force print the profiling metrics
+ PrintMsg,
+ /// Tells the profiler to shut down.
+ ExitMsg,
+}
+
+#[repr(u32)]
+#[deriving(PartialEq, Clone, PartialOrd, Eq, Ord)]
+pub enum TimeProfilerCategory {
+ CompositingCategory,
+ LayoutQueryCategory,
+ LayoutPerformCategory,
+ LayoutStyleRecalcCategory,
+ LayoutSelectorMatchCategory,
+ LayoutTreeBuilderCategory,
+ LayoutDamagePropagateCategory,
+ LayoutMainCategory,
+ LayoutParallelWarmupCategory,
+ LayoutShapingCategory,
+ LayoutDispListBuildCategory,
+ GfxRegenAvailableFontsCategory,
+ RenderingDrawingCategory,
+ RenderingPrepBuffCategory,
+ RenderingCategory,
+ // FIXME(rust#8803): workaround for lack of CTFE function on enum types to return length
+ NumBuckets,
+}
+
+impl TimeProfilerCategory {
+ // convenience function to not have to cast every time
+ pub fn num_buckets() -> uint {
+ NumBuckets as uint
+ }
+
+ // enumeration of all TimeProfilerCategory types
+ fn empty_buckets() -> TimeProfilerBuckets {
+ let mut buckets = TreeMap::new();
+ buckets.insert(CompositingCategory, vec!());
+ buckets.insert(LayoutQueryCategory, vec!());
+ buckets.insert(LayoutPerformCategory, vec!());
+ buckets.insert(LayoutStyleRecalcCategory, vec!());
+ buckets.insert(LayoutSelectorMatchCategory, vec!());
+ buckets.insert(LayoutTreeBuilderCategory, vec!());
+ buckets.insert(LayoutMainCategory, vec!());
+ buckets.insert(LayoutParallelWarmupCategory, vec!());
+ buckets.insert(LayoutShapingCategory, vec!());
+ buckets.insert(LayoutDamagePropagateCategory, vec!());
+ buckets.insert(LayoutDispListBuildCategory, vec!());
+ buckets.insert(GfxRegenAvailableFontsCategory, vec!());
+ buckets.insert(RenderingDrawingCategory, vec!());
+ buckets.insert(RenderingPrepBuffCategory, vec!());
+ buckets.insert(RenderingCategory, vec!());
+
+ buckets
+ }
+
+ // some categories are subcategories of LayoutPerformCategory
+ // and should be printed to indicate this
+ pub fn format(self) -> String {
+ let padding = match self {
+ LayoutStyleRecalcCategory |
+ LayoutMainCategory |
+ LayoutDispListBuildCategory |
+ LayoutShapingCategory |
+ LayoutDamagePropagateCategory => "+ ",
+ LayoutParallelWarmupCategory |
+ LayoutSelectorMatchCategory |
+ LayoutTreeBuilderCategory => "| + ",
+ _ => ""
+ };
+ format!("{:s}{:?}", padding, self)
+ }
+}
+
+type TimeProfilerBuckets = TreeMap<TimeProfilerCategory, Vec<f64>>;
+
+// back end of the profiler that handles data aggregation and performance metrics
+pub struct TimeProfiler {
+ pub port: Receiver<TimeProfilerMsg>,
+ buckets: TimeProfilerBuckets,
+ pub last_msg: Option<TimeProfilerMsg>,
+}
+
+impl TimeProfiler {
+ pub fn create(period: Option<f64>) -> TimeProfilerChan {
+ let (chan, port) = channel();
+ match period {
+ Some(period) => {
+ let period = (period * 1000f64) as u64;
+ let chan = chan.clone();
+ spawn_named("Time profiler timer", proc() {
+ loop {
+ sleep(period);
+ if chan.send_opt(PrintMsg).is_err() {
+ break;
+ }
+ }
+ });
+ // Spawn the time profiler.
+ spawn_named("Time profiler", proc() {
+ let mut profiler = TimeProfiler::new(port);
+ profiler.start();
+ });
+ }
+ None => {
+ // No-op to handle messages when the time profiler is inactive.
+ spawn_named("Time profiler", proc() {
+ loop {
+ match port.recv_opt() {
+ Err(_) | Ok(ExitMsg) => break,
+ _ => {}
+ }
+ }
+ });
+ }
+ }
+
+ TimeProfilerChan(chan)
+ }
+
+ pub fn new(port: Receiver<TimeProfilerMsg>) -> TimeProfiler {
+ TimeProfiler {
+ port: port,
+ buckets: TimeProfilerCategory::empty_buckets(),
+ last_msg: None,
+ }
+ }
+
+ pub fn start(&mut self) {
+ loop {
+ let msg = self.port.recv_opt();
+ match msg {
+ Ok(msg) => {
+ if !self.handle_msg(msg) {
+ break
+ }
+ }
+ _ => break
+ }
+ }
+ }
+
+ fn handle_msg(&mut self, msg: TimeProfilerMsg) -> bool {
+ match msg {
+ TimeMsg(category, t) => self.buckets.find_mut(&category).unwrap().push(t),
+ PrintMsg => match self.last_msg {
+ // only print if more data has arrived since the last printout
+ Some(TimeMsg(..)) => self.print_buckets(),
+ _ => ()
+ },
+ ExitMsg => return false,
+ };
+ self.last_msg = Some(msg);
+ true
+ }
+
+ fn print_buckets(&mut self) {
+ println!("{:39s} {:15s} {:15s} {:15s} {:15s} {:15s}",
+ "_category_", "_mean (ms)_", "_median (ms)_",
+ "_min (ms)_", "_max (ms)_", "_bucket size_");
+ for (category, data) in self.buckets.mut_iter() {
+ data.sort_by(|a, b| {
+ if a < b {
+ Less
+ } else {
+ Greater
+ }
+ });
+ let data_len = data.len();
+ if data_len > 0 {
+ let (mean, median, min, max) =
+ (data.iter().map(|&x|x).sum() / (data_len as f64),
+ (*data)[data_len / 2],
+ data.iter().fold(f64::INFINITY, |a, &b| a.min(b)),
+ data.iter().fold(-f64::INFINITY, |a, &b| a.max(b)));
+ println!("{:-35s}: {:15.4f} {:15.4f} {:15.4f} {:15.4f} {:15u}",
+ category.format(), mean, median, min, max, data_len);
+ }
+ }
+ println!("");
+ }
+}
+
+
+pub fn profile<T>(category: TimeProfilerCategory,
+ time_profiler_chan: TimeProfilerChan,
+ callback: || -> T)
+ -> T {
+ let start_time = precise_time_ns();
+ let val = callback();
+ let end_time = precise_time_ns();
+ let ms = (end_time - start_time) as f64 / 1000000f64;
+ time_profiler_chan.send(TimeMsg(category, ms));
+ return val;
+}
+
+pub fn time<T>(msg: &str, callback: || -> T) -> T{
+ let start_time = precise_time_ns();
+ let val = callback();
+ let end_time = precise_time_ns();
+ let ms = (end_time - start_time) as f64 / 1000000f64;
+ if ms >= 5f64 {
+ debug!("{:s} took {} ms", msg, ms);
+ }
+ return val;
+}
+
+// ensure that the order of the buckets matches the order of the enum categories
+#[test]
+fn check_order() {
+ let buckets = TimeProfilerCategory::empty_buckets();
+ assert!(buckets.len() == NumBuckets as uint);
+}
diff --git a/components/util/vec.rs b/components/util/vec.rs
new file mode 100644
index 00000000000..b8d24687d28
--- /dev/null
+++ b/components/util/vec.rs
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::cmp::{PartialOrd, PartialEq};
+
+/// FIXME(pcwalton): Workaround for lack of unboxed closures. This is called in
+/// performance-critical code, so a closure is insufficient.
+pub trait Comparator<K,T> {
+ fn compare(&self, key: &K, value: &T) -> Ordering;
+}
+
+pub trait BinarySearchMethods<'a, T: Ord + PartialOrd + PartialEq> {
+ fn binary_search(&self, key: &T) -> Option<&'a T>;
+ fn binary_search_index(&self, key: &T) -> Option<uint>;
+}
+
+pub trait FullBinarySearchMethods<T> {
+ fn binary_search_index_by<K,C:Comparator<K,T>>(&self, key: &K, cmp: C) -> Option<uint>;
+}
+
+impl<'a, T: Ord + PartialOrd + PartialEq> BinarySearchMethods<'a, T> for &'a [T] {
+ fn binary_search(&self, key: &T) -> Option<&'a T> {
+ self.binary_search_index(key).map(|i| &self[i])
+ }
+
+ fn binary_search_index(&self, key: &T) -> Option<uint> {
+ self.binary_search_index_by(key, DefaultComparator)
+ }
+}
+
+impl<'a, T> FullBinarySearchMethods<T> for &'a [T] {
+ fn binary_search_index_by<K,C:Comparator<K,T>>(&self, key: &K, cmp: C) -> Option<uint> {
+ if self.len() == 0 {
+ return None;
+ }
+
+ let mut low : int = 0;
+ let mut high : int = (self.len() as int) - 1;
+
+ while low <= high {
+ // http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
+ let mid = ((low as uint) + (high as uint)) >> 1;
+ let midv = &self[mid];
+
+ match cmp.compare(key, midv) {
+ Greater => low = (mid as int) + 1,
+ Less => high = (mid as int) - 1,
+ Equal => return Some(mid),
+ }
+ }
+ return None;
+ }
+}
+
+struct DefaultComparator;
+
+impl<T:PartialEq + PartialOrd + Ord> Comparator<T,T> for DefaultComparator {
+ fn compare(&self, key: &T, value: &T) -> Ordering {
+ (*key).cmp(value)
+ }
+}
+
+#[cfg(test)]
+fn test_find_all_elems<T: PartialEq + PartialOrd + Eq + Ord>(arr: &[T]) {
+ let mut i = 0;
+ while i < arr.len() {
+ assert!(test_match(&arr[i], arr.binary_search(&arr[i])));
+ i += 1;
+ }
+}
+
+#[cfg(test)]
+fn test_miss_all_elems<T: PartialEq + PartialOrd + Eq + Ord>(arr: &[T], misses: &[T]) {
+ let mut i = 0;
+ while i < misses.len() {
+ let res = arr.binary_search(&misses[i]);
+ debug!("{:?} == {:?} ?", misses[i], res);
+ assert!(!test_match(&misses[i], arr.binary_search(&misses[i])));
+ i += 1;
+ }
+}
+
+#[cfg(test)]
+fn test_match<T: PartialEq>(b: &T, a: Option<&T>) -> bool {
+ match a {
+ None => false,
+ Some(t) => t == b
+ }
+}
+
+#[test]
+fn should_find_all_elements() {
+ let arr_odd = [1u32, 2, 4, 6, 7, 8, 9];
+ let arr_even = [1u32, 2, 5, 6, 7, 8, 9, 42];
+ let arr_double = [1u32, 1, 2, 2, 6, 8, 22];
+ let arr_one = [234986325u32];
+ let arr_two = [3044u32, 8393];
+ let arr_three = [12u32, 23, 34];
+
+ test_find_all_elems(arr_odd);
+ test_find_all_elems(arr_even);
+ test_find_all_elems(arr_double);
+ test_find_all_elems(arr_one);
+ test_find_all_elems(arr_two);
+ test_find_all_elems(arr_three);
+}
+
+#[test]
+fn should_not_find_missing_elements() {
+ let arr_odd = [1u32, 2, 4, 6, 7, 8, 9];
+ let arr_even = [1u32, 2, 5, 6, 7, 8, 9, 42];
+ let arr_double = [1u32, 1, 2, 2, 6, 8, 22];
+ let arr_one = [234986325u32];
+ let arr_two = [3044u32, 8393];
+ let arr_three = [12u32, 23, 34];
+
+ test_miss_all_elems(arr_odd, [-22, 0, 3, 5, 34938, 10, 11, 12]);
+ test_miss_all_elems(arr_even, [-1, 0, 3, 34938, 10, 11, 12]);
+ test_miss_all_elems(arr_double, [-1, 0, 3, 4, 34938, 10, 11, 12, 234, 234, 33]);
+ test_miss_all_elems(arr_one, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]);
+ test_miss_all_elems(arr_two, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]);
+ test_miss_all_elems(arr_three, [-2, 0, 1, 2, 3, 34938, 10, 11, 234, 234, 33]);
+}
diff --git a/components/util/workqueue.rs b/components/util/workqueue.rs
new file mode 100644
index 00000000000..5b27b4a5dab
--- /dev/null
+++ b/components/util/workqueue.rs
@@ -0,0 +1,291 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A work queue for scheduling units of work across threads in a fork-join fashion.
+//!
+//! Data associated with queues is simply a pair of unsigned integers. It is expected that a
+//! higher-level API on top of this could allow safe fork-join parallelism.
+
+use native::task::NativeTaskBuilder;
+use rand::{Rng, XorShiftRng};
+use std::mem;
+use std::rand::weak_rng;
+use std::sync::atomics::{AtomicUint, SeqCst};
+use std::sync::deque::{Abort, BufferPool, Data, Empty, Stealer, Worker};
+use std::task::TaskBuilder;
+
+/// A unit of work.
+///
+/// # Type parameters
+///
+/// - `QueueData`: global custom data for the entire work queue.
+/// - `WorkData`: custom data specific to each unit of work.
+pub struct WorkUnit<QueueData, WorkData> {
+ /// The function to execute.
+ pub fun: extern "Rust" fn(WorkData, &mut WorkerProxy<QueueData, WorkData>),
+ /// Arbitrary data.
+ pub data: WorkData,
+}
+
+/// Messages from the supervisor to the worker.
+enum WorkerMsg<QueueData, WorkData> {
+ /// Tells the worker to start work.
+ StartMsg(Worker<WorkUnit<QueueData, WorkData>>, *mut AtomicUint, *const QueueData),
+ /// Tells the worker to stop. It can be restarted again with a `StartMsg`.
+ StopMsg,
+ /// Tells the worker thread to terminate.
+ ExitMsg,
+}
+
+/// Messages to the supervisor.
+enum SupervisorMsg<QueueData, WorkData> {
+ FinishedMsg,
+ ReturnDequeMsg(uint, Worker<WorkUnit<QueueData, WorkData>>),
+}
+
+/// Information that the supervisor thread keeps about the worker threads.
+struct WorkerInfo<QueueData, WorkData> {
+ /// The communication channel to the workers.
+ chan: Sender<WorkerMsg<QueueData, WorkData>>,
+ /// The worker end of the deque, if we have it.
+ deque: Option<Worker<WorkUnit<QueueData, WorkData>>>,
+ /// The thief end of the work-stealing deque.
+ thief: Stealer<WorkUnit<QueueData, WorkData>>,
+}
+
+/// Information specific to each worker thread that the thread keeps.
+struct WorkerThread<QueueData, WorkData> {
+ /// The index of this worker.
+ index: uint,
+ /// The communication port from the supervisor.
+ port: Receiver<WorkerMsg<QueueData, WorkData>>,
+ /// The communication channel on which messages are sent to the supervisor.
+ chan: Sender<SupervisorMsg<QueueData, WorkData>>,
+ /// The thief end of the work-stealing deque for all other workers.
+ other_deques: Vec<Stealer<WorkUnit<QueueData, WorkData>>>,
+ /// The random number generator for this worker.
+ rng: XorShiftRng,
+}
+
+static SPIN_COUNT: uint = 1000;
+
+impl<QueueData: Send, WorkData: Send> WorkerThread<QueueData, WorkData> {
+ /// The main logic. This function starts up the worker and listens for
+ /// messages.
+ fn start(&mut self) {
+ loop {
+ // Wait for a start message.
+ let (mut deque, ref_count, queue_data) = match self.port.recv() {
+ StartMsg(deque, ref_count, queue_data) => (deque, ref_count, queue_data),
+ StopMsg => fail!("unexpected stop message"),
+ ExitMsg => return,
+ };
+
+ // We're off!
+ //
+ // FIXME(pcwalton): Can't use labeled break or continue cross-crate due to a Rust bug.
+ loop {
+ // FIXME(pcwalton): Nasty workaround for the lack of labeled break/continue
+ // cross-crate.
+ let mut work_unit = unsafe {
+ mem::uninitialized()
+ };
+ match deque.pop() {
+ Some(work) => work_unit = work,
+ None => {
+ // Become a thief.
+ let mut i = 0;
+ let mut should_continue = true;
+ loop {
+ let victim = (self.rng.next_u32() as uint) % self.other_deques.len();
+ match self.other_deques.get_mut(victim).steal() {
+ Empty | Abort => {
+ // Continue.
+ }
+ Data(work) => {
+ work_unit = work;
+ break
+ }
+ }
+
+ if i == SPIN_COUNT {
+ match self.port.try_recv() {
+ Ok(StopMsg) => {
+ should_continue = false;
+ break
+ }
+ Ok(ExitMsg) => return,
+ Ok(_) => fail!("unexpected message"),
+ _ => {}
+ }
+
+ i = 0
+ } else {
+ i += 1
+ }
+ }
+
+ if !should_continue {
+ break
+ }
+ }
+ }
+
+ // At this point, we have some work. Perform it.
+ let mut proxy = WorkerProxy {
+ worker: &mut deque,
+ ref_count: ref_count,
+ queue_data: queue_data,
+ };
+ (work_unit.fun)(work_unit.data, &mut proxy);
+
+ // The work is done. Now decrement the count of outstanding work items. If this was
+ // the last work unit in the queue, then send a message on the channel.
+ unsafe {
+ if (*ref_count).fetch_sub(1, SeqCst) == 1 {
+ self.chan.send(FinishedMsg)
+ }
+ }
+ }
+
+ // Give the deque back to the supervisor.
+ self.chan.send(ReturnDequeMsg(self.index, deque))
+ }
+ }
+}
+
+/// A handle to the work queue that individual work units have.
+pub struct WorkerProxy<'a, QueueData, WorkData> {
+ worker: &'a mut Worker<WorkUnit<QueueData, WorkData>>,
+ ref_count: *mut AtomicUint,
+ queue_data: *const QueueData,
+}
+
+impl<'a, QueueData, WorkData: Send> WorkerProxy<'a, QueueData, WorkData> {
+ /// Enqueues a block into the work queue.
+ #[inline]
+ pub fn push(&mut self, work_unit: WorkUnit<QueueData, WorkData>) {
+ unsafe {
+ drop((*self.ref_count).fetch_add(1, SeqCst));
+ }
+ self.worker.push(work_unit);
+ }
+
+ /// Retrieves the queue user data.
+ #[inline]
+ pub fn user_data<'a>(&'a self) -> &'a QueueData {
+ unsafe {
+ mem::transmute(self.queue_data)
+ }
+ }
+}
+
+/// A work queue on which units of work can be submitted.
+pub struct WorkQueue<QueueData, WorkData> {
+ /// Information about each of the workers.
+ workers: Vec<WorkerInfo<QueueData, WorkData>>,
+ /// A port on which deques can be received from the workers.
+ port: Receiver<SupervisorMsg<QueueData, WorkData>>,
+ /// The amount of work that has been enqueued.
+ work_count: uint,
+ /// Arbitrary user data.
+ pub data: QueueData,
+}
+
+impl<QueueData: Send, WorkData: Send> WorkQueue<QueueData, WorkData> {
+ /// Creates a new work queue and spawns all the threads associated with
+ /// it.
+ pub fn new(task_name: &'static str, thread_count: uint, user_data: QueueData) -> WorkQueue<QueueData, WorkData> {
+ // Set up data structures.
+ let (supervisor_chan, supervisor_port) = channel();
+ let (mut infos, mut threads) = (vec!(), vec!());
+ for i in range(0, thread_count) {
+ let (worker_chan, worker_port) = channel();
+ let pool = BufferPool::new();
+ let (worker, thief) = pool.deque();
+ infos.push(WorkerInfo {
+ chan: worker_chan,
+ deque: Some(worker),
+ thief: thief,
+ });
+ threads.push(WorkerThread {
+ index: i,
+ port: worker_port,
+ chan: supervisor_chan.clone(),
+ other_deques: vec!(),
+ rng: weak_rng(),
+ });
+ }
+
+ // Connect workers to one another.
+ for i in range(0, thread_count) {
+ for j in range(0, thread_count) {
+ if i != j {
+ threads.get_mut(i).other_deques.push(infos[j].thief.clone())
+ }
+ }
+ assert!(threads.get(i).other_deques.len() == thread_count - 1)
+ }
+
+ // Spawn threads.
+ for thread in threads.move_iter() {
+ TaskBuilder::new().named(task_name).native().spawn(proc() {
+ let mut thread = thread;
+ thread.start()
+ })
+ }
+
+ WorkQueue {
+ workers: infos,
+ port: supervisor_port,
+ work_count: 0,
+ data: user_data,
+ }
+ }
+
+ /// Enqueues a block into the work queue.
+ #[inline]
+ pub fn push(&mut self, work_unit: WorkUnit<QueueData, WorkData>) {
+ match self.workers.get_mut(0).deque {
+ None => {
+ fail!("tried to push a block but we don't have the deque?!")
+ }
+ Some(ref mut deque) => deque.push(work_unit),
+ }
+ self.work_count += 1
+ }
+
+ /// Synchronously runs all the enqueued tasks and waits for them to complete.
+ pub fn run(&mut self) {
+ // Tell the workers to start.
+ let mut work_count = AtomicUint::new(self.work_count);
+ for worker in self.workers.mut_iter() {
+ worker.chan.send(StartMsg(worker.deque.take_unwrap(), &mut work_count, &self.data))
+ }
+
+ // Wait for the work to finish.
+ drop(self.port.recv());
+ self.work_count = 0;
+
+ // Tell everyone to stop.
+ for worker in self.workers.iter() {
+ worker.chan.send(StopMsg)
+ }
+
+ // Get our deques back.
+ for _ in range(0, self.workers.len()) {
+ match self.port.recv() {
+ ReturnDequeMsg(index, deque) => self.workers.get_mut(index).deque = Some(deque),
+ FinishedMsg => fail!("unexpected finished message!"),
+ }
+ }
+ }
+
+ pub fn shutdown(&mut self) {
+ for worker in self.workers.iter() {
+ worker.chan.send(ExitMsg)
+ }
+ }
+}
+