Вложенные ссылки

Котики играют во вложенные ссылки

Проблема

В спе­ци­фи­ка­ции HTML есть мно­же­ство раз­но­об­раз­ных за­пре­тов. Обос­но­ван­ность мно­гих из них очень хо­чет­ся оспо­рить. Один из при­ме­ров, с ко­то­рым мне при­хо­дит­ся стал­ки­вать­ся чаще все­го, —вло­жен­ные ссылки.

Спе­ци­фи­ка­ция пря­мо за­пре­ща­ет вкла­ды­вать одну ссыл­ку в другую:

The a el­ement

[…]

Con­tent model: trans­par­ent, but there must be no in­ter­ac­tive con­tent de­scendant.

Если так сде­лать, то пар­сер бра­у­зе­ра вас не пой­мёт и, как толь­ко встре­тит от­кры­ва­ю­щий тег вло­жен­ной ссыл­ки, за­кро­ет ро­ди­тель­скую ссыл­ку пе­ред собой:

<a href="#Foo">
    Foo
    <a href="#Bar">
        Bar
    </a>
    Baz
</a>

в гла­зах бра­у­зе­ра ста­нет чем-то та­ким —

<a href="#Foo">
    Foo
    </a><a href="#Bar">
        Bar
    </a>
    Baz

Жи­вой пример:

Од­на­ко, су­ще­ству­ют сце­на­рии, в ко­то­рых за­хо­чет­ся по­ло­жить одну ссыл­ку в дру­гую, не об­ра­щая вни­ма­ние на за­преты.

Вот и в оче­ред­ной раз, в рам­ках ра­бо­чей за­да­чи, я ока­зал­ся в та­кой си­ту­а­ции. Рань­ше я встре­чал и ис­поль­зо­вал мно­же­ство ва­ри­ан­тов того, как мож­но его обой­ти. Это и эму­ля­ция внут­рен­них ссы­лок на JS (на­при­мер, че­рез ба­наль­ный onclick), и по­зи­ци­о­ни­ро­ва­ние од­ной из ссы­лок во­круг ро­ди­тель­ско­го кон­тей­не­ра (см, на­при­мер, со­от­вет­ству­ю­щее ре­ше­ние Гар­ри Ро­берт­са), но все эти ва­ри­ан­ты —яв­ные ко­сты­ли. Ис­поль­зуя их, мы либо те­ря­ем всю на­тив­ность обыч­ных ссы­лок, либо по­лу­ча­ем огра­ни­чен­ное чис­ло сце­на­ри­ев, в ко­то­рых та­кие об­ход­ные пути сра­бо­тают.

Пе­ре­про­бо­вав в го­ло­ве все ва­ри­ан­ты, я по­нял, что для моей за­да­чи мо­жет по­дой­ти толь­ко пол­ная эму­ля­ция на JS —сред­ства­ми чи­сто­го CSS до­стичь того, что мне тре­бо­ва­лось, ока­за­лось невоз­мож­но. Но все мы зна­ем, что эму­ли­ро­вать на­тив­ные эле­мен­ты на JS —одно из са­мых небла­го­дар­ных дел. И я ре­шил по­экс­пе­ри­мен­ти­ро­вать ещё.

И —на­шёл ре­ше­ние. При этом, чи­сто HTML-ре­ше­ние, да­ю­щее воз­мож­ность вкла­ды­вать лю­бое ко­ли­че­ство на­тив­ных ссы­лок друг в друга.

Решение

<a href="#a">
    Foo
    <object type="lol/wut">
        <a href="#b">
            Bar
        </a>
    </object>
    Baz
</a>

Всё, что мы, в ито­ге, де­ла­ем —про­кла­ды­ва­ем меж­ду внут­рен­ней и внеш­ней ссыл­кой объ­ект. Вне­зап­но, это ра­бо­та­ет: все пар­се­ры со­вре­мен­ных бра­у­зе­ров пе­ре­ста­ют взры­вать раз­мет­ку и на­чи­на­ют вос­при­ни­мать вло­жен­ность те­гов так, как нам это­го хо­чет­ся. Ура.

Почему это работает

Что та­кое, в тео­рии, объ­ек­ты? Это некие внеш­ние сущ­но­сти, тип ко­то­рых за­да­ёт­ся ат­ри­бу­том type, а со­дер­жи­мое/​​ссыл­ка на объ­ект за­да­ёт­ся ат­ри­бу­том data. Со­дер­жи­мое же меж­ду от­кры­ва­ю­щим и за­кры­ва­ю­щим те­гом ob­ject на са­мом деле яв­ля­ет­ся фол­бе­ком, и долж­но отоб­ра­жать­ся в том слу­чае, если бра­у­зер не спо­со­бен по ка­кой-либо при­чине отоб­ра­зить со­от­вет­ству­ю­щее со­дер­жи­мое. На­при­мер, если в бра­у­зе­ре не уста­нов­лен опре­де­лён­ный плагин.

