From cdd89d5929dc4f99b880a0166709769343883b1c Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Sat, 11 Feb 2017 19:11:06 -0600 Subject: Revert "Remove view source handler" This reverts commit d6dba00ad85ab79a3ebea3363b03e16951ce33e6. --- LICENSE.prism | 21 + embed/ephy-embed-shell.c | 18 + embed/ephy-embed-utils.c | 4 + embed/ephy-view-source-handler.c | 172 ++++++-- embed/ephy-web-view.c | 2 + embed/meson.build | 1 + po/POTFILES.in | 1 + src/resources/epiphany.gresource.xml | 2 + src/resources/prism.css | 179 +++++++++ src/resources/prism.js | 745 +++++++++++++++++++++++++++++++++++ src/window-commands.c | 32 +- 11 files changed, 1132 insertions(+), 45 deletions(-) create mode 100644 LICENSE.prism create mode 100644 src/resources/prism.css create mode 100644 src/resources/prism.js diff --git a/LICENSE.prism b/LICENSE.prism new file mode 100644 index 000000000..528949f42 --- /dev/null +++ b/LICENSE.prism @@ -0,0 +1,21 @@ +MIT LICENSE + +Copyright (c) 2012 Lea Verou + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c index 0c1d38a1a..ac08ca02c 100644 --- a/embed/ephy-embed-shell.c +++ b/embed/ephy-embed-shell.c @@ -40,6 +40,7 @@ #include "ephy-tabs-catalog.h" #include "ephy-uri-helpers.h" #include "ephy-uri-tester-shared.h" +#include "ephy-view-source-handler.h" #include "ephy-web-app-utils.h" #include "ephy-web-extension-proxy.h" @@ -68,6 +69,7 @@ typedef struct { EphyDownloadsManager *downloads_manager; EphyPermissionsManager *permissions_manager; EphyAboutHandler *about_handler; + EphyViewSourceHandler *source_handler; guint update_overview_timeout_id; guint hiding_overview_item; GDBusServer *dbus_server; @@ -175,6 +177,7 @@ ephy_embed_shell_dispose (GObject *object) g_clear_object (&priv->global_history_service); g_clear_object (&priv->global_gsb_service); g_clear_object (&priv->about_handler); + g_clear_object (&priv->source_handler); g_clear_object (&priv->user_content); g_clear_object (&priv->downloads_manager); g_clear_object (&priv->permissions_manager); @@ -675,6 +678,15 @@ about_request_cb (WebKitURISchemeRequest *request, ephy_about_handler_handle_request (priv->about_handler, request); } +static void +source_request_cb (WebKitURISchemeRequest *request, + EphyEmbedShell *shell) +{ + EphyEmbedShellPrivate *priv = ephy_embed_shell_get_instance_private (shell); + + ephy_view_source_handler_handle_request (priv->source_handler, request); +} + static void ephy_resource_request_cb (WebKitURISchemeRequest *request) { @@ -1063,6 +1075,12 @@ ephy_embed_shell_startup (GApplication *application) webkit_security_manager_register_uri_scheme_as_local (webkit_web_context_get_security_manager (priv->web_context), EPHY_ABOUT_SCHEME); + /* view source handler */ + priv->source_handler = ephy_view_source_handler_new (); + webkit_web_context_register_uri_scheme (priv->web_context, EPHY_VIEW_SOURCE_SCHEME, + (WebKitURISchemeRequestCallback)source_request_cb, + shell, NULL); + /* ephy-resource handler */ webkit_web_context_register_uri_scheme (priv->web_context, "ephy-resource", (WebKitURISchemeRequestCallback)ephy_resource_request_cb, diff --git a/embed/ephy-embed-utils.c b/embed/ephy-embed-utils.c index 7a546a8be..d83f8ee96 100644 --- a/embed/ephy-embed-utils.c +++ b/embed/ephy-embed-utils.c @@ -27,6 +27,7 @@ #include "ephy-about-handler.h" #include "ephy-settings.h" #include "ephy-string.h" +#include "ephy-view-source-handler.h" #include #include @@ -331,6 +332,9 @@ ephy_embed_utils_is_no_show_address (const char *address) if (!strcmp (address, do_not_show_address[i])) return TRUE; + if (strstr (address, EPHY_VIEW_SOURCE_SCHEME) == address) + return TRUE; + return FALSE; } diff --git a/embed/ephy-view-source-handler.c b/embed/ephy-view-source-handler.c index af6276929..fe2de7124 100644 --- a/embed/ephy-view-source-handler.c +++ b/embed/ephy-view-source-handler.c @@ -21,9 +21,12 @@ #include "config.h" #include "ephy-view-source-handler.h" +#include "ephy-embed-container.h" #include "ephy-embed-shell.h" +#include "ephy-web-view.h" #include +#include #include struct _EphyViewSourceHandler { @@ -47,13 +50,11 @@ ephy_view_source_request_new (EphyViewSourceHandler *handler, WebKitURISchemeRequest *request) { EphyViewSourceRequest *view_source_request; - EphyEmbedShell *shell = ephy_embed_shell_get_default (); - WebKitWebContext *context = ephy_embed_shell_get_web_context (shell); view_source_request = g_slice_new (EphyViewSourceRequest); - view_source_request->source_handler = handler; + view_source_request->source_handler = g_object_ref (handler); view_source_request->scheme_request = g_object_ref (request); - view_source_request->web_view = g_object_ref_sink (webkit_web_view_new_with_context (context)); + view_source_request->web_view = NULL; /* created only if required */ view_source_request->cancellable = g_cancellable_new (); view_source_request->load_changed_id = 0; @@ -66,8 +67,9 @@ ephy_view_source_request_free (EphyViewSourceRequest *request) if (request->load_changed_id > 0) g_signal_handler_disconnect (request->web_view, request->load_changed_id); + g_object_unref (request->source_handler); g_object_unref (request->scheme_request); - g_object_unref (request->web_view); + g_clear_object (&request->web_view); g_cancellable_cancel (request->cancellable); g_object_unref (request->cancellable); @@ -77,21 +79,28 @@ ephy_view_source_request_free (EphyViewSourceRequest *request) static void finish_uri_scheme_request (EphyViewSourceRequest *request, - gchar *data) + gchar *data, + GError *error) { GInputStream *stream; gssize data_length; - data_length = MIN (strlen (data), G_MAXSSIZE); - stream = g_memory_input_stream_new_from_data (data, data_length, g_free); - webkit_uri_scheme_request_finish (request->scheme_request, stream, data_length, "text/html"); + g_assert ((data && !error) || (!data && error)); + + if (error) { + webkit_uri_scheme_request_finish_error (request->scheme_request, error); + } else { + data_length = MIN (strlen (data), G_MAXSSIZE); + stream = g_memory_input_stream_new_from_data (data, data_length, g_free); + webkit_uri_scheme_request_finish (request->scheme_request, stream, data_length, "text/html"); + g_object_unref (stream); + } request->source_handler->outstanding_requests = g_list_remove (request->source_handler->outstanding_requests, request); ephy_view_source_request_free (request); - g_object_unref (stream); } static void @@ -108,10 +117,8 @@ web_resource_data_cb (WebKitWebResource *resource, data = webkit_web_resource_get_data_finish (resource, result, &length, &error); if (error) { - html = g_strdup (error->message); - length = strlen (html); + finish_uri_scheme_request (request, NULL, error); g_error_free (error); - finish_uri_scheme_request (request, html); return; } @@ -123,15 +130,32 @@ web_resource_data_cb (WebKitWebResource *resource, escaped_str = g_markup_escape_text (data_str, -1); g_free (data_str); - html = g_strdup_printf ("" - "
"
-                              "%s"
+  html = g_strdup_printf (""
+                            ""
+                          ""
+                          ""
+                            ""
+                            /* http://prismjs.com/plugins/line-numbers/ */
+                            "
"
+                              "%s"
                             "
" "", escaped_str); g_free (escaped_str); - finish_uri_scheme_request (request, html); + finish_uri_scheme_request (request, html, NULL); +} + +static void +ephy_view_source_request_begin_get_source_from_web_view (EphyViewSourceRequest *request, + WebKitWebView *web_view) +{ + WebKitWebResource *resource = webkit_web_view_get_main_resource (web_view); + g_assert (resource); + webkit_web_resource_get_data (resource, + request->cancellable, + (GAsyncReadyCallback)(web_resource_data_cb), + request); } static void @@ -139,13 +163,79 @@ load_changed_cb (WebKitWebView *web_view, WebKitLoadEvent load_event, EphyViewSourceRequest *request) { - if (load_event == WEBKIT_LOAD_FINISHED) { - WebKitWebResource *resource = webkit_web_view_get_main_resource (web_view); - webkit_web_resource_get_data (resource, - request->cancellable, - (GAsyncReadyCallback)(web_resource_data_cb), - request); - } + if (load_event == WEBKIT_LOAD_FINISHED) + ephy_view_source_request_begin_get_source_from_web_view (request, web_view); +} + +static void +ephy_view_source_request_begin_get_source_from_uri (EphyViewSourceRequest *request, + const char *uri) +{ + EphyEmbedShell *shell = ephy_embed_shell_get_default (); + WebKitWebContext *context = ephy_embed_shell_get_web_context (shell); + + request->web_view = WEBKIT_WEB_VIEW (g_object_ref_sink (webkit_web_view_new_with_context (context))); + + g_assert (request->load_changed_id == 0); + request->load_changed_id = g_signal_connect (request->web_view, "load-changed", + G_CALLBACK (load_changed_cb), + request); + + webkit_web_view_load_uri (request->web_view, uri); +} + +static gint +embed_is_displaying_matching_uri (EphyEmbed *embed, + SoupURI *uri) +{ + EphyWebView *web_view; + SoupURI *view_uri; + gint ret = -1; + + if (ephy_embed_has_load_pending (embed)) + return -1; + + web_view = ephy_embed_get_web_view (embed); + if (ephy_web_view_is_loading (web_view)) + return -1; + + view_uri = soup_uri_new (ephy_web_view_get_address (web_view)); + if (!view_uri) + return -1; + + soup_uri_set_fragment (view_uri, NULL); + ret = soup_uri_equal (view_uri, uri) ? 0 : -1; + + soup_uri_free (view_uri); + + return ret; +} + +static WebKitWebView * +get_web_view_matching_uri (SoupURI *uri) +{ + EphyEmbedShell *shell; + GtkWindow *window; + GList *embeds = NULL; + GList *found; + EphyEmbed *embed = NULL; + + shell = ephy_embed_shell_get_default (); + window = gtk_application_get_active_window (GTK_APPLICATION (shell)); + + if (!EPHY_IS_EMBED_CONTAINER (window)) + goto out; + + embeds = ephy_embed_container_get_children (EPHY_EMBED_CONTAINER (window)); + found = g_list_find_custom (embeds, uri, (GCompareFunc)embed_is_displaying_matching_uri); + + if (found) + embed = found->data; + +out: + g_list_free (embeds); + + return embed ? WEBKIT_WEB_VIEW (ephy_embed_get_web_view (embed)) : NULL; } static void @@ -155,6 +245,7 @@ ephy_view_source_request_start (EphyViewSourceRequest *request) char *modified_uri; char *decoded_fragment; const char *original_uri; + WebKitWebView *web_view; request->source_handler->outstanding_requests = g_list_prepend (request->source_handler->outstanding_requests, request); @@ -162,38 +253,49 @@ ephy_view_source_request_start (EphyViewSourceRequest *request) original_uri = webkit_uri_scheme_request_get_uri (request->scheme_request); soup_uri = soup_uri_new (original_uri); - if (!soup_uri) { - g_critical ("Failed to construct SoupURI for %s", original_uri); - finish_uri_scheme_request (request, g_strdup ("")); + if (!soup_uri || !soup_uri->fragment) { + /* Can't assert because user could theoretically input something weird */ + GError *error = g_error_new (WEBKIT_NETWORK_ERROR, + WEBKIT_NETWORK_ERROR_FAILED, + _("%s is not a valid URI"), + original_uri); + finish_uri_scheme_request (request, NULL, error); + g_error_free (error); return; } /* Convert e.g. ephy-source://gnome.org#https to https://gnome.org */ - g_assert (soup_uri->fragment); decoded_fragment = soup_uri_decode (soup_uri->fragment); soup_uri_set_scheme (soup_uri, decoded_fragment); soup_uri_set_fragment (soup_uri, NULL); modified_uri = soup_uri_to_string (soup_uri, FALSE); + g_assert (modified_uri); - g_assert(request->load_changed_id == 0); - request->load_changed_id = g_signal_connect (request->web_view, "load-changed", - G_CALLBACK (load_changed_cb), - request); - - webkit_web_view_load_uri (request->web_view, modified_uri); + web_view = get_web_view_matching_uri (soup_uri); + if (web_view) + ephy_view_source_request_begin_get_source_from_web_view (request, WEBKIT_WEB_VIEW (web_view)); + else + ephy_view_source_request_begin_get_source_from_uri (request, modified_uri); g_free (decoded_fragment); g_free (modified_uri); soup_uri_free (soup_uri); } +static void +cancel_outstanding_request (EphyViewSourceRequest *request) +{ + g_cancellable_cancel (request->cancellable); +} + static void ephy_view_source_handler_dispose (GObject *object) { EphyViewSourceHandler *handler = EPHY_VIEW_SOURCE_HANDLER (object); if (handler->outstanding_requests) { - g_list_free_full (handler->outstanding_requests, (GDestroyNotify)ephy_view_source_request_free); + g_list_foreach (handler->outstanding_requests, (GFunc)cancel_outstanding_request, NULL); + g_list_free (handler->outstanding_requests); handler->outstanding_requests = NULL; } diff --git a/embed/ephy-web-view.c b/embed/ephy-web-view.c index 77f688a00..214f8faab 100644 --- a/embed/ephy-web-view.c +++ b/embed/ephy-web-view.c @@ -43,6 +43,7 @@ #include "ephy-snapshot-service.h" #include "ephy-string.h" #include "ephy-uri-helpers.h" +#include "ephy-view-source-handler.h" #include "ephy-web-app-utils.h" #include "ephy-web-extension-proxy.h" #include "ephy-zoom.h" @@ -1717,6 +1718,7 @@ update_security_status_for_committed_load (EphyWebView *view, g_clear_pointer (&view->tls_error_failing_uri, g_free); if (!soup_uri || + strcmp (soup_uri_get_scheme (soup_uri), EPHY_VIEW_SOURCE_SCHEME) == 0 || webkit_security_manager_uri_scheme_is_local (security_manager, soup_uri->scheme) || webkit_security_manager_uri_scheme_is_empty_document (security_manager, soup_uri->scheme)) { security_level = EPHY_SECURITY_LEVEL_LOCAL_PAGE; diff --git a/embed/meson.build b/embed/meson.build index 99315d704..1e08736c1 100644 --- a/embed/meson.build +++ b/embed/meson.build @@ -26,6 +26,7 @@ libephyembed_sources = [ 'ephy-filters-manager.c', 'ephy-find-toolbar.c', 'ephy-option-menu.c', + 'ephy-view-source-handler.c', 'ephy-web-view.c', 'ephy-web-extension-proxy.c', enums diff --git a/po/POTFILES.in b/po/POTFILES.in index 8ee9cd808..710d5e636 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -11,6 +11,7 @@ embed/ephy-embed-utils.c embed/ephy-embed-utils.h embed/ephy-encodings.c embed/ephy-find-toolbar.c +embed/ephy-view-source-handler.c embed/ephy-web-view.c lib/ephy-file-helpers.c lib/ephy-gui.c diff --git a/src/resources/epiphany.gresource.xml b/src/resources/epiphany.gresource.xml index 22a01ec28..1d4ac2e8b 100644 --- a/src/resources/epiphany.gresource.xml +++ b/src/resources/epiphany.gresource.xml @@ -9,6 +9,8 @@ security-high-symbolic.png about.ini epiphany.css + prism.css + prism.js about.css error.css error.html diff --git a/src/resources/prism.css b/src/resources/prism.css new file mode 100644 index 000000000..7dc9a0a3d --- /dev/null +++ b/src/resources/prism.css @@ -0,0 +1,179 @@ +/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript&plugins=line-numbers */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre.line-numbers > code { + position: relative; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } diff --git a/src/resources/prism.js b/src/resources/prism.js new file mode 100644 index 000000000..db9f16e4b --- /dev/null +++ b/src/resources/prism.js @@ -0,0 +1,745 @@ +/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript&plugins=line-numbers */ +var _self = (typeof window !== 'undefined') + ? window // if in browser + : ( + (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) + ? self // if in worker + : {} // if in node js + ); + +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */ + +var Prism = (function(){ + +// Private helper vars +var lang = /\blang(?:uage)?-(\w+)\b/i; +var uniqueId = 0; + +var _ = _self.Prism = { + util: { + encode: function (tokens) { + if (tokens instanceof Token) { + return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias); + } else if (_.util.type(tokens) === 'Array') { + return tokens.map(_.util.encode); + } else { + return tokens.replace(/&/g, '&').replace(/ text.length) { + // Something went terribly wrong, ABORT, ABORT! + break tokenloop; + } + + if (str instanceof Token) { + continue; + } + + pattern.lastIndex = 0; + + var match = pattern.exec(str), + delNum = 1; + + // Greedy patterns can override/remove up to two previously matched tokens + if (!match && greedy && i != strarr.length - 1) { + pattern.lastIndex = pos; + match = pattern.exec(text); + if (!match) { + break; + } + + var from = match.index + (lookbehind ? match[1].length : 0), + to = match.index + match[0].length, + k = i, + p = pos; + + for (var len = strarr.length; k < len && p < to; ++k) { + p += (strarr[k].matchedStr || strarr[k]).length; + // Move the index i to the element in strarr that is closest to from + if (from >= p) { + ++i; + pos = p; + } + } + + /* + * If strarr[i] is a Token, then the match starts inside another Token, which is invalid + * If strarr[k - 1] is greedy we are in conflict with another greedy pattern + */ + if (strarr[i] instanceof Token || strarr[k - 1].greedy) { + continue; + } + + // Number of tokens to delete and replace with the new match + delNum = k - i; + str = text.slice(pos, p); + match.index -= pos; + } + + if (!match) { + continue; + } + + if(lookbehind) { + lookbehindLength = match[1].length; + } + + var from = match.index + lookbehindLength, + match = match[0].slice(lookbehindLength), + to = from + match.length, + before = str.slice(0, from), + after = str.slice(to); + + var args = [i, delNum]; + + if (before) { + args.push(before); + } + + var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias, match, greedy); + + args.push(wrapped); + + if (after) { + args.push(after); + } + + Array.prototype.splice.apply(strarr, args); + } + } + } + + return strarr; + }, + + hooks: { + all: {}, + + add: function (name, callback) { + var hooks = _.hooks.all; + + hooks[name] = hooks[name] || []; + + hooks[name].push(callback); + }, + + run: function (name, env) { + var callbacks = _.hooks.all[name]; + + if (!callbacks || !callbacks.length) { + return; + } + + for (var i=0, callback; callback = callbacks[i++];) { + callback(env); + } + } + } +}; + +var Token = _.Token = function(type, content, alias, matchedStr, greedy) { + this.type = type; + this.content = content; + this.alias = alias; + // Copy of the full string this token was created from + this.matchedStr = matchedStr || null; + this.greedy = !!greedy; +}; + +Token.stringify = function(o, language, parent) { + if (typeof o == 'string') { + return o; + } + + if (_.util.type(o) === 'Array') { + return o.map(function(element) { + return Token.stringify(element, language, o); + }).join(''); + } + + var env = { + type: o.type, + content: Token.stringify(o.content, language, parent), + tag: 'span', + classes: ['token', o.type], + attributes: {}, + language: language, + parent: parent + }; + + if (env.type == 'comment') { + env.attributes['spellcheck'] = 'true'; + } + + if (o.alias) { + var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias]; + Array.prototype.push.apply(env.classes, aliases); + } + + _.hooks.run('wrap', env); + + var attributes = ''; + + for (var name in env.attributes) { + attributes += (attributes ? ' ' : '') + name + '="' + (env.attributes[name] || '') + '"'; + } + + return '<' + env.tag + ' class="' + env.classes.join(' ') + '"' + (attributes ? ' ' + attributes : '') + '>' + env.content + ''; + +}; + +if (!_self.document) { + if (!_self.addEventListener) { + // in Node.js + return _self.Prism; + } + // In worker + _self.addEventListener('message', function(evt) { + var message = JSON.parse(evt.data), + lang = message.language, + code = message.code, + immediateClose = message.immediateClose; + + _self.postMessage(_.highlight(code, _.languages[lang], lang)); + if (immediateClose) { + _self.close(); + } + }, false); + + return _self.Prism; +} + +//Get current script and highlight +var script = document.currentScript || [].slice.call(document.getElementsByTagName("script")).pop(); + +if (script) { + _.filename = script.src; + + if (document.addEventListener && !script.hasAttribute('data-manual')) { + if(document.readyState !== "loading") { + if (window.requestAnimationFrame) { + window.requestAnimationFrame(_.highlightAll); + } else { + window.setTimeout(_.highlightAll, 16); + } + } + else { + document.addEventListener('DOMContentLoaded', _.highlightAll); + } + } +} + +return _self.Prism; + +})(); + +if (typeof module !== 'undefined' && module.exports) { + module.exports = Prism; +} + +// hack for components to work correctly in node.js +if (typeof global !== 'undefined') { + global.Prism = Prism; +} +; +Prism.languages.markup = { + 'comment': //, + 'prolog': /<\?[\w\W]+?\?>/, + 'doctype': //i, + 'cdata': //i, + 'tag': { + pattern: /<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i, + inside: { + 'tag': { + pattern: /^<\/?[^\s>\/]+/i, + inside: { + 'punctuation': /^<\/?/, + 'namespace': /^[^\s>\/:]+:/ + } + }, + 'attr-value': { + pattern: /=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i, + inside: { + 'punctuation': /[=>"']/ + } + }, + 'punctuation': /\/?>/, + 'attr-name': { + pattern: /[^\s>\/]+/, + inside: { + 'namespace': /^[^\s>\/:]+:/ + } + } + + } + }, + 'entity': /&#?[\da-z]{1,8};/i +}; + +// Plugin to make entity title show the real entity, idea by Roman Komarov +Prism.hooks.add('wrap', function(env) { + + if (env.type === 'entity') { + env.attributes['title'] = env.content.replace(/&/, '&'); + } +}); + +Prism.languages.xml = Prism.languages.markup; +Prism.languages.html = Prism.languages.markup; +Prism.languages.mathml = Prism.languages.markup; +Prism.languages.svg = Prism.languages.markup; + +Prism.languages.css = { + 'comment': /\/\*[\w\W]*?\*\//, + 'atrule': { + pattern: /@[\w-]+?.*?(;|(?=\s*\{))/i, + inside: { + 'rule': /@[\w-]+/ + // See rest below + } + }, + 'url': /url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i, + 'selector': /[^\{\}\s][^\{\};]*?(?=\s*\{)/, + 'string': { + pattern: /("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/, + greedy: true + }, + 'property': /(\b|\B)[\w-]+(?=\s*:)/i, + 'important': /\B!important\b/i, + 'function': /[-a-z0-9]+(?=\()/i, + 'punctuation': /[(){};:]/ +}; + +Prism.languages.css['atrule'].inside.rest = Prism.util.clone(Prism.languages.css); + +if (Prism.languages.markup) { + Prism.languages.insertBefore('markup', 'tag', { + 'style': { + pattern: /()[\w\W]*?(?=<\/style>)/i, + lookbehind: true, + inside: Prism.languages.css, + alias: 'language-css' + } + }); + + Prism.languages.insertBefore('inside', 'attr-value', { + 'style-attr': { + pattern: /\s*style=("|').*?\1/i, + inside: { + 'attr-name': { + pattern: /^\s*style/i, + inside: Prism.languages.markup.tag.inside + }, + 'punctuation': /^\s*=\s*['"]|['"]\s*$/, + 'attr-value': { + pattern: /.+/i, + inside: Prism.languages.css + } + }, + alias: 'language-css' + } + }, Prism.languages.markup.tag); +}; +Prism.languages.clike = { + 'comment': [ + { + pattern: /(^|[^\\])\/\*[\w\W]*?\*\//, + lookbehind: true + }, + { + pattern: /(^|[^\\:])\/\/.*/, + lookbehind: true + } + ], + 'string': { + pattern: /(["'])(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, + greedy: true + }, + 'class-name': { + pattern: /((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i, + lookbehind: true, + inside: { + punctuation: /(\.|\\)/ + } + }, + 'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/, + 'boolean': /\b(true|false)\b/, + 'function': /[a-z0-9_]+(?=\()/i, + 'number': /\b-?(?:0x[\da-f]+|\d*\.?\d+(?:e[+-]?\d+)?)\b/i, + 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/, + 'punctuation': /[{}[\];(),.:]/ +}; + +Prism.languages.javascript = Prism.languages.extend('clike', { + 'keyword': /\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\b/, + 'number': /\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/, + // Allow for all non-ASCII characters (See http://stackoverflow.com/a/2008444) + 'function': /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*(?=\()/i, + 'operator': /--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*\*?|\/|~|\^|%|\.{3}/ +}); + +Prism.languages.insertBefore('javascript', 'keyword', { + 'regex': { + pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/, + lookbehind: true, + greedy: true + } +}); + +Prism.languages.insertBefore('javascript', 'string', { + 'template-string': { + pattern: /`(?:\\\\|\\?[^\\])*?`/, + greedy: true, + inside: { + 'interpolation': { + pattern: /\$\{[^}]+\}/, + inside: { + 'interpolation-punctuation': { + pattern: /^\$\{|\}$/, + alias: 'punctuation' + }, + rest: Prism.languages.javascript + } + }, + 'string': /[\s\S]+/ + } + } +}); + +if (Prism.languages.markup) { + Prism.languages.insertBefore('markup', 'tag', { + 'script': { + pattern: /()[\w\W]*?(?=<\/script>)/i, + lookbehind: true, + inside: Prism.languages.javascript, + alias: 'language-javascript' + } + }); +} + +Prism.languages.js = Prism.languages.javascript; +(function() { + +if (typeof self === 'undefined' || !self.Prism || !self.document) { + return; +} + +Prism.hooks.add('complete', function (env) { + if (!env.code) { + return; + } + + // works only for wrapped inside
 (not inline)
+	var pre = env.element.parentNode;
+	var clsReg = /\s*\bline-numbers\b\s*/;
+	if (
+		!pre || !/pre/i.test(pre.nodeName) ||
+			// Abort only if nor the 
 nor the  have the class
+		(!clsReg.test(pre.className) && !clsReg.test(env.element.className))
+	) {
+		return;
+	}
+
+	if (env.element.querySelector(".line-numbers-rows")) {
+		// Abort if line numbers already exists
+		return;
+	}
+
+	if (clsReg.test(env.element.className)) {
+		// Remove the class "line-numbers" from the 
+		env.element.className = env.element.className.replace(clsReg, '');
+	}
+	if (!clsReg.test(pre.className)) {
+		// Add the class "line-numbers" to the 
+		pre.className += ' line-numbers';
+	}
+
+	var match = env.code.match(/\n(?!$)/g);
+	var linesNum = match ? match.length + 1 : 1;
+	var lineNumbersWrapper;
+
+	var lines = new Array(linesNum + 1);
+	lines = lines.join('');
+
+	lineNumbersWrapper = document.createElement('span');
+	lineNumbersWrapper.setAttribute('aria-hidden', 'true');
+	lineNumbersWrapper.className = 'line-numbers-rows';
+	lineNumbersWrapper.innerHTML = lines;
+
+	if (pre.hasAttribute('data-start')) {
+		pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
+	}
+
+	env.element.appendChild(lineNumbersWrapper);
+
+});
+
+}());
diff --git a/src/window-commands.c b/src/window-commands.c
index 93d9616b8..c52741edd 100644
--- a/src/window-commands.c
+++ b/src/window-commands.c
@@ -51,6 +51,7 @@
 #include "ephy-settings.h"
 #include "ephy-shell.h"
 #include "ephy-string.h"