Если про­пи­сать в ат­ри­бут type неиз­вест­ный при­ро­де MIME-тип, то бра­у­зер сра­зу же пе­рей­дёт к отоб­ра­же­нию фол­бе­ка. Но он это сде­ла­ет (На са­мом деле, см. до­пол­не­ние к ста­тье) и в том слу­чае, если мы во­об­ще не за­да­дим ни один из «обя­за­тель­ных» ат­ри­бутов.

Та­ким об­ра­зом, об­рам­ляя лю­бой HTML в та­кой без­атри­бут­ный <ob­ject>, мы по­лу­ча­ем про­сто эле­мент-врап­пер с со­дер­жи­мым. Но врап­пер с очень необыч­ным свой­ством: лю­бое его со­дер­жи­мое бу­дет вер­но рас­по­зна­но пар­се­ром вне за­ви­си­мо­сти от того, ка­кой у объ­ек­та был кон­текст. Ис­поль­зуя это свой­ство, мы мо­жем, на­ко­нец, вло­жить ссыл­ку в дру­гую ссылку.

Пред­по­ла­гаю, что та­кое по­ве­де­ние объ­ек­тов обу­слов­ле­но тем, что эти фол­бе­ки ис­поль­зу­ют­ся чаще все­го для того, что­бы по­ка­зать ссыл­ку вида «у вас не уста­нов­лен наш за­ме­ча­тель­ный пла­гин, ска­чай­те же его!» для вся­ких объ­ек­тов (на­при­мер, флеш-ро­ли­ков). При этом мно­гие раз­ра­бот­чи­ки на­вер­ня­ка хо­те­ли ис­поль­зо­вать объ­ект как обыч­ный кон­тент, то есть вкла­ды­вать его в ссыл­ку, в па­ра­граф, в за­го­ло­вок —да куда угод­но. И тут бра­у­зе­рам при­ш­лось пе­ре­стра­хо­вать­ся и раз­ре­шить вкла­ды­вать в объ­ект всё что угод­но, что­бы при ко­пи­па­сте кода объ­ек­та от­ку­да-либо и встав­ке его в со­дер­жи­мое стра­ни­цы у ав­то­ров ни­че­го не сло­малось.

Поддержка браузерами

В неко­то­рых бра­у­зе­рах та­кое по­ве­де­ние по­яви­лось не сразу.

  • In­ter­net Ex­plorer под­дер­жи­ва­ет вло­жен­ные объ­ек­ты толь­ко с де­вя­той версии.

  • Fire­fox —с чет­вёртой.

  • Opera —с как ми­ни­мум де­вя­той (мо­жет, и с бо­лее ран­ней —я не стал углуб­лять­ся ещё даль­ше).

  • Веб­ки­ты —все, что про­ве­рял, Са­фа­ри —точ­но с 5.1, Хром —с 14, даль­ше не по­шёл.

Оче­вид­но, что в боль­шин­стве слу­ча­ев нам мо­жет по­тре­бо­вать­ся лишь под­держ­ка ста­рых IE, все осталь­ные бра­у­зе­ры до­ста­точ­но хо­ро­шо об­нов­ля­ют­ся для того, что­бы те вер­сии, в ко­то­рых были про­бле­мы с этим трю­ком, ушли из всех спис­ков сов­ме­сти­мости.

Фолбек для IE

К со­жа­ле­нию, я не знаю про­сто­го спо­со­ба обой­ти эту про­бле­му в ста­рых IE. Как ми­ни­мум, мож­но по­про­бо­вать по­пра­вить си­ту­а­цию так, что­бы ни­че­го не взры­ва­лось —на­при­мер, обер­нуть теги внут­рен­ней ссыл­ки в услов­ные ком­мен­тарии:

<a href="…">
    текст основной ссылки…
    <object>
        <!--[if gte IE 9]><!--><a href="…"><!--<![endif]-->
            content of the nested link…
        <!--[if gte IE 9]><!--></a><!--<![endif]-->
    </object>
</a>

Если по­доб­ная по­те­ря функ­ци­о­наль­но­сти вас устро­ит —от­лич­но, ина­че (Са­мые лю­бо­пыт­ные мо­гут по­про­бо­вать по­ду­мать: мож­но ли со­ору­дить ка­кой-ни­будь фол­бек, ис­поль­зуя экс­преш­ны?) же при­дёт­ся ис­поль­зо­вать в услов­ных ком­мен­та­ри­ях иные фол­бе­ки для этой про­блемы.

Это валидно?

Нет, ни разу. Это не ва­лид­но, по­то­му что у объ­ек­та нет ни од­но­го из тре­бу­е­мых спе­ци­фи­ка­ци­ей ат­ри­бу­тов. Мож­но было бы ука­зать ка­кой-либо ва­лид­ный mime-тип вро­де type="lol/​​wut", и сам по себе та­кой объ­ект стал бы ва­лид­ным, но, как толь­ко мы вло­жим в него ссыл­ку, ва­ли­да­тор нач­нёт на эту вло­жен­ность ру­гаться.

Оче­вид­но, что ва­ли­да­тор —дав­но не по­ка­за­тель чего-либо, кро­ме фор­маль­но­го со­от­вет­ствия кода спе­ци­фи­ка­ци­ям. В дан­ном слу­чае само по­доб­ное ис­поль­зо­ва­ние ссыл­ки внут­ри объ­ек­та внут­ри ссыл­ки мо­жет быть со­вер­шен­но оправ­дан­ным (при­ме­ры типа «ска­чать пла­гин»), по­это­му он не дол­жен вы­зы­вать ошиб­ку ва­ли­дации.

Бо­лее того, я не вижу ни од­ной при­чи­ны, по­че­му нель­зя было бы раз­ре­шить вкла­ды­вать ссыл­ки внутрь дру­гих ссы­лок по умол­ча­нию. Нет ни-ка-ких при­чин для это­го. Но су­ще­ству­ет очень мно­го сце­на­ри­ев, в ко­то­рых по­доб­ное по­ве­де­ние со­вер­шен­но необ­хо­димо.

Примеры использования

Сна­ча­ла я хо­тел по­дроб­но опи­сать все воз­мож­ные сце­на­рии, в ко­то­рых мож­но и нуж­но ис­поль­зо­вать вло­жен­ные ссыл­ки, с жи­вы­ми при­ме­ра­ми и всем та­ким. Но по­том по­нял, что эти при­ме­ры ни­ко­го не убе­дят: тем, кому нуж­на эта воз­мож­ность, бу­дет до­ста­точ­но пер­во­го ра­бо­та­ю­ще­го при­ме­ра выше, осталь­ных ни­че­го, кро­ме их соб­ствен­но­го опы­та, не убе­дит. А ещё это очень за­трат­но —вер­стать столь­ко при­ме­ров. Так что я сухо пе­ре­чис­лю их:

  • Вы­держ­ки из ста­тей, ко­гда в снип­пе­те, со­дер­жа­щем пер­вые несколь­ко пред­ло­же­ний, мо­гут ока­зать­ся ссыл­ки, ко­то­рые не за­хо­чет­ся вы­ре­зать при со­зда­нии снип­пета.

  • Снос­ки и вло­жен­ные тер­ми­ны, ко­то­рые мо­гут ока­зать­ся внут­ри ссылок.

  • Лю­бой слож­ный ин­тер­фейс со вло­жен­ны­ми сущ­но­стя­ми, ко­то­рые мож­но пред­ста­вить в виде ссы­лок. Это и тви­ты в лю­бом ин­тер­фей­се твит­те­ра, сами по себе ве­ду­щие на рас­ши­рен­ную стра­ни­цу тви­та, и при этом со­дер­жа­щие дру­гие ссыл­ки внут­ри: на поль­зо­ва­те­лей и внеш­ние стра­ни­цы. Это и поч­то­вые ин­тер­фей­сы, где в снип­пе­те пись­ма, яв­ля­ю­щим­ся ссыл­кой на пись­мо, мо­гут ока­зать­ся дру­гие ссыл­ки —на тред, на вло­же­ния, на мет­ки и т.д.

*

В за­вер­ше­ние сто­ит ска­зать, что этот трюк мож­но про­вер­нуть с лю­бым со­дер­жи­мым стра­ни­цы, ко­то­рое хо­чет­ся ис­поль­зо­вать там, где спе­ци­фи­ка­ция явно за­пре­ща­ет это делать.

На­при­мер, не так дав­но по­яви­лась воз­мож­ность ис­поль­зо­вать эле­мен­ты de­tails и fig­ure. Но, толь­ко по­ду­май­те: по спе­ци­фи­ка­ции они мо­гут на­хо­дить­ся толь­ко в блоч­ном кон­тек­сте. У вас не мо­жет быть ил­лю­стра­ции с под­пи­сью, при­вя­зан­ной к опре­де­лён­но­му сло­ву в аб­за­це, а та­к­же не мо­жет быть рас­ши­рен­но­го опи­са­ния ка­ко­го-либо сло­ва или пред­ло­же­ния (ска­жем, для сно­сок; ка­кие бы вы вы­бра­ли теги для сно­сок внут­ри аб­за­цев?)

Трюк с <ob­ject> ре­ша­ет все эти про­бле­мы. Во­прос толь­ко в том, бу­дет ли его ис­поль­зо­ва­ние оправ­дан­ным. Лич­но я счи­таю, что мно­гие за­пре­ты в спе­ци­фи­ка­ци­ях бес­смыс­лен­ны, и воз­мож­ность обой­ти их при ра­зум­ной ар­гу­мен­та­ции бес­ценна.

Update from 2015-03-05

Вла­ди­мир Род­кин об­на­ру­жил, что пла­гин Flash­block для Fire­fox уби­ра­ет со стра­ниц «сло­ман­ные объ­ек­ты», и он счи­та­ет та­ко­вы­ми без­атри­бут­ные <ob­ject>. До­бав­ле­ние неиз­вест­но­го при­ро­де mime-типа вро­де type="lol/​​wut" ре­ша­ет эту про­бле­му и ФФ на­чи­на­ет пра­виль­но вос­при­ни­мать объект.