+#include "ephy-view-source-handler.h"
 #include "ephy-web-app-utils.h"
 #include "ephy-zoom.h"
 
@@ -1815,6 +1816,23 @@ static void
 view_source_embedded (const char *uri, EphyEmbed *embed)
 {
   EphyEmbed *new_embed;
+  SoupURI *soup_uri;
+  char *source_uri;
+
+  /* Abort if we're already in view source mode */
+  if (strstr (uri, EPHY_VIEW_SOURCE_SCHEME) == uri)
+    return;
+
+  soup_uri = soup_uri_new (uri);
+  if (!soup_uri) {
+    g_critical ("Failed to construct SoupURI for %s", uri);
+    return;
+  }
+
+  /* Convert e.g. https://gnome.org to ephy-source://gnome.org#https */
+  soup_uri_set_fragment (soup_uri, soup_uri->scheme);
+  soup_uri_set_scheme (soup_uri, EPHY_VIEW_SOURCE_SCHEME);
+  source_uri = soup_uri_to_string (soup_uri, FALSE);
 
   new_embed = ephy_shell_new_tab
                 (ephy_shell_get_default (),
@@ -1822,13 +1840,11 @@ view_source_embedded (const char *uri, EphyEmbed *embed)
                 embed,
                 EPHY_NEW_TAB_JUMP | EPHY_NEW_TAB_APPEND_AFTER);
 
-  /* FIXME: Implement embedded view source mode using a custom URI handler and a
-   * javascript library for the syntax highlighting.
-   * https://bugzilla.gnome.org/show_bug.cgi?id=731558
-   */
-  webkit_web_view_load_uri
-    (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (new_embed), uri);
+  webkit_web_view_load_uri (EPHY_GET_WEBKIT_WEB_VIEW_FROM_EMBED (new_embed), source_uri);
   gtk_widget_grab_focus (GTK_WIDGET (new_embed));
+
+  g_free (source_uri);
+  soup_uri_free (soup_uri);
 }
 
 static void
@@ -2015,15 +2031,11 @@ window_cmd_page_source (GSimpleAction *action,
 
   address = ephy_web_view_get_address (ephy_embed_get_web_view (embed));
 
-#if 0
- FIXME: Disabled due to bug #738475
-
   if (g_settings_get_boolean (EPHY_SETTINGS_MAIN,
                               EPHY_PREFS_INTERNAL_VIEW_SOURCE)) {
     view_source_embedded (address, embed);
     return;
   }
-#endif
 
   user_time = gtk_get_current_event_time ();
 
-- 
cgit v1.2.